diff --git a/.gitignore b/.gitignore
index 8682767ec2a5ac50995b767f6e165df96f6cacb0..47c3a50c12a4aa68932408e86c6c11dfbfb3ef23 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,13 +15,11 @@
 # Ignore files created during CI
 /test/group_vars/all/
 /test/inventory*
-/test/behave/behave.ini
-/test/behave/rerun_failing.features
 /clusters
 /install/installation-kustomization/*.txt
 
 # Ignore files created during tests
-/test/behave/**/screenshots/
+Screenshot-*.png
 
 # Etc
 __pycache__
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 218423a5beaa925901b97cc679e3b1de3f051823..bd4ec365d2c6c10893f52d188fda94289765d17f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -94,12 +94,12 @@ include:
         - flux2/apps/$APP/*.yaml
         - flux2/cluster/optional/$APP/*.yaml
         - install/install-${APP}.sh
-        - test/behave/features/$APP.feature
+        - test/taiko/*
     - if: '$TRIGGER_JOBS =~ /enable-nextcloud/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-nextcloud/'
     - if: '$CI_COMMIT_BRANCH == "master"'
 
-.prometheus_stack_rules:
+.kube_prometheus_stack_rules:
   extends:
     - .general_rules
 
@@ -117,7 +117,7 @@ include:
         - flux2/apps/$APP/*.yaml
         - flux2/cluster/optional/$APP/*.yaml
         - install/install-${APP}.sh
-        - test/behave/features/$APP.feature
+        - test/taiko/*
     - if: '$TRIGGER_JOBS =~ /enable-rocketchat/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-rocketchat/'
     - if: '$CI_COMMIT_BRANCH == "master"'
@@ -127,7 +127,7 @@ include:
     - changes:
         - flux2/core/base/$APP/*.yaml
         - install/install-openappstack.sh
-        - test/behave/features/$APP.feature
+        - test/taiko/*
     - if: '$TRIGGER_JOBS =~ /enable-single-sign-on/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-single-sign-on/'
     - if: '$CI_COMMIT_BRANCH == "master"'
@@ -138,7 +138,7 @@ include:
         - flux2/apps/$APP/*.yaml
         - flux2/cluster/optional/$APP/*.yaml
         - install/install-${APP}.sh
-        - test/behave/features/$APP.feature
+        - test/taiko/*
     - if: '$TRIGGER_JOBS =~ /enable-wordpress/'
     - if: '$CI_COMMIT_MESSAGE =~ /TRIGGER_JOBS=.*enable-wordpress/'
     - if: '$CI_COMMIT_BRANCH == "master"'
@@ -415,7 +415,7 @@ kube-prometheus-stack-helm-release:
     APP: "kube-prometheus-stack"
   extends:
     - .base-helm-release
-    - .prometheus_stack_rules
+    - .kube_prometheus_stack_rules
 
 single-sign-on-helm-release:
   variables:
@@ -591,7 +591,7 @@ kube-prometheus-stack-ready:
     - job: setup-openappstack
   extends:
     - .apps-ready
-    - .prometheus_stack_rules
+    - .kube_prometheus_stack_rules
 
 rocketchat-ready:
   variables:
@@ -656,7 +656,7 @@ kube-prometheus-stack-cert:
     - job: setup-openappstack
   extends:
     - .apps-cert
-    - .prometheus_stack_rules
+    - .kube_prometheus_stack_rules
 
 rocketchat-cert:
   variables:
@@ -708,7 +708,7 @@ testinfra:
 kube-prometheus-stack-alerts:
   stage: health-test
   variables:
-    # APP var is used in job specific rules (i.e. .prometheus_stack_rules)
+    # APP var is used in job specific rules (i.e. .kube_prometheus_stack_rules)
     APP: "kube-prometheus-stack"
   allow_failure: true
   script:
@@ -717,7 +717,7 @@ kube-prometheus-stack-alerts:
     - pytest -s -m 'prometheus' --connection=ansible --ansible-inventory=${CLUSTER_DIR}/inventory.yml --hosts='ansible://*'
   extends:
     - .ssh_setup
-    - .prometheus_stack_rules
+    - .kube_prometheus_stack_rules
   needs:
     - job: kube-prometheus-stack-ready
     - job: setup-openappstack
@@ -727,63 +727,62 @@ kube-prometheus-stack-alerts:
 # Stage: integration-test
 # =======================
 #
-# Runs integration tests for most apps using behave
+# Runs integration tests for most apps using taiko
 
-.behave:
+.taiko:
   stage: integration-test
   script:
     - *debug_information
-    # Run the behave tests for specific app
-    - python3 -m openappstack $HOSTNAME test --behave-headless --behave-ignore-certificate-errors --behave-tags $APP || python3 -m openappstack $HOSTNAME test --behave-headless --behave-ignore-certificate-errors --behave-tags $APP --behave-rerun-failing
+    # Run the taiko tests for specific app
+    - python3 -m openappstack $HOSTNAME test --apps $APP
   retry: 2
   artifacts:
     paths:
-      - test/behave/screenshots/
+      - test/taiko/Screenshot*
     expire_in: 1 month
     when: on_failure
   extends:
     - .ssh_setup
   interruptible: true
 
-kube-prometheus-stack-behave:
+grafana-taiko:
   variables:
-    APP: "kube-prometheus-stack"
+    APP: "grafana"
   needs:
     - job: kube-prometheus-stack-cert
     - job: setup-openappstack
   extends:
-    - .behave
-    - .prometheus_stack_rules
+    - .taiko
+    - .kube_prometheus_stack_rules
 
-nextcloud-behave:
+nextcloud-taiko:
   variables:
     APP: "nextcloud"
   needs:
     - job: nextcloud-cert
     - job: setup-openappstack
   extends:
-    - .behave
+    - .taiko
     - .nextcloud_rules
 
-rocketchat-behave:
+rocketchat-taiko:
   variables:
     APP: "rocketchat"
   needs:
     - job: rocketchat-cert
     - job: setup-openappstack
   extends:
-    - .behave
+    - .taiko
     - .rocketchat_rules
 
-
-wordpress-behave:
+wordpress-taiko:
   variables:
     APP: "wordpress"
   needs:
     - job: wordpress-cert
     - job: setup-openappstack
   extends:
-    - .behave
+    - .taiko
     - .wordpress_rules
 
 
diff --git a/.gitlab/ci_templates/kaniko.yml b/.gitlab/ci_templates/kaniko.yml
index 93dfd3654795e98931f1a516db2babfa15291ee6..fd5b379183ee25490ad42a28142da615376f7c8a 100644
--- a/.gitlab/ci_templates/kaniko.yml
+++ b/.gitlab/ci_templates/kaniko.yml
@@ -17,7 +17,7 @@
   image:
     # We need a shell to provide the registry credentials, so we need to use the
     # kaniko debug image (https://github.com/GoogleContainerTools/kaniko#debug-image)
-    name: gcr.io/kaniko-project/executor:debug
+    name: gcr.io/kaniko-project/executor:v1.6.0-debug
     entrypoint: [""]
   script:
     - mkdir -p /kaniko/.docker/
diff --git a/.gitlab/issue_templates/new_app.md b/.gitlab/issue_templates/new_app.md
index 3bdfdbfcd352b1646bbdf83bf361939de2292886..f9bfe0bd6739ca9ea1f2d3e0221b59af0b67241f 100644
--- a/.gitlab/issue_templates/new_app.md
+++ b/.gitlab/issue_templates/new_app.md
@@ -17,7 +17,7 @@
 
 ## Tests
 
-* [ ] Add behave feature (`tests/behave/feature`)
+* [ ] Add taiko test (`tests/taiko`)
 * [ ] Check for successful helmrelease (`test/pytest/test_helmreleases.py`)
 * [ ] Test cert (`test/pytest/test_certs.py`)
 
diff --git a/Dockerfile b/Dockerfile
index 56a5130d85b3211677c47d3c29b5c5e3ef4f9708..4d8c86aac25af10ed3d256a552c895283d4490b1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,9 +1,12 @@
 FROM alpine:3.13.5
 
 LABEL name="OpenAppStack management"
-LABEL version="4.3"
+LABEL version="4.3.1"
 LABEL vendor1="Greenhost"
 
+ENV TAIKO_SKIP_CHROMIUM_DOWNLOAD=true
+ENV TAIKO_BROWSER_PATH=/usr/bin/chromium-browser
+ENV TAIKO_BROWSER_ARGS=--no-sandbox,--start-maximized,--disable-dev-shm-usage,--ignore-certificate-errors
 
 # Download yq v4 from github until it's packaged in alpine > 3.13
 ADD https://github.com/mikefarah/yq/releases/download/v4.7.0/yq_linux_amd64 /usr/local/bin/yq
@@ -17,7 +20,6 @@ RUN \
     bash=5.1.0-r0 \
     cargo=~1.47.0-r2 \
     chromium=~86.0.4240.111-r0 \
-    chromium-chromedriver=~86.0.4240.111-r0 \
     curl=~7.77.0-r1 \
     # needed for installing pycurl python module
     curl-dev=~7.77.0-r1 \
@@ -26,14 +28,18 @@ RUN \
     libffi-dev=3.3-r2 \
     make=~4.3-r0 \
     musl-dev=~1.2.2-r1 \
+    npm=~14.16.1-r1 \
     openssh-client=~8.4_p1-r3 \
     py3-pip=~20.3.4-r0 \
     py3-wheel=~0.36.2-r0 \
     python3-dev=~3.8.10-r0 \
     rsync=~3.2.3-r1 \
     rust=~1.47.0-r2 && \
+  rm -rf /var/cache/* && \
+  mkdir /var/cache/apk && \
   chmod a+x /usr/local/bin/* && \
   update-ca-certificates && \
   pip install --no-cache-dir -r /requirements.txt && \
   ln -s /usr/bin/python3 /usr/bin/python && \
-  tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin
+  tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin && \
+  npm install -g taiko@1.2.5
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index bee86ba4153a6c0ae3303e4a30fa8d8bb0fb8a45..a71096c5169d6406db19bd182c5903534a65c731 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -17,7 +17,7 @@ To get an overall status of your cluster you can run the tests from the
 command line.
 
 There are two types of tests: [testinfra](https://testinfra.readthedocs.io/en/latest/)
-tests, and [behave](https://behave.readthedocs.io/en/latest/) tests.
+tests, and [taiko](https://taiko.dev) tests.
 
 ### Testinfra tests
 
@@ -64,37 +64,32 @@ Test a specific application:
   not verified. Therefore we need to force plain ssh:// with either
   `connection=ssh` or `--hosts=ssh://…`
 
-### Behave tests
+### Taiko tests
 
-Behave tests run in a browser and test if all the interfaces are up
+Taiko tests run in a browser and test if all the interfaces are up
 and running and correctly connected to each other. They are integrated in the
 `openappstack` CLI command suite.
 
 #### Prerequisites
 
-By default the behave tests use the Chromium browser and Chromium webdriver. If
-you want/need to use the Firefox webdriver please refer to the manual behave
-test instructions below, under [Advanced Usage](#advanced-usage).
+Install [taiko](https://taiko.dev):
 
-Install [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/),
-i.e. for Debian/Ubuntu use:
-
-    apt install chromium-chromedriver
+    npm install -g taiko
 
 #### Usage
 
-To run all behave tests, run the following command in this repository:
+To run all taiko tests, run the following command in this repository:
 
     python -m openappstack CLUSTERNAME test
 
-In the future, this command will run all tests, but now only *behave* is
+In the future, this command will run all tests, but now only *taiko* is
 implemented. To learn more about the `test` subcommand, run:
 
     python -m openappstack CLUSTERNAME test --help
 
-You can also only run a behave test for a specific application, i.e.:
+You can also only run a taiko test for a specific application, i.e.:
 
-    python -m openappstack CLUSTERNAME test --behave-tags nextcloud
+    python -m openappstack CLUSTERNAME test --taiko-tags nextcloud
 
 ### Advanced usage
 
@@ -133,42 +128,22 @@ then:
 
     gitlab-runner exec docker --env CI_REGISTRY_IMAGE="$CI_REGISTRY_IMAGE" --env SSH_PRIVATE_KEY="$SSH_PRIVATE_KEY" --env COSMOS_API_TOKEN="$COSMOS_API_TOKEN" bootstrap
 
-#### Behave tests
-
-##### Using Firefox instead of Chromium
-
-If you want to use Firefox instead of Chromium, you need to install the gecko
-driver
-
-    apt install firefox-geckodriver
-
-Now you only need to add `-D browser=firefox` to the behave command line
-options, so run:
+#### Taiko tests
 
-    python -m openappstack CLUSTER_NAME test --behave-param='-D browser=firefox'
+##### Using Taiko without the OpenAppStack CLI
 
-##### Using behave without the OpenAppStack CLI
-
-Go to the `test/behave` directory and run:
+Go to the `test/taiko` directory and run:
 
 For nextcloud & onlyoffice tests:
 
-    behave -D nextcloud.url=https://files.example.openappstack.net \
-           -D nextcloud.password="$(cat ../../clusters/YOUR_CLUSTERNAME/secrets/nextcloud_admin_password)" \
-           -t nextcloud
-
-You can replace `nextcloud` with `grafana` or `rocketchat` to test the other
-applications.
-
-#### Run behave tests in openappstack-ci docker image
-
-    docker run --rm -it open.greenhost.net:4567/openappstack/openappstack/openappstack-ci sh
+    export DOMAIN='oas.example.net'
+    export SSO_USERNAME='user1'
+    export SSO_USER_PW='...'
+    export TAIKO_TESTS='nextcloud'
+    taiko --observe taiko-tests.js
 
-      apk --no-cache add git
-      git clone https://open.greenhost.net/openappstack/openappstack.git
-      cd openappstack/test/behave
-      behave -D nextcloud.url=https://files.ci-20410.ci.openappstack.net \
-       -D nextcloud.admin.password=…
+You can replace `nextcloud` with `grafana` or `wordpress` to test the other
+applications, or with `all` to test all applications.
 
 ## SSH access
 
diff --git a/flux2/apps/monitoring/loki-release.yaml b/flux2/apps/monitoring/loki-release.yaml
index df4409e1097c828992d3718fd05247cae9907b7c..07769a71ece2b4d99a8fe04a87bbf4ab732c2460 100644
--- a/flux2/apps/monitoring/loki-release.yaml
+++ b/flux2/apps/monitoring/loki-release.yaml
@@ -8,6 +8,7 @@ spec:
   releaseName: loki
   chart:
     spec:
+      # https://artifacthub.io/packages/helm/grafana/loki
       chart: loki
       version: 2.5.0
       sourceRef:
@@ -16,6 +17,7 @@ spec:
         namespace: flux-system
   interval: 40m
   values:
+    # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml
     resources:
       limits:
         cpu: 800m
@@ -30,7 +32,14 @@ spec:
       size: 10Gi
       annotations: {}
       # existingClaim:
+    # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml#L46
     config:
+      # https://github.com/grafana/helm-charts/blob/main/charts/loki/values.yaml#L48
+      ingester:
+        chunk_idle_period: 30m
+        chunk_block_size: 1048576
+        chunk_retain_period: 15m
+        max_transfer_retries: 10
       # https://github.com/grafana/loki/blob/main/cmd/loki/loki-local-config.yaml
       # https://grafana.com/docs/loki/latest/operations/storage/retention
       schema_config:
diff --git a/install/install-openappstack.sh b/install/install-openappstack.sh
index 0b0efe56a6deb9dcff08900589bcef5854597331..2071c12371223920db04614dc7b2deec91124d89 100755
--- a/install/install-openappstack.sh
+++ b/install/install-openappstack.sh
@@ -7,9 +7,15 @@ flux install \
   --watch-all-namespaces=true \
   --namespace=flux-system
 
+# get current git branch name
+branch=$CI_COMMIT_REF_NAME
+[ -z "$branch" ] && branch=$(git rev-parse --abbrev-ref HEAD)
+
+echo "Tracking branch $branch for https://open.greenhost.net/openappstack/openappstack flux repo"
+
 flux create source git openappstack \
   --url=https://open.greenhost.net/openappstack/openappstack \
-  --branch=master \
+  --branch=$branch \
   --interval=1m
 
 flux create kustomization openappstack \
diff --git a/openappstack/__main__.py b/openappstack/__main__.py
index 98ecdbd38261dc989c4f82b77301d7e418bbf7c1..56ac73998ff94ebd4ba7d82bc4b56370f06dc933 100755
--- a/openappstack/__main__.py
+++ b/openappstack/__main__.py
@@ -23,12 +23,10 @@ import logging
 from math import floor
 import os
 import sys
+from subprocess import Popen, PIPE
 import greenhost_cloud
-from behave.__main__ import main as behave_main
 from openappstack import name, cluster, ansible
 
-ALL_TESTS = ['behave']
-
 # We're limiting to 50, because we use subdomains, the current longest of which
 # is 7 characters (`office.`). Max CN length is 63 characters, so we have some
 # wiggle room for longer subdomains in the future.
@@ -210,41 +208,18 @@ def main():  # pylint: disable=too-many-statements,too-many-branches,too-many-lo
     test_parser.set_defaults(func=test)
 
     test_parser.add_argument(
-        '--run-test',
-        action='append',
-        help=('Run only a specific kind of test. If not provided, all tests '
-              'are run. The currently available tests are: ') \
-            + ','.join(ALL_TESTS))
-
-    test_parser.add_argument(
-        '--behave-rerun-failing',
-        action='store_true',
-        help=('Run behave with @rerun_failing.features'))
-    test_parser.add_argument(
-        '--behave-tags',
-        nargs='+',
-        default=[],
-        help=('Only run behave tests with these tags'))
-    test_parser.add_argument(
-        '--behave-headless',
-        action='store_true',
-        help=('Run behave in headless mode'))
-    test_parser.add_argument(
-        '--behave-ignore-certificate-errors',
+        '--observe',
         action='store_true',
-        help=('Ignore certificate errors, needed if you use i.e. the Letsencrypt ACME staging CA'))
+        help=('Enables headful mode and runs script with 3000ms delay.'))
     test_parser.add_argument(
-        '--behave-param',
-        metavar=['PARAM[=VALUE]'],
-        action='append',
-        nargs=1,
-        default=[],
-        help=('Pass additional behave options (like "-D browser=firefox"'))
+        '--apps',
+        default='all',
+        help=('Which apps to test (i.e. "nextcloud,wordpress". Defaults to'
+              '"all".'))
 
     info_parser = subparsers.add_parser(
         'info',
         help=("Show information about a cluster"))
-
     info_parser.set_defaults(func=info)
     info_parser.add_argument(
         '--ip-address',
@@ -252,6 +227,11 @@ def main():  # pylint: disable=too-many-statements,too-many-branches,too-many-lo
         help=('Only output the IP address of the machine')
     )
 
+    secrets_parser = subparsers.add_parser(
+        'secrets',
+        help=("Show OAS cluster secrets"))
+    secrets_parser.set_defaults(func=secrets)
+
     args = parser.parse_args()
     loglevel = logging.DEBUG if args.verbose else logging.INFO
     init_logging(log, loglevel)
@@ -288,6 +268,16 @@ def info(clus, args):
     clus.print_info(args)
 
 
+def secrets(clus, args):
+    """
+    Dumps cluster secrets and then exits
+
+    :param cluster.Cluster clus: cluster to show information about
+    """
+    clus.load_data()
+    clus.dump_secrets(args)
+
+
 def create(clus, args):  # pylint: disable=too-many-branches
     """
     Parses arguments for the 'create' subcommand
@@ -398,52 +388,86 @@ def install(clus, args):
 
 def test(clus, args):
     """
-    Runs behave or testinfra test. Overwrites behave_path/behave.ini!
+    Runs taiko tests.
 
     :param cluster.Cluster clus: Cluster object to run tests on
     :param argparse.Namespace args: Command line arguments
     """
 
-    # At the moment we only have one type if test, but more tests will be added
-    # to this in the future. If args.run_test is empty, we run all the tests
-    run_test = args.run_test
-    if not run_test:
-        run_test = ALL_TESTS
-
-    if 'behave' in run_test:
-        behave_path = os.path.join(os.path.dirname(__file__), '..', 'test',
-                                   'behave')
-        # Run from the behave directory so behave automatically loads all the
-        # necessary files
-        os.chdir(behave_path)
-        clus.load_data()
-        behave_ini = os.path.join(behave_path, 'behave.ini')
-        clus.write_behave_config(behave_ini)
-        command = []
-        if args.behave_rerun_failing:
-            command.append('@rerun_failing.features')
-        if args.behave_headless:
-            command.append('-D headless=True')
-        if args.behave_ignore_certificate_errors:
-            command.append('-D ignore_certificate_errors=True')
-        for tag in args.behave_tags:
-            log.info(command)
-            command.append('-t {tag}'.format(tag=tag))
-        if args.behave_param:
-            for param in args.behave_param:
-                if len(param) > 1:
-                    log.warning('More than 1 parameter. Ignoring the rest! '
-                                'Use --behave-param several times to supply '
-                                'more than 1 parameter')
-                param = param[0]
-                command.append(param)
-        log.info('Running behave command %s', command)
-        return_code = behave_main(command)
-
-        # Remove behave.ini so we don't leave secrets hanging around.
-        os.remove(behave_ini)
-
-        sys.exit(return_code)
+    taiko_path = os.path.join(os.path.dirname(__file__), '..', 'test', 'taiko')
+    # Run from the taiko directory so taiko automatically loads all the
+    # necessary files
+    os.chdir(taiko_path)
+    clus.load_data()
+    command = ['taiko']
+    if args.observe:
+        command.append('--observe')
+    command.append('apps.js')
+
+    # Set env vars
+
+    # SSO tests currently only work with valid letsencrypt production certs.
+    # Therefor we disable SSO tests for now, until
+    # https://open.greenhost.net/openappstack/single-sign-on/-/issues/62
+    # is fixed.
+    #
+    #  sso_username = clus.get_password_from_kubernetes(
+    #      'oas-single-sign-on-variables',
+    #      'userbackend_admin_username',
+    #      'flux-system'
+    #  )
+    #  sso_password = clus.get_password_from_kubernetes(
+    #      'oas-single-sign-on-variables',
+    #      'userbackend_admin_password',
+    #      'flux-system'
+    #  )
+    #  os.environ["SSO_USERNAME"] = sso_username
+    #  os.environ["SSO_PASSWORD"] = sso_password
+
+    if "nextcloud" in args.apps or args.apps=='all':
+        nextcloud_password = clus.get_password_from_kubernetes(
+            'oas-nextcloud-variables',
+            'nextcloud_password',
+            'flux-system'
+        )
+        os.environ["NEXTCLOUD_PASSWORD"] = nextcloud_password
+
+    if "rocketchat" in args.apps or args.apps=='all':
+        rocketchat_password = clus.get_password_from_kubernetes(
+            'oas-rocketchat-variables',
+            'rocketchat_admin_password',
+            'flux-system'
+        )
+        os.environ["ROCKETCHAT_PASSWORD"] = rocketchat_password
+
+    if "wordpress" in args.apps or args.apps=='all':
+        wordpress_password = clus.get_password_from_kubernetes(
+            'oas-wordpress-variables',
+            'wordpress_admin_password',
+            'flux-system'
+        )
+        os.environ["WORDPRESS_PASSWORD"] = wordpress_password
+
+    if "grafana" in args.apps or args.apps=='all':
+        grafana_password = clus.get_password_from_kubernetes(
+            'oas-kube-prometheus-stack-variables',
+            'grafana_admin_password',
+            'flux-system'
+        )
+        os.environ["GRAFANA_PASSWORD"] = grafana_password
+
+    os.environ["TAIKO_TESTS"] = args.apps
+    os.environ['DOMAIN'] = clus.domain
+
+    log.info('Running taiko command %s', command)
+
+    with Popen(command, stdout=PIPE, bufsize=1,
+               universal_newlines=True) as taiko:
+        for line in taiko.stdout:
+            print(line, end='')
+
+    return_code = (taiko.returncode)
+    sys.exit(return_code)
 
 def create_domain_records(domain, droplet_ip, subdomain=None):
     """
diff --git a/openappstack/cluster.py b/openappstack/cluster.py
index 3a15b99debf3abfc422fc65bb69c8163702c3ea1..71425bea89ee2aa4205891247a2265e316562193 100644
--- a/openappstack/cluster.py
+++ b/openappstack/cluster.py
@@ -257,76 +257,46 @@ KUBECONFIG={secret_dir}/kube_config_cluster.yml
         """Path where all the passwords for cluster admins are saved"""
         return os.path.join(self.cluster_dir, 'secrets')
 
-    def write_behave_config(self, config_path):
+    def dump_secrets(self, args):
         """
-        Write behave config file for the cluster.
-
-        :param str config_path: Configuration is written to config_path (e.g.
-            /home/you/openappstack/test/behave.ini). If config_path already
-            exists, the program is aborted.
+        Shows all OAS cluster secrets.
         """
-        if os.path.isfile(config_path):
-            log.error('%s file already exists, not overwriting '
-                      'file! Remove the file if you want to run behave. '
-                      'Program will exit now', config_path)
-            sys.exit(2)
-
-        grafana_admin_password = self.get_password_from_kubernetes(
-            'kube-prometheus-stack-grafana',
-            'admin-password',
-            'oas')
-
-        rocketchat_admin_password = self.get_password_from_kubernetes(
-            'oas-rocketchat-variables',
-            'rocketchat_admin_password',
-            'flux-system')
-
-        nextcloud_admin_password = self.get_password_from_kubernetes(
-            'nc-nextcloud',
-            'nextcloud-password',
-            'oas-apps')
-
-        wordpress_admin_password = self.get_password_from_kubernetes(
-            'oas-wordpress-variables',
-            'wordpress_admin_password',
-            'flux-system')
-
-        behave_config = configparser.ConfigParser()
-        behave_config['behave'] = {}
-        behave_config['behave']['format'] = 'rerun'
-        behave_config['behave']['outfiles'] = 'rerun_failing.features'
-        behave_config['behave']['show_skipped'] = 'false'
-
-        behave_config['behave.userdata'] = {}
-
-        behave_config['behave.userdata']['nextcloud.url'] = \
-            'https://files.{}'.format(self.domain)
-        behave_config['behave.userdata']['nextcloud.username'] = 'admin'
-        behave_config['behave.userdata']['nextcloud.password'] = \
-            nextcloud_admin_password
-        behave_config['behave.userdata']['onlyoffice.url'] = \
-            'https://office.{}/welcome'.format(self.domain)
-
-        behave_config['behave.userdata']['rocketchat.url'] = \
-            'https://chat.{}'.format(self.domain)
-        behave_config['behave.userdata']['rocketchat.username'] = 'admin'
-        behave_config['behave.userdata']['rocketchat.password'] = \
-            rocketchat_admin_password
-
-        behave_config['behave.userdata']['wordpress.url'] = \
-            'https://www.{}/wp-login.php'.format(self.domain)
-        behave_config['behave.userdata']['wordpress.username'] = 'admin'
-        behave_config['behave.userdata']['wordpress.password'] = \
-            wordpress_admin_password
-
-        behave_config['behave.userdata']['grafana.url'] = \
-            'https://grafana.{}'.format(self.domain)
-        behave_config['behave.userdata']['grafana.username'] = 'admin'
-        behave_config['behave.userdata']['grafana.password'] = \
-            grafana_admin_password
-
-        with open(config_path, 'w') as config_file:
-            behave_config.write(config_file)
+
+        all_secrets = {
+            'flux-system': {
+                'oas-kube-prometheus-stack-variables': ['grafana_admin_password'],
+                'oas-nextcloud-variables': [
+                    'nextcloud_mariadb_password',
+                    'nextcloud_mariadb_root_password',
+                    'nextcloud_password',
+                    'onlyoffice_jwt_secret',
+                    'onlyoffice_postgresql_password',
+                    'onlyoffice_rabbitmq_password'],
+                'oas-rocketchat-variables': [
+                    'rocketchat_admin_password',
+                    'mongodb_root_password',
+                    'mongodb_password' ],
+                'oas-single-sign-on-variables': [
+                    'userbackend_admin_username',
+                    'userbackend_admin_password',
+                    'userbackend_postgres_password',
+                    'hydra_system_secret' ],
+                'oas-wordpress-variables': [
+                    'wordpress_admin_password',
+                    'wordpress_mariadb_password',
+                    'wordpress_mariadb_root_password' ]
+            }
+
+        }
+
+        for sec in all_secrets.values():
+            for app, app_secrets in sec.items():
+                for app_secret in app_secrets:
+                    secret = self.get_password_from_kubernetes(
+                        app,
+                        app_secret,
+                        namespace)
+                    print(app_secret + '=' + secret )
 
     def get_password_from_kubernetes(self, secret, key, namespace):
         """
diff --git a/requirements.in b/requirements.in
index ce21c81430aa32ef88eecb355f0ec37f9fccf6e7..387e00d4dced1d5074279f822024445047f19719 100644
--- a/requirements.in
+++ b/requirements.in
@@ -22,5 +22,4 @@ tld>=0.12.5
 setuptools>=40.6.2
 wheel>=0.33.1
 -e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud
--e git+https://open.greenhost.net/openappstack/oas_behave@c05009a#egg=oas_behave
 passlib>=1.7.2
diff --git a/requirements.txt b/requirements.txt
index d3bdb09e1901a6c433812a481a8ddce1d9a890e7..37719e0d57399159432edebb2e9f33c96a26c9c1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,18 +6,12 @@
 #
 -e git+https://open.greenhost.net/greenhost/cloud-api#egg=greenhost_cloud
     # via -r requirements.in
--e git+https://open.greenhost.net/openappstack/oas_behave@c05009a#egg=oas_behave
-    # via -r requirements.in
 ansible==2.9.18
     # via -r requirements.in
 attrs==20.3.0
     # via pytest
 bcrypt==3.2.0
     # via paramiko
-behave-webdriver==0.3.0
-    # via oas-behave
-behave==1.2.6
-    # via behave-webdriver
 cachetools==4.2.1
     # via google-auth
 certifi==2020.12.5
@@ -60,12 +54,6 @@ packaging==20.9
     # via pytest
 paramiko==2.7.2
     # via -r requirements.in
-parse-type==0.5.2
-    # via behave
-parse==1.19.0
-    # via
-    #   behave
-    #   parse-type
 passlib==1.7.4
     # via -r requirements.in
 pluggy==0.13.1
@@ -119,16 +107,12 @@ ruamel.yaml.clib==0.2.2
     # via ruamel.yaml
 ruamel.yaml==0.16.13
     # via openshift
-selenium==3.141.0
-    # via behave-webdriver
 six==1.15.0
     # via
     #   bcrypt
-    #   behave
     #   google-auth
     #   kubernetes
     #   openshift
-    #   parse-type
     #   pynacl
     #   pyopenssl
     #   python-dateutil
@@ -145,7 +129,6 @@ urllib3==1.26.4
     # via
     #   kubernetes
     #   requests
-    #   selenium
 websocket-client==0.58.0
     # via kubernetes
 wheel==0.36.2
diff --git a/test/behave/features/environment.py b/test/behave/features/environment.py
deleted file mode 100644
index 2cdc9f865c58d2f652a072702a4ce6547ff77ab2..0000000000000000000000000000000000000000
--- a/test/behave/features/environment.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from oas_behave.environment import *
-
-
-def before_tag(context, tag):
-    """Define steps run before each tag."""
-
-    userdata = context.config.userdata
-    if tag == 'kube-prometheus-stack':
-        context.grafana = get_values(userdata, 'grafana')
-
-    if tag == 'nextcloud':
-        context.nextcloud = get_values(userdata, 'nextcloud')
-
-    if tag == 'rocketchat':
-        context.rocketchat = get_values(userdata, 'rocketchat')
-
-    if tag == 'wordpress':
-        context.wordpress = get_values(userdata, 'wordpress')
diff --git a/test/behave/features/kube-prometheus-stack.feature b/test/behave/features/kube-prometheus-stack.feature
deleted file mode 100644
index 52cca2246f35031a5dc4437032b6aed5e5e37cdd..0000000000000000000000000000000000000000
--- a/test/behave/features/kube-prometheus-stack.feature
+++ /dev/null
@@ -1,26 +0,0 @@
-@kube-prometheus-stack
-Feature: Test grafana admin login
-  As an OAS admin
-  I want to be able to login to grafana as the user admin
-  And I want to be able to look at the logs
-
-Scenario: Open grafana
-  Given I have closed all but the first window
-  And I open the grafana URL
-  Then I wait on element "//input[@name='user']" for 25000ms to be visible
-  And I expect that the title is "Grafana"
-  And I expect that element "//input[@name='password']" is visible
-  And I expect that the path is "/login"
-
-Scenario: Login to grafana
-  Given the element "//input[@name='user']" is visible
-  When I enter the "grafana" "username" in the inputfield "//input[@name='user']"
-  And I enter the "grafana" "password" in the inputfield "//input[@name='password']"
-  And I click on the button "//button[@aria-label='Login button']"
-  Then I wait on element "sidemenu.sidemenu" for 60000ms to be visible
-  And I expect that the path is "/"
-
-Scenario: As an admin I want to look at the helm-controller logs
-  When I open the grafana explore helm-controller URL
-  Then I wait on element ".graph-panel" for 25000ms to be visible
-  And I expect that element ".datapoints-warning" does not exist
diff --git a/test/behave/features/nextcloud.feature b/test/behave/features/nextcloud.feature
deleted file mode 100644
index eeb638db1624a598ef9dcb46e83ea9f4267d79e2..0000000000000000000000000000000000000000
--- a/test/behave/features/nextcloud.feature
+++ /dev/null
@@ -1,54 +0,0 @@
-@nextcloud
-Feature: Test nextcloud admin login
-  As an OAS admin
-  I want to be able to login to nextcloud as the user admin
-  And I want to be able to open a document in OnlyOffice
-
-Scenario: Test OnlyOffice welcome screen
-  Given I have closed all but the first window
-  And I open the onlyoffice URL
-  Then I wait on element "#status-ok-icon" for 1000ms to exist
-
-Scenario: Open nextcloud
-  When I open the nextcloud URL
-  Then I wait on element "input#user" for 25000ms to be visible
-  And  I expect that element "input#user" is visible
-
-Scenario: Login to nextcloud
-  Given the element "input#user" is visible
-  When I enter the "nextcloud" "username" in the inputfield "input#user"
-  And I enter the "nextcloud" "password" in the inputfield "input#password"
-  And I click on the button "input#submit-form"
-  Then I expect that element ".logo" does exist
-  And I expect that cookie "nc_session_id" exists
-  And I expect that cookie "nc_token" exists
-  And I expect that cookie "nc_username" exists
-
-Scenario: Close welcome to nextcloud modal
-  When I close the nextcloud welcome wizard if it exists
-  Then I wait on element "#firstrunwizard" for 1000ms to not exist
-  And I wait on element "[aria-label='Files']" for 5000ms to be visible
-
-Scenario: Create a new document in OnlyOffice
-  When I click on the element "[aria-label='Files']"
-  # Unfortunaty there's no check if the element that's already visible
-  # is also clickable.
-  And I wait on element "[class='button new']" to be clickable
-  And I click on the button "[class='button new']"
-  And I click on the element "[data-action='onlyofficeDocx']"
-  And I add a random string to the inputfield ".filenameform input[type=text]"
-  And I click on the element "input.icon-confirm"
-  And I focus the last opened tab
-  Then I expect a new tab has been opened
-  And I expect that element "div.toast-error" does not exist
-
-Scenario: Assert the bold button is not activated
-  When I wait for the iframe named "frameEditor" and switch to it
-  Then I wait on element "div.asc-loadmask" for 20000ms to be visible
-  And I wait on element "div.asc-loadmask" for 20000ms to not exist
-  And I wait on element "[id='id-toolbar-btn-bold']" for 20000ms to be visible
-  And I expect that element "[id='id-toolbar-btn-bold']" does not have the class "active"
-
-Scenario: Active the bold button
-  When I click on the element "[id='id-toolbar-btn-bold']"
-  Then I expect that element "[id='id-toolbar-btn-bold']" has the class "active"
diff --git a/test/behave/features/rocketchat.feature b/test/behave/features/rocketchat.feature
deleted file mode 100644
index 0351712775c052b814567dbd180a01b364e0d19b..0000000000000000000000000000000000000000
--- a/test/behave/features/rocketchat.feature
+++ /dev/null
@@ -1,22 +0,0 @@
-@rocketchat
-Feature: Test rocketchat admin login
-  As an OAS admin
-  I want to be able to login to rocketchat as the user admin
-
-Scenario: Open rocketchat
-  Given I have closed all but the first window
-  And I open the rocketchat URL
-  Then I wait on element "//input[@name='emailOrUsername']" for 25000ms to be visible
-  And I expect that element "#pass" is visible
-
-Scenario: See SSO button
-  When I open the rocketchat URL
-  Then I wait on element "//input[@name='emailOrUsername']" for 25000ms to be visible
-  Then I wait on element "//button[@title='Openappstack']" for 5000ms to be visible
-
-Scenario: Login to rocketchat
-  Given the element "//input[@name='emailOrUsername']" is visible
-  When I enter the "rocketchat" "username" in the inputfield "//input[@name='emailOrUsername']"
-  And I enter the "rocketchat" "password" in the inputfield "#pass"
-  And I click on the button ".login"
-  Then I wait on element ".rooms-list" for 25000ms to be visible
diff --git a/test/behave/features/steps/steps.py b/test/behave/features/steps/steps.py
deleted file mode 100644
index b4d93bb6c4bae413093952f24d22a8565382248d..0000000000000000000000000000000000000000
--- a/test/behave/features/steps/steps.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from oas_behave.common_steps import *
-from selenium.webdriver.common.by import By
-from selenium.webdriver.support import expected_conditions as EC
-from selenium.webdriver.support.ui import WebDriverWait
-
-@when(u'I open the rocketchat URL')
-@given(u'I open the rocketchat URL')
-def step_impl(context):
-    """Open rocketchat URL."""
-    context.behave_driver.get(context.rocketchat['url'])
-
-@when(u'I open the wordpress URL')
-@given(u'I open the wordpress URL')
-def step_impl(context):
-    """Open wordpress URL."""
-    context.behave_driver.get(context.wordpress['url'])
-
-@when(u'I open the grafana explore helm-controller URL')
-@given(u'I open the grafana explore helm-controller URL')
-def step_impl(context):
-    """Open wordpress URL."""
-    helm_controller_url = str(context.grafana["url"]) + '/explore?orgId=1&left=["now-1h","now","Loki",{"expr":"{app=\\\"helm-controller\\\"}"}]'
-    print(helm_controller_url)
-    context.behave_driver.get(helm_controller_url)
-
-@when(u'I wait on element "{element}" to be clickable')
-@given(u'I wait on element "{element}" to be clickable')
-def step_impl(context, element):
-    """Wait for element ro be clickable."""
-    wait = WebDriverWait(context.behave_driver, 30)
-    wait.until(EC.element_to_be_clickable((By.CLASS_NAME, "button.new")))
-
-@when(u'I wait for the iframe named "{name}" and switch to it')
-@given(u'I wait for the iframe named "{name}" and switch to it')
-def step_impl(context, name):
-    """Wait for the iframe with given name and switch to it."""
-    wait = WebDriverWait(context.behave_driver, 30)
-    wait.until(EC.frame_to_be_available_and_switch_to_it((By.NAME, name)))
diff --git a/test/behave/features/wordpress.feature b/test/behave/features/wordpress.feature
deleted file mode 100644
index 076173a750b3c0df177e435a42c0d8de7ea69c5e..0000000000000000000000000000000000000000
--- a/test/behave/features/wordpress.feature
+++ /dev/null
@@ -1,17 +0,0 @@
-@wordpress
-Feature: Test WordPress admin login
-  As an OAS admin
-  I want to be able to login to WordPress as the user admin
-
-Scenario: Open WordPress
-  Given I have closed all but the first window
-  And I open the wordpress URL
-  Then I wait on element "#user_login" for 25000ms to be visible
-  And I expect that element "#user_pass" is visible
-
-Scenario: Login to WordPress
-  Given the element "#user_login" is visible
-  When I enter the "wordpress" "username" in the inputfield "#user_login"
-  And I enter the "wordpress" "password" in the inputfield "#user_pass"
-  And I click on the button "#wp-submit"
-  Then I wait on element "#wpwrap" for 25000ms to be visible
diff --git a/test/taiko/.eslintrc.yml b/test/taiko/.eslintrc.yml
new file mode 100644
index 0000000000000000000000000000000000000000..16c7b9d511b0afb039336b99d76606ea21d657cc
--- /dev/null
+++ b/test/taiko/.eslintrc.yml
@@ -0,0 +1,9 @@
+env:
+  browser: true
+  es2021: true
+extends:
+  - standard
+parserOptions:
+  ecmaVersion: 12
+  sourceType: module
+rules: {}
diff --git a/test/taiko/apps.js b/test/taiko/apps.js
new file mode 100644
index 0000000000000000000000000000000000000000..c4984fa18b105933563b58f6cbf073a440824b2e
--- /dev/null
+++ b/test/taiko/apps.js
@@ -0,0 +1,132 @@
+// Tests if logging into all apps works using the admin user without SSO
+// For SSO login tests see ./single-sign-on
+const { openBrowser, goto, textBox, into, write, click, toRightOf, below, link, press, image, waitFor, closeBrowser, screenshot } = require('taiko');
+const assert = require('assert');
+
+(async () => {
+  try {
+    const taikoTests = process.env.TAIKO_TESTS || 'all'
+    const domain = process.env.DOMAIN
+    const globalTimeout = 60000
+
+    // https://docs.taiko.dev/api/setconfig/
+    // setConfig( { observeTime: 1000});
+    setConfig( { observeTime: 0, navigationTimeout: globalTimeout });
+
+    console.log('Executing these tests: ' + taikoTests)
+    await openBrowser()
+
+    // Nextcloud and Onlyoffice
+    if (taikoTests.includes('nextcloud') || taikoTests === 'all') {
+      const nextcloudUrl = 'https://files.' + domain
+      const onlyofficeUrl = 'https://office.' + domain
+      const nextcloudUsername = process.env.NEXTCLOUD_USERNAME || 'admin'
+      const nextcloudPassword = process.env.NEXTCLOUD_PASSWORD
+      console.log('• Nextcloud and Onlyoffice')
+
+      await goto(onlyofficeUrl + '/welcome')
+      await waitFor('Document Server is running')
+
+      await goto(nextcloudUrl)
+      await write(nextcloudUsername, into(textBox('Username')))
+      await write(nextcloudPassword, into(textBox('Password')))
+
+      await click('Log in')
+
+      // Close potential nextcloud first run wizard modal
+      // https://github.com/nextcloud/firstrunwizard/issues/488
+      // Unfortunately, we need to sleep a while since I haven't found a
+      // good way that closes the modal *if* it pops up, since these
+      // tests should also work on subsequent logins.
+      await waitFor(5000)
+      await press('Escape')
+
+      // Open document and type some text
+      await click(link(), above('Add notes'))
+      await click('Document')
+      await press('Enter')
+
+      let italicButtonId = '#id-toolbar-btn-italic'
+      await waitFor($(italicButtonId), globalTimeout)
+
+      // Activate italic button
+      let buttonStateBefore = await evaluate($(italicButtonId), (elem) => {return elem.getAttribute('class')})
+      await assert.ok(!buttonStateBefore.includes('active'))
+      await click($(italicButtonId))
+      let buttonStateAfter = await evaluate($(italicButtonId), (elem) => {return elem.getAttribute('class')})
+      await assert.ok(buttonStateAfter.includes('active'))
+
+      await press(['H', 'i', ' ', 'f', 'r', 'o', 'm', ' ', 't', 'a', 'i', 'k', 'o', '!', 'Enter'])
+
+      // Deactivate italic finially
+      await click($(italicButtonId))
+      await closeTab()
+    }
+
+    // Rocketchat
+    if (taikoTests.includes('rocketchat') || taikoTests === 'all') {
+      const rocketchatUrl = 'https://chat.' + domain
+      const rocketchatUsername = process.env.ROCKETCHAT_USERNAME || 'admin'
+      const rocketchatPassword = process.env.ROCKETCHAT_PASSWORD
+
+      console.log('• Rocketchat')
+      await goto(rocketchatUrl)
+      await write(rocketchatUsername, into(textBox('Username')))
+      await write(rocketchatPassword, into(textBox('Password')))
+      await click('Login')
+      await waitFor('Welcome to Rocket.Chat!')
+    }
+
+    // Wordpress
+    if (taikoTests.includes('wordpress') || taikoTests === 'all') {
+      const wordpressUrl = 'https://www.' + domain
+      const wordpressUsername = process.env.WORDPRESS_USERNAME || 'admin'
+      const wordpressPassword = process.env.WORDPRESS_PASSWORD
+
+      console.log('• Wordpress')
+      await goto(wordpressUrl)
+      await click('Log in')
+      await write(wordpressUsername, into(textBox('Username')))
+      await write(wordpressPassword, into(textBox('Password')))
+      await click('Log in')
+      await assert.ok(await link('Dashboard').exists())
+    }
+
+    // Grafana
+    if (taikoTests.includes('grafana') || taikoTests === 'all') {
+      const grafanaUrl = 'https://grafana.' + domain
+      const grafanaUsername = process.env.GRAFANA_USERNAME || 'admin'
+      const grafanaPassword = process.env.GRAFANA_PASSWORD
+
+      console.log('• Grafana')
+      await goto(grafanaUrl)
+      await write(grafanaUsername, into(textBox('Username')))
+      await write(grafanaPassword, into(textBox('Password')))
+      await click('Log in')
+
+      // Node exporter dashboard
+      // Couldn't select "Manage dashboards" from the sidebar menu easily,
+      // so we just go there
+      await goto(grafanaUrl + '/dashboards')
+      await click(link('Nodes'))
+      await('CPU Usage')
+
+      // Explore Loki log messages
+      await goto(grafanaUrl + '/explore')
+      await click(image(toRightOf('Explore')))
+      await click('Loki')
+      await click('Log browser')
+      await click('app')
+      await click('cert-manager')
+      await click('grafana')
+      await click('Show logs')
+    }
+
+  } catch (error) {
+    await screenshot()
+    console.error(error)
+    process.exitCode = 1
+  } finally {
+    await closeBrowser()
+  }
+})()
diff --git a/test/taiko/single-sign-on.js b/test/taiko/single-sign-on.js
new file mode 100644
index 0000000000000000000000000000000000000000..8dc60bc76ef3227ad95521fccb8f3fd345f4d283
--- /dev/null
+++ b/test/taiko/single-sign-on.js
@@ -0,0 +1,91 @@
+// Tests if logging into all apps works using SSO
+// Unfortunately we still can't run this test in CI because we haven't found
+// a way to use SSO with LE staging certs.
+// See https://open.greenhost.net/openappstack/single-sign-on/-/issues/62
+
+const { openBrowser, goto, textBox, into, write, click, toRightOf, below, link, press, image, waitFor, closeBrowser, screenshot } = require('taiko');
+const assert = require('assert');
+
+(async () => {
+  try {
+    const taikoTests = process.env.TAIKO_TESTS || 'all'
+    const username = process.env.SSO_USERNAME
+    const pw = process.env.SSO_USER_PW
+    const domain = process.env.DOMAIN
+    const adminpanelUrl = 'https://admin.' + domain
+    const grafanaUrl = 'https://grafana.' + domain
+    const globalTimeout = 60000
+
+    // https://docs.taiko.dev/api/setconfig/
+    // setConfig( { observeTime: 1000});
+    setConfig( { observeTime: 0, navigationTimeout: globalTimeout });
+
+    console.log('Executing these tests: ' + taikoTests)
+    console.log('• Login to admin panel')
+    await openBrowser()
+
+    await goto(adminpanelUrl)
+    await click('Login')
+    await click('Login with OAS')
+    await write(username, into(textBox('Username')))
+    await write(pw, into(textBox('Password')))
+    await click('Remember me')
+    await click('Sign in')
+
+    // Nextcloud
+    if (taikoTests.includes('nextcloud') || taikoTests === 'all') {
+      console.log('• Nextcloud and Onlyoffice')
+      await click(link(below('nextcloud')))
+      await click('Log in with OpenAppStack')
+      await click('Continue with ' + username)
+
+      // Close potential nextcloud first run wizard modal
+      // https://github.com/nextcloud/firstrunwizard/issues/488
+      // Unfortunately, we need to sleep a while since I haven't found a
+      // good way that closes the modal *if* it pops up, since these
+      // tests should also work on subsequent logins.
+      await waitFor(5000)
+      await press('Escape')
+
+      await assert.ok(await text('Add notes, lists or links …').exists());
+    }
+
+    // Rocketchat
+    if (taikoTests.includes('rocketchat') || taikoTests === 'all') {
+      // Navigate to rocketchat
+      // Breaks because of 2fa challenge which is not recieved
+      // by email
+      // https://open.greenhost.net/openappstack/openappstack/-/issues/819
+      // await click(link(below('rocketchat')));
+      // await click('Login via OpenAppStack');
+      // await click('Continue with ' + username)
+    }
+
+    // Wordpress
+    if (taikoTests.includes('wordpress') || taikoTests === 'all') {
+      console.log('• Wordpress')
+      await goto(adminpanelUrl)
+      await click(link(below('wordpress')))
+      await click('Log in')
+      await click('Login with OpenID Connect')
+      await click('Continue with ' + username)
+      await assert.ok(await link('Dashboard').exists())
+    }
+
+    // Grafana
+    if (taikoTests.includes('grafana') || taikoTests === 'all') {
+      console.log('• Grafana')
+      await goto(adminpanelUrl)
+      await click(link(below('grafana')))
+      await click('Sign in with OpenAppStack')
+      await click('Continue with ' + username)
+      await assert.ok(await text('Welcome to Grafana').exists());
+    }
+  } catch (error) {
+    await screenshot()
+    console.error(error)
+    process.exitCode = 1
+  } finally {
+    await closeBrowser()
+  }
+})()