From 08a859a5b8b01552322fc6fe1ad5bfa17f33638a Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <ben@bnf.dev>
Date: Thu, 8 Feb 2024 22:07:26 +0100
Subject: [PATCH] [TASK] Allow execution of acceptance tests with local
 chromedriver
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A local instance can sometimes be easier to debug and browser issues
can be introspected, when acceptance tests are possible to be run on
the host instead of in containers only.

Allow to run acceptance tests against a local server using a local
chromedriver. Also avoid the usage of precalculated session hashes,
which has no clear advantage, but requires codecept to perform
database connections directly: The tests now login via the backend
login form instead and store the session cookies between test runs.

As a side effect the codeception suites are cleaned up, to use
more efficient module config, instead of redundantly repeating
all WebDriver settings for headless and non-headless environment.

To execute codeception with local chromedriver against a custom
server URL run:

  chromedriver --silent & # use `killall chromedriver` when done

  typo3TestingAcceptanceBaseUrl=https://mycore.example.com/ \
  typo3TestingAcceptanceAdminPassword="MyAdminPa$$w0rd" \
  typo3TestingAcceptanceEditorPassword="MyEditorPa$$w0rd" \
  bin/codecept -c typo3/sysext/core/Tests/codeception.yml -d \
    run Application

Resolves: #103086
Releases: main, 12.4, 11.5
Change-Id: Id1210706147e0d66c4a905a53e6cc13b15bf00e8
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82866
Reviewed-by: Benjamin Franzke <ben@bnf.dev>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benjamin Franzke <ben@bnf.dev>
Tested-by: Benni Mack <benni@typo3.org>
---
 Build/Scripts/runTests.sh                     | 20 ++--
 .../Tests/Acceptance/Application.suite.yml    | 39 ++------
 .../Fixtures/BackendEnvironment.csv           |  5 -
 .../Tests/Acceptance/Helper/PasswordLogin.php | 91 +++++++++++++++++++
 .../core/Tests/Acceptance/Install.suite.yml   | 26 ------
 typo3/sysext/core/Tests/codeception.yml       | 25 +++++
 typo3/sysext/core/Tests/parameters.yml        |  5 +-
 7 files changed, 138 insertions(+), 73 deletions(-)
 create mode 100644 typo3/sysext/core/Tests/Acceptance/Helper/PasswordLogin.php

diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh
index 7ae1ee16d351..af4723c7f565 100755
--- a/Build/Scripts/runTests.sh
+++ b/Build/Scripts/runTests.sh
@@ -566,9 +566,9 @@ fi
 # Suite execution
 case ${TEST_SUITE} in
     acceptance)
-        CODECEPION_ENV=""
+        CODECEPION_ENV="--env ci"
         if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then
-            CODECEPION_ENV="--env headless"
+            CODECEPION_ENV="--env ci,headless"
         fi
         if [ "${CHUNKS}" -gt 0 ]; then
             ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name ac-splitter-${SUFFIX} ${IMAGE_PHP} php -dxdebug.mode=off Build/Scripts/splitAcceptanceTests.php -v ${CHUNKS}
@@ -656,9 +656,9 @@ case ${TEST_SUITE} in
         fi
         case ${DBMS} in
             mariadb)
-                CODECEPION_ENV="--env mysql"
+                CODECEPION_ENV="--env ci,mysql"
                 if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then
-                    CODECEPION_ENV="--env mysql,headless"
+                    CODECEPION_ENV="--env ci,mysql,headless"
                 fi
                 ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-ac-install-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null
                 waitFor mariadb-ac-install-${SUFFIX} 3306
@@ -668,9 +668,9 @@ case ${TEST_SUITE} in
                 SUITE_EXIT_CODE=$?
                 ;;
             mysql)
-                CODECEPION_ENV="--env mysql"
+                CODECEPION_ENV="--env ci,mysql"
                 if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then
-                    CODECEPION_ENV="--env mysql,headless"
+                    CODECEPION_ENV="--env ci,mysql,headless"
                 fi
                 ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-ac-install-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null
                 waitFor mysql-ac-install-${SUFFIX} 3306
@@ -680,9 +680,9 @@ case ${TEST_SUITE} in
                 SUITE_EXIT_CODE=$?
                 ;;
             postgres)
-                CODECEPION_ENV="--env postgresql"
+                CODECEPION_ENV="--env ci,postgresql"
                 if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then
-                    CODECEPION_ENV="--env postgresql,headless"
+                    CODECEPION_ENV="--env ci,postgresql,headless"
                 fi
                 ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-ac-install-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null
                 waitFor postgres-ac-install-${SUFFIX} 5432
@@ -694,9 +694,9 @@ case ${TEST_SUITE} in
             sqlite)
                 rm -rf "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/"
                 mkdir -p "${CORE_ROOT}/typo3temp/var/tests/acceptance-sqlite-dbs/"
-                CODECEPION_ENV="--env sqlite"
+                CODECEPION_ENV="--env ci,sqlite"
                 if [ "${ACCEPTANCE_HEADLESS}" -eq 1 ]; then
-                    CODECEPION_ENV="--env sqlite,headless"
+                    CODECEPION_ENV="--env ci,sqlite,headless"
                 fi
                 CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite"
                 COMMAND="bin/codecept run Install -d -c typo3/sysext/core/Tests/codeception.yml ${EXTRA_TEST_OPTIONS} ${CODECEPION_ENV} --html reports.html"
diff --git a/typo3/sysext/core/Tests/Acceptance/Application.suite.yml b/typo3/sysext/core/Tests/Acceptance/Application.suite.yml
index f884f81d21a7..4ccfcfbef30e 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application.suite.yml
+++ b/typo3/sysext/core/Tests/Acceptance/Application.suite.yml
@@ -6,41 +6,20 @@ step_decorators:
 
 modules:
   enabled:
-    - WebDriver:
-        url: '%typo3TestingAcceptanceBaseUrl%'
-        browser: chrome
-        wait: 2
-        host: chrome
-        window_size: 1280x1024
-        capabilities:
-          goog:chromeOptions:
-            args: ["--no-sandbox", "--disable-gpu", "--unsafely-treat-insecure-origin-as-secure=http://web"]
     - \TYPO3\TestingFramework\Core\Acceptance\Helper\Acceptance
-    - \TYPO3\TestingFramework\Core\Acceptance\Helper\Login:
-        sessions:
-            # These sessions must exist in the database fixture to get a logged in state.
-            editor: ff83dfd81e20b34c27d3e97771a4525a
-            admin: 886526ce72b86870739cc41991144ec1
-    - Asserts
+    - \TYPO3\CMS\Core\Tests\Acceptance\Helper\PasswordLogin
     - Codeception\Module\Cli
+  config:
+    \TYPO3\CMS\Core\Tests\Acceptance\Helper\PasswordLogin:
+      passwords:
+        admin: '%typo3TestingAcceptanceAdminPassword%'
+        editor: '%typo3TestingAcceptanceEditorPassword%'
 
 env:
-  headless:
-    modules:
+  ci:
+    extensions:
       enabled:
-        - WebDriver:
-            url: '%typo3TestingAcceptanceBaseUrl%'
-            browser: chrome
-            wait: 2
-            host: chrome
-            window_size: 1280x1024
-            capabilities:
-              goog:chromeOptions:
-                args: ["--headless", "--no-sandbox", "--disable-gpu"]
-
-extensions:
-  enabled:
-    - TYPO3\CMS\Core\Tests\Acceptance\Support\Extension\ApplicationEnvironment
+        - TYPO3\CMS\Core\Tests\Acceptance\Support\Extension\ApplicationEnvironment
 
 groups:
   AcceptanceTests-Job-*: AcceptanceTests-Job-*
diff --git a/typo3/sysext/core/Tests/Acceptance/Fixtures/BackendEnvironment.csv b/typo3/sysext/core/Tests/Acceptance/Fixtures/BackendEnvironment.csv
index c647184779f5..ce3b6cf145ed 100644
--- a/typo3/sysext/core/Tests/Acceptance/Fixtures/BackendEnvironment.csv
+++ b/typo3/sysext/core/Tests/Acceptance/Fixtures/BackendEnvironment.csv
@@ -3,11 +3,6 @@
 # password of both users is "password"
 ,1,0,1366642540,"admin","$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1",1,0,0,0,0,1366642540,1,0,,1371033743,0,0,0,"Klaus Admin"
 ,2,0,1452944912,"editor","$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1",0,0,0,0,0,1452944912,1,0,,1452944915,0,1,1,""
-"be_sessions"
-,"ses_id","ses_iplock","ses_userid","ses_tstamp","ses_data"
-# ses_id: hash_hmac('sha256', '886526ce72b86870739cc41991144ec1', sha1('iAmInvalid' . 'core-session-backend'))
-,"9869d429fc72742a476d5073d006d45dfb732962d9c024423efafef537e1c5bd","[DISABLED]",1,1777777777,"",
-,"f4c02f70058e79a8e7b523a266d4291007deacba6b2ca2536dd72d2fbb23696a","[DISABLED]",2,1777777777,"",
 "be_groups"
 ,"uid","pid","tstamp","title","tables_modify","crdate","subgroup"
 ,1,0,1452959228,"editor-group","pages",1452959228,
diff --git a/typo3/sysext/core/Tests/Acceptance/Helper/PasswordLogin.php b/typo3/sysext/core/Tests/Acceptance/Helper/PasswordLogin.php
new file mode 100644
index 000000000000..ecf5b7fd924a
--- /dev/null
+++ b/typo3/sysext/core/Tests/Acceptance/Helper/PasswordLogin.php
@@ -0,0 +1,91 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Acceptance\Helper;
+
+use Codeception\Module;
+use Codeception\Module\WebDriver;
+use Codeception\Util\Locator;
+use Facebook\WebDriver\WebDriverKeys;
+
+/**
+ * Helper class to log in backend users and load backend.
+ */
+final class PasswordLogin extends Module
+{
+    /**
+     * @var array Filled by .yml config with valid sessions per role
+     */
+    protected array $config = [
+        'passwords' => [],
+    ];
+
+    /**
+     * Log in a backend user (or use a session snapshot) ato ensure a
+     * session cookie is set. Afterwards the backend entrypoint is loaded.
+     *
+     * Use this action to change the backend user and avoid switching between users in the backend module
+     * "Backend Users" as this will change the user session ID and make it useless for subsequent calls of this action.
+     *
+     * @param string $role The backend user who should be logged in.
+     * @param int|float $waitTime Used waitTime in seconds between single steps. Default: 0.5
+     */
+    public function useExistingSession(string $role, float|int $waitTime = 0.5): void
+    {
+        $webDriver = $this->getWebDriver();
+
+        $hasSession = $this->loadSession($role);
+
+        $webDriver->amOnPage('/typo3');
+        $webDriver->wait($waitTime);
+
+        if (!$hasSession) {
+            $webDriver->waitForElement('body[data-typo3-login-ready]');
+            $password = $this->_getConfig('passwords')[$role];
+            $webDriver->fillField('#t3-username', $role);
+            $webDriver->fillField('#t3-password', $password);
+            $webDriver->pressKey('#t3-password', WebDriverKeys::ENTER);
+            $webDriver->waitForElement('.t3js-scaffold-toolbar');
+            $webDriver->saveSessionSnapshot('login.' . $role);
+        }
+
+        // Ensure main content frame is fully loaded, otherwise there are load-race-conditions ..
+        $webDriver->debugSection('IFRAME', 'Switch to list_frame');
+        $webDriver->waitForElement('iframe[name="list_frame"]');
+        $webDriver->switchToIFrame('list_frame');
+        $webDriver->waitForElement(Locator::firstElement('div.module'));
+        $webDriver->wait($waitTime);
+        // .. and switch back to main frame.
+        $webDriver->debugSection('IFRAME', 'Switch to main frame');
+        $webDriver->switchToIFrame();
+
+        $webDriver->debug(sprintf('useExistingSession("%s", %s) finished.', $role, $waitTime));
+    }
+
+    private function loadSession(string $role): bool
+    {
+        $webDriver = $this->getWebDriver();
+        $webDriver->webDriver->manage()->deleteCookieNamed('be_typo_user');
+        $webDriver->webDriver->manage()->deleteCookieNamed('be_lastLoginProvider');
+        return $webDriver->loadSessionSnapshot('login.' . $role, false);
+    }
+
+    private function getWebDriver(): WebDriver
+    {
+        return $this->getModule('WebDriver');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Acceptance/Install.suite.yml b/typo3/sysext/core/Tests/Acceptance/Install.suite.yml
index d2321da9b0e3..6a5ea1cda813 100644
--- a/typo3/sysext/core/Tests/Acceptance/Install.suite.yml
+++ b/typo3/sysext/core/Tests/Acceptance/Install.suite.yml
@@ -1,32 +1,6 @@
 actor: InstallTester
 
-modules:
-  enabled:
-    - WebDriver:
-        url: '%typo3TestingAcceptanceBaseUrl%'
-        browser: chrome
-        wait: 2
-        host: chrome
-        window_size: 1280x1024
-        capabilities:
-          goog:chromeOptions:
-            args: [ "--no-sandbox", "--disable-gpu" ]
-    - Asserts
-
 env:
-  headless:
-    modules:
-      enabled:
-        - WebDriver:
-            url: '%typo3TestingAcceptanceBaseUrl%'
-            browser: chrome
-            wait: 2
-            host: chrome
-            window_size: 1280x1024
-            capabilities:
-              goog:chromeOptions:
-                args: [ "--headless", "--no-sandbox", "--disable-gpu" ]
-
   mysql:
     extensions:
       enabled:
diff --git a/typo3/sysext/core/Tests/codeception.yml b/typo3/sysext/core/Tests/codeception.yml
index f7dff4d0756a..747ecbd3c250 100644
--- a/typo3/sysext/core/Tests/codeception.yml
+++ b/typo3/sysext/core/Tests/codeception.yml
@@ -14,7 +14,32 @@ extensions:
 modules:
   enabled:
     - Filesystem
+    - Asserts
+    - WebDriver
     - TYPO3\CMS\Core\Tests\Acceptance\Support\Helper\Config
+  config:
+    WebDriver:
+      url: '%typo3TestingAcceptanceBaseUrl%'
+      browser: chrome
+      port: 9515
+      path: /
+      window_size: 1280x1024
+env:
+  ci:
+    modules:
+      config:
+        WebDriver:
+          host: chrome
+          port: 4444
+          path: /wd/hub
+          wait: 2
+  headless:
+    modules:
+      config:
+        WebDriver:
+          capabilities:
+            goog:chromeOptions:
+              args: ["headless", "no-sandbox", "disable-gpu"]
 params:
   - parameters.yml
   - env
diff --git a/typo3/sysext/core/Tests/parameters.yml b/typo3/sysext/core/Tests/parameters.yml
index 5af975f81ba5..d4fe8aa21e16 100644
--- a/typo3/sysext/core/Tests/parameters.yml
+++ b/typo3/sysext/core/Tests/parameters.yml
@@ -1,7 +1,8 @@
 #
 # Default parameters for use in the codeception.yml.
 # These values can be overridden by environment variables,
-# e.g. in Build/testing-docker/local/docker-compose.yml
+# e.g. in Build/Scripts/runTest.sh
 #
 typo3TestingAcceptanceBaseUrl: http://web:80
-
+typo3TestingAcceptanceAdminPassword: password
+typo3TestingAcceptanceEditorPassword: password
-- 
GitLab