diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fb243c290a6b3bec40026dabd5fae15ebdaa86c3..99420c7fe8b3043160c4cbc2f38bbe2d2e6b4d92 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -26,6 +26,7 @@ include:
     echo "KANIKO_BUILD_IMAGENAME:    $KANIKO_BUILD_IMAGENAME"
     echo "KANIKO build image ref:    ${CI_REGISTRY_IMAGE}/${KANIKO_BUILD_IMAGENAME}:${CI_CONTAINER_TAG}"
     echo "SSH_KEY_ID:                $SSH_KEY_ID"
+    echo "SHELL                      $SHELL"
     echo
     [ -d $CLUSTER_DIR ] && find $CLUSTER_DIR || echo "directory ${CLUSTER_DIR} not found"
     echo
@@ -863,18 +864,17 @@ kube-prometheus-stack-alerts:
 
 .taiko:
   stage: integration-test
-  script:
+  before_script:
     - *debug_information
-    # Run the taiko tests for specific app
-    - python3 -m openappstack $HOSTNAME test --apps $RESOURCE
-  retry: 2
+  script:
+    # Retry taiko tests 60 times until they succeed,
+    # with a sleep interval of 10s in between tests
+    - bash ./.gitlab/ci_scripts/retry_cmd_until_success.sh 60 10 unbuffer python3 -m openappstack $HOSTNAME test --apps $RESOURCE | ts -i | ts
   artifacts:
     paths:
       - test/taiko/Screenshot*
     expire_in: 1 month
     when: on_failure
-  extends:
-    - .ssh_setup
   interruptible: true
 
 grafana-taiko:
@@ -906,7 +906,6 @@ rocketchat-taiko:
   extends:
     - .taiko
     - .rocketchat_rules
-  allow_failure: true
 
 wekan-taiko:
   variables:
diff --git a/.gitlab/ci_scripts/retry_cmd_until_success.sh b/.gitlab/ci_scripts/retry_cmd_until_success.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e165988afe56e80b585dbbc16f93ecf25a8e31f0
--- /dev/null
+++ b/.gitlab/ci_scripts/retry_cmd_until_success.sh
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+#
+# Executes given cms multiple times until it is successful
+#
+# Usage:
+#
+#   ./retry_cmd_until_success.sh [retries] [sleep] [cmd]
+#
+# Example:
+#
+#   ./retry_cmd_until_success.sh 10 20 ping ix.de
+
+retries=$1
+if [[ ! $retries =~ ^-?[0-9]+$ ]]
+then
+  echo "Please specify retries count."
+  exit 1
+fi
+
+shift
+sleep_secs=$1
+if [[ ! $sleep_secs =~ ^-?[0-9]+$ ]]
+then
+  echo "Please specify sleep interval in seconds."
+  exit 1
+fi
+
+shift
+cmd=$*
+if [[ -z "$cmd" ]]
+then
+  echo "Please specify a cmd."
+  exit 1
+fi
+echo "Retrying \"$cmd\" ${retries} times."
+
+i=0
+until eval $cmd
+do
+  echo -e "return code: $?\n"
+  if [[ $i -ge $retries ]]
+  then
+    exit 1
+  fi
+  (( i++ ))
+  sleep $sleep_secs
+  echo "${i}. retry:"
+done
diff --git a/Dockerfile b/Dockerfile
index a1ca5cbf9c2446400fbeb2ab53eb14744a28001f..7f88afb1fe134464d2ee9e7d5a27538cfde5ed98 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -21,10 +21,14 @@ RUN \
     curl=~7.78.0-r0 \
     # needed for installing pycurl python module
     curl-dev=~7.78.0-r0 \
+    # Needed for "unbuffer" to timestamp cmds
+    expect=~5.45.4-r0 \
     gcc=~10.3.1_git20210424-r2 \
     git=~2.32.0-r0 \
     libffi-dev=3.3-r2 \
     make=~4.3-r0 \
+    # Needed for timestamp cmd "ts"
+    moreutils=~0.65-r0 \
     musl-dev=~1.2.2-r3 \
     npm=~7.17.0-r0 \
     openssh-client=~8.6_p1-r2 \
@@ -39,4 +43,4 @@ RUN \
   pip install --no-cache-dir --ignore-installed six -r /requirements.txt && \
   ln -s /usr/bin/python3 /usr/bin/python && \
   tar -xzf /tmp/flux*.tar.gz && mv ./flux /usr/local/bin && \
-  npm install -g taiko@1.2.5
+  npm install -g taiko@1.2.6
diff --git a/flux2/apps/nextcloud/release.yaml b/flux2/apps/nextcloud/release.yaml
index 649ed2bbc31d2e866e9ae957ec350d5d52915c1f..97a58a3a7b864b9f567cee0a36bdc8c0089bb457 100644
--- a/flux2/apps/nextcloud/release.yaml
+++ b/flux2/apps/nextcloud/release.yaml
@@ -117,6 +117,14 @@ spec:
         rootUser:
           password: "${nextcloud_mariadb_root_password}"
 
+    apps:
+      - name: sociallogin
+        enabled: true
+      - name: onlyoffice
+        enabled: true
+      - name: calendar
+        enabled: true
+
     setupApps:
       backoffLimit: 20
 
diff --git a/flux2/infrastructure/sources/nextcloud.yaml b/flux2/infrastructure/sources/nextcloud.yaml
index 87b730f6070e77007c78a760e98421f38ac2f761..7f8fe0929a2fbbc4aa7fe4a8a81ec2005c2228bf 100644
--- a/flux2/infrastructure/sources/nextcloud.yaml
+++ b/flux2/infrastructure/sources/nextcloud.yaml
@@ -13,4 +13,4 @@ spec:
   # For all available options, see:
   # https://toolkit.fluxcd.io/components/source/api/#source.toolkit.fluxcd.io/v1beta1.GitRepositoryRef
   ref:
-    tag: 0.3.0
+    tag: 0.3.1
diff --git a/test/taiko/apps.js b/test/taiko/apps.js
index cd8cf45da71cdf9d4196b0d5f20ef783d38050d5..80069f27e3b196fe686a7134f6c53b0fe70d9cae 100644
--- a/test/taiko/apps.js
+++ b/test/taiko/apps.js
@@ -11,7 +11,12 @@ const assert = require('assert');
 
     // https://docs.taiko.dev/api/setconfig/
     // setConfig( { observeTime: 1000});
-    setConfig( { observeTime: 0, navigationTimeout: globalTimeout });
+    setConfig( {
+      observeTime: 0,
+      // Navigation timeout value in milliseconds for navigation after performing openTab, goto, reload, goBack, goForward, click, write, clear, press and evaluate.
+      navigationTimeout: globalTimeout,
+      highlightOnAction: 'true'
+    });
 
     console.log('Executing these tests: ' + taikoTests)
     await openBrowser({
@@ -50,6 +55,7 @@ const assert = require('assert');
       await write(nextcloudPassword, into(textBox('Password')))
 
       await click('Log in')
+      await waitFor(async () => (await text("Set location for weather").isVisible()), globalTimeout)
       // 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
@@ -57,27 +63,41 @@ const assert = require('assert');
       // tests should also work on subsequent logins.
       await waitFor(5000)
       await press('Escape')
-      await waitFor(async () => (await text("Set location for weather").isVisible()))
+
+      // Test if calendar app is enabled
+      await click('Calendar')
+      await waitFor(async () => (await text("Settings & Import").isVisible()), globalTimeout)
 
       console.log('• Nextcloud Onlyoffice integration')
       // Open document and type some text
       await click('Files')
-      await waitFor(async () => (await link({class:'new'}).isVisible()))
+      // Force page reload because of random empty pages for files app
+      // https://open.greenhost.net/openappstack/nextcloud/-/issues/973
+      await reload()
+
+      await waitFor(async () => (await link({class:'new'}).isVisible()), globalTimeout)
       await click(link({class:'new'}))
       await click('Document')
-      await press('Enter')
+
+      // Create random file name because NC20 will complain if file already
+      // exists
+      const file_rand = 'test-' + Math.random().toString(16).substr(2, 12)
+      await press([...file_rand, 'Enter'])
 
       let italicButtonId = '#id-toolbar-btn-italic'
-      await waitFor($(italicButtonId), 120000)
+      await waitFor(async () => (await $(italicButtonId).isVisible()), globalTimeout)
 
       // Activate italic button
       let buttonStateBefore = await evaluate($(italicButtonId), (elem) => {return elem.getAttribute('class')})
       await assert.ok(!buttonStateBefore.includes('active'))
+
+      await waitFor(async () => (await $('#id_target_cursor').isVisible()), globalTimeout)
       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'])
+      await press([...'Hi from taiko!', 'Enter'])
 
       // Deactivate italic finially
       await click($(italicButtonId))