diff --git a/requirements.txt b/requirements.txt
index 377c7c9f97eac1d0c40fe4289561fc668c3902f9..90e189fe79eb878c9fc871a5f6e8306a4c968d2d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -16,3 +16,4 @@ setuptools>=40.6.2
 wheel>=0.33.1
 pytz>=2019.1
 -e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud
+-e git+https://open.greenhost.net/openappstack/oas_behave#egg=oas_behave
diff --git a/test/behave/README.md b/test/behave/README.md
index b15cd6a3f044e6b8a5ccb6c9d833ea9a2b1add84..9cafd5ed9e1c254e22db12d96d6891e18df483e8 100644
--- a/test/behave/README.md
+++ b/test/behave/README.md
@@ -1,5 +1,21 @@
 # Run behave test manually
 
+## Prerequisites
+
+By default the behave tests use the Chrome/Chromium webdriver. You can use the
+Firefox webdriver by adding `-D browser=firefox` to the behave command line
+options.
+Depending on your choice you need to have the matching webdriver binary installed.
+I.e for Debian/Ubuntu use:
+
+    apt install chromium-chromedriver
+
+or to use Firefox:
+
+    apt install firefox-geckodriver
+
+## Usage
+
 Change to the `test/behave` directory and run:
 
 For nextcloud test:
diff --git a/test/behave/features/environment.py b/test/behave/features/environment.py
index bdcedf221e13e1a5dcd9e0f6f51177938d769453..6859d51c8878eaf82c0ce3bdfd8bfbf9904a008c 100644
--- a/test/behave/features/environment.py
+++ b/test/behave/features/environment.py
@@ -1,77 +1 @@
-"""Basic setup for behave and chromedriver."""
-import behave_webdriver
-import os
-import re
-import time
-from behave_webdriver.driver import ChromeOptions
-
-
-def save_screenshot(context, step):
-    """Save a screenshot to ./screenshots."""
-    timestamp = time.strftime("%Y-%m-%dT%H:%M:%S")
-    filename = re.sub('\W', '-', '{} failed {}'.format(timestamp,
-                                                       str(step.name)))
-    filepath = os.path.join('screenshots', filename + '.png')
-    if not os.path.exists('screenshots'):
-        os.mkdir('screenshots')
-
-    print('Saving screenshot to %s' % filepath)
-    context.behave_driver.save_screenshot(filepath)
-
-
-def before_all(context):
-    """Run at the very beginning."""
-    userdata = context.config.userdata
-
-    headless = userdata.get('headless', 'False')
-
-    chrome_options = ChromeOptions()
-    if headless == 'True':
-        chrome_options.add_argument('--headless')
-    chrome_options.add_argument('--no-sandbox')
-    chrome_options.add_argument('--disable-dev-shm-usage')
-    context.behave_driver = behave_webdriver.Chrome(
-        chrome_options=chrome_options)
-
-
-def before_tag(context, tag):
-    def get_values(app):
-        values = {}
-        values['url'] = userdata.get(app + '.url')
-        values['username'] = \
-            userdata.get(app + '.username', 'admin')
-        values['password'] = \
-            userdata.get(app + '.password')
-        assert values['url'], '{0}.url variable missing in' \
-            'userdata. Provide it with "-D {0}.url".'.format(app)
-        assert values['username'], '{0}.username variable missing in' \
-            'userdata. Provide it with "-D {0}.username".'.format(app)
-        assert values['password'], '{0}.password variable missing in' \
-            'userdata. Provide it with "-D {0}.password".'.format(app)
-        return(values)
-
-    userdata = context.config.userdata
-    if tag == 'grafana':
-        context.grafana = get_values('grafana')
-
-    if tag == 'nextcloud':
-        context.nextcloud = get_values('nextcloud')
-
-
-
-def after_all(context):
-    """Cleanup after tests run."""
-    context.behave_driver.quit()
-
-
-def after_step(context, step):
-    """Save screeshot if step fails."""
-    if step.status == 'failed':
-        save_screenshot(context, step)
-        print("Console log output:")
-        for message in context.behave_driver.get_log('browser'):
-            print("  Level: {}".format(message['level']))
-            print("  Timestamp: {}".format(message['timestamp']))
-            print("  Source: {}".format(message['source']))
-            print("  Message: {}\n".format(message['message']))
-
+from oas_behave.environment import *
diff --git a/test/behave/features/steps/login.py b/test/behave/features/steps/login.py
deleted file mode 100644
index b45ad5b90eb67616aa108e61e3c212cba674a031..0000000000000000000000000000000000000000
--- a/test/behave/features/steps/login.py
+++ /dev/null
@@ -1,56 +0,0 @@
-"""Custom steps for login tests."""
-
-import random
-import string
-
-from behave import given, when
-from behave_webdriver.steps import *
-
-
-def before_all(context):
-    pass  # login and save cookies here
-
-@when(u'I open the onlyoffice URL')
-@given(u'I open the onlyoffice URL')
-def step_impl(context):
-    """Open onlyoffice URL."""
-    print(context.nextcloud)
-    context.behave_driver.get(context.config.userdata.get('onlyoffice.url'))
-
-@when(u'I open the nextcloud URL')
-@given(u'I open the nextcloud URL')
-def step_impl(context):
-    """Open nextcloud URL."""
-    context.behave_driver.get(context.nextcloud['url'])
-
-
-@when(u'I open the grafana URL')
-@given(u'I open the grafana URL')
-def step_impl(context):
-    """Open grafana URL."""
-    context.behave_driver.get(context.grafana['url'])
-
-@when(u'I enter the "{section}" "{cred_type}" in the inputfield "{element}"')
-def step_impl(context, section, cred_type, element):
-    """Enter username/password into login inputfields."""
-    elem = context.behave_driver.get_element(element)
-    elem.clear()
-
-    context_section = getattr(context, section)
-    value = context_section[cred_type]
-    elem.send_keys(value)
-
-@when(u'I add a random string to the inputfield "{element}"')
-def step_impl(context, element):
-    """Add a random string of 20 characters to the specified input field"""
-    random_string = ''.join(random.choices(string.ascii_letters, k=20))
-    context.execute_steps(
-        'When I add "{}" to the inputfield "{}"'.format(
-            random_string, element))
-
-@when(u'I close the nextcloud welcome wizard if it exists')
-def step_impl(context):
-    """Checks if the #firstrunwizard element exists and closes it if so"""
-    if context.behave_driver.element_exists("#firstrunwizard"):
-        context.execute_steps(
-            'When I click on the element "a.close.icon-close"')
diff --git a/test/behave/features/steps/steps.py b/test/behave/features/steps/steps.py
new file mode 100644
index 0000000000000000000000000000000000000000..b41cf10f0d1c796ccb85936963358ab672362e45
--- /dev/null
+++ b/test/behave/features/steps/steps.py
@@ -0,0 +1 @@
+from oas_behave.common_steps import *