From da7691ae53c289abeffbc81be910521ba0dd625f Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Tue, 26 May 2026 20:16:50 +0200 Subject: [PATCH] [TASK] Remove code and mentions of acceptance test framework With core switching away from codeception to playwright, the patch removes the codeception based scaffolding, helpers and mentions in TF main / v10. --- .github/ISSUE_TEMPLATE/bug_report.md | 3 - Build/Scripts/runTests.sh | 10 - Build/phpstan/phpstan.neon | 2 - CONTRIBUTING.rst | 6 - .../Extension/BackendEnvironment.php | 452 ------------------ .../Acceptance/Helper/AbstractModalDialog.php | 82 ---- .../Acceptance/Helper/AbstractPageTree.php | 108 ----- Classes/Core/Acceptance/Helper/Acceptance.php | 132 ----- Classes/Core/Acceptance/Helper/Login.php | 166 ------- Classes/Core/Acceptance/Helper/Topbar.php | 49 -- Classes/Core/Acceptance/Step/FrameSteps.php | 253 ---------- .../Framework/DataHandling/DataSet.php | 6 +- Classes/Core/Testbase.php | 34 +- README.md | 4 +- composer.json | 2 +- 15 files changed, 24 insertions(+), 1285 deletions(-) delete mode 100644 Classes/Core/Acceptance/Extension/BackendEnvironment.php delete mode 100644 Classes/Core/Acceptance/Helper/AbstractModalDialog.php delete mode 100644 Classes/Core/Acceptance/Helper/AbstractPageTree.php delete mode 100644 Classes/Core/Acceptance/Helper/Acceptance.php delete mode 100644 Classes/Core/Acceptance/Helper/Login.php delete mode 100644 Classes/Core/Acceptance/Helper/Topbar.php delete mode 100644 Classes/Core/Acceptance/Step/FrameSteps.php diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dafdc617..6b9b0d9c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -54,6 +54,3 @@ Add more context to the problem here. - PHP version: - Web server used + version: - Operating system: -- Selenium server version: -- Browser driver (chromedriver/geckodriver...) version: -- Browser used + version: diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index c632fd39..6a954dd4 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -256,16 +256,6 @@ echo "########################################################################## echo "Result of ${TEST_SUITE}" >&2 echo "Container runtime: ${CONTAINER_BIN}" >&2 echo "PHP: ${PHP_VERSION}" >&2 -if [[ ${TEST_SUITE} =~ ^(functional|functionalDeprecated|acceptance|acceptanceInstall)$ ]]; then - case "${DBMS}" in - mariadb|mysql|postgres) - echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver ${DATABASE_DRIVER}" >&2 - ;; - sqlite) - echo "DBMS: ${DBMS}" >&2 - ;; - esac -fi if [[ -n ${EXTRA_TEST_OPTIONS} ]]; then echo " Note: Using -e is deprecated. Simply add the options at the end of the command." echo " Instead of: Build/Scripts/runTests.sh -s ${TEST_SUITE} -e '${EXTRA_TEST_OPTIONS}' $@" diff --git a/Build/phpstan/phpstan.neon b/Build/phpstan/phpstan.neon index 2396ea0a..1f0230a7 100644 --- a/Build/phpstan/phpstan.neon +++ b/Build/phpstan/phpstan.neon @@ -16,8 +16,6 @@ parameters: - ../../Tests excludePaths: - # Checking acceptance support files is cumbersome due to codeception dynamic mixin generation - - ../../Classes/Core/Acceptance/* # Text fixtures extensions uses $_EXTKEY phpstan would be report as "might not defined" - ../../Tests/Unit/*/Fixtures/Extensions/*/ext_emconf.php - ../../Tests/Unit/*/Fixtures/Packages/*/ext_emconf.php diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index b62e0515..414f24da 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -78,12 +78,6 @@ To provide a change to the testing framework, you have to ./Build/Scripts/runTests.sh -s functional - * TYPO3 Core acceptance tests (slow ~ 30-60 minutes): - - .. code-block:: bash - - ./Build/Scripts/runTests.sh -s acceptance - and confirming that the test runs have the same result as without the changes, diff --git a/Classes/Core/Acceptance/Extension/BackendEnvironment.php b/Classes/Core/Acceptance/Extension/BackendEnvironment.php deleted file mode 100644 index 045e7811..00000000 --- a/Classes/Core/Acceptance/Extension/BackendEnvironment.php +++ /dev/null @@ -1,452 +0,0 @@ - true, - 'typo3Cleanup' => true, - 'typo3DatabaseHost' => null, - 'typo3DatabaseUsername' => null, - 'typo3DatabasePassword' => null, - 'typo3DatabasePort' => null, - 'typo3DatabaseSocket' => null, - 'typo3DatabaseDriver' => null, - 'typo3DatabaseCharset' => null, - - /** - * Additional core extensions to load. - * - * To be used in own acceptance test suites. - * - * If a test suite needs additional core extensions, for instance as a dependency of - * an extension that is tested, those core extension names can be noted here and will - * be loaded. - * - * @var array - */ - 'coreExtensionsToLoad' => [], - - /** - * Array of test/fixture extensions paths that should be loaded for a test. - * - * To be used in own acceptance test suites. - * - * Given path is expected to be relative to your document root, example: - * - * array( - * 'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension', - * 'typo3conf/ext/base_extension', - * ); - * - * Extensions in this array are linked to the test instance, loaded - * and their ext_tables.sql will be applied. - * - * @var array - */ - 'testExtensionsToLoad' => [], - - /** - * Array of test/fixture folder or file paths that should be linked for a test. - * - * To be used in own acceptance test suites. - * - * array( - * 'link-source' => 'link-destination' - * ); - * - * Given paths are expected to be relative to the test instance root. - * The array keys are the source paths and the array values are the destination - * paths, example: - * - * array( - * 'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' => - * 'fileadmin/user_upload', - * ); - * - * To be able to link from my_own_ext the extension path needs also to be registered in - * property $testExtensionsToLoad - * - * @var array - */ - 'pathsToLinkInTestInstance' => [], - - /** - * This configuration array is merged with TYPO3_CONF_VARS - * that are set in default configuration and factory configuration - * - * To be used in own acceptance test suites. - * - * @var array - */ - 'configurationToUseInTestInstance' => [], - - /** - * Array of folders that should be created inside the test instance document root. - * - * To be used in own acceptance test suites. - * - * Per default the following folder are created - * /fileadmin - * /typo3temp - * /typo3conf - * /typo3conf/ext - * - * To create additional folders add the paths to this array. Given paths are expected to be - * relative to the test instance root and have to begin with a slash. Example: - * - * array( - * '/fileadmin/user_upload' - * ); - * - * @var array - */ - 'additionalFoldersToCreate' => [], - - /** - * Array of absolute paths to .csv files to be loaded into database. - * This can be used to prime the database with fixture records. - * - * The core for example uses this to have a default page tree and - * to create valid sessions so users are logged-in automatically. - * - * Example: [ __DIR__ . '/../../Fixtures/BackendEnvironment.csv' ] - */ - 'csvDatabaseFixtures' => [], - - /** - * Copy files within created test instance. - * - * @var array - */ - 'copyInstanceFiles' => [], - - /** - * Should target paths for self::$config['copyInstanceFiles'] be created. - * Will throw an exception if folder does not exists and set to `false`. - */ - 'copyInstanceFilesCreateTargetPath' => true, - ]; - - /** - * This array is to be extended by consuming extensions. - * It is merged with $config early in bootstrap to create - * the final setup configuration. - * - * Typcially, extensions specify here which core extensions - * should be loaded and that the extension that is tested should - * be loaded by setting 'coreExtensionsToLoad' and 'testExtensionsToLoad'. - * - * @var array - */ - protected $localConfig = []; - - /** - * Events to listen to - */ - public static $events = [ - Events::SUITE_BEFORE => 'bootstrapTypo3Environment', - Events::TEST_BEFORE => 'cleanupTypo3Environment', - ]; - - /** - * @var string|null Test instance path when created. - */ - protected ?string $instancePath = null; - - /** - * Initialize config array, called before events. - * - * Config options can be overridden via .yml config, example: - * - * extensions: - * enabled: - * - TYPO3\TestingFramework\Core\Acceptance\Extension\CoreEnvironment: - * typo3DatabaseHost: 127.0.0.1 - * - * Some config options can also be set via environment variables, which then - * take precedence: - * - * typo3DatabaseHost=127.0.0.1 ./bin/codecept run ... - */ - public function _initialize(): void - { - $this->config = array_replace($this->config, $this->localConfig); - $env = getenv('typo3Setup'); - $this->config['typo3Setup'] = is_string($env) - ? (trim($env) === 'false' ? false : (bool)$env) - : $this->config['typo3Setup']; - $env = getenv('typo3Cleanup'); - $this->config['typo3Cleanup'] = is_string($env) - ? (trim($env) === 'false' ? false : (bool)$env) - : $this->config['typo3Cleanup']; - $env = getenv('typo3DatabaseHost'); - $this->config['typo3DatabaseHost'] = is_string($env) ? trim($env) : $this->config['typo3DatabaseHost']; - $env = getenv('typo3DatabaseUsername'); - $this->config['typo3DatabaseUsername'] = is_string($env) ? trim($env) : $this->config['typo3DatabaseUsername']; - $env = getenv('typo3DatabasePassword'); - $this->config['typo3DatabasePassword'] = is_string($env) ? $env : $this->config['typo3DatabasePassword']; - $env = getenv('typo3DatabasePort'); - $this->config['typo3DatabasePort'] = is_string($env) ? (int)$env : (int)$this->config['typo3DatabasePort']; - $env = getenv('typo3DatabaseSocket'); - $this->config['typo3DatabaseSocket'] = is_string($env) ? trim($env) : $this->config['typo3DatabaseSocket']; - $env = getenv('typo3DatabaseDriver'); - $this->config['typo3DatabaseDriver'] = is_string($env) ? trim($env) : $this->config['typo3DatabaseDriver']; - $env = getenv('typo3DatabaseCharset'); - $this->config['typo3DatabaseCharset'] = is_string($env) ? trim($env) : $this->config['typo3DatabaseCharset']; - } - - /** - * Handle SUITE_BEFORE event. - * - * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance, - * create a database and create database schema. - * - * @param SuiteEvent $suiteEvent - */ - public function bootstrapTypo3Environment(SuiteEvent $suiteEvent) - { - if (!$this->config['typo3Setup']) { - return; - } - $testbase = new Testbase(); - $testbase->defineOriginalRootPath(); - $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'); - $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient'); - - $instancePath = $this->instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'; - putenv('TYPO3_PATH_ROOT=' . $instancePath); - putenv('TYPO3_PATH_APP=' . $instancePath); - $testbase->setTypo3TestingContext(); - - $testbase->removeOldInstanceIfExists($instancePath); - // Basic instance directory structure - $testbase->createDirectory($instancePath . '/fileadmin'); - $testbase->createDirectory($instancePath . '/typo3temp/var/transient'); - $testbase->createDirectory($instancePath . '/typo3temp/assets'); - $testbase->createDirectory($instancePath . '/typo3conf/ext'); - // Additionally requested directories - foreach ($this->config['additionalFoldersToCreate'] as $directory) { - $testbase->createDirectory($instancePath . '/' . $directory); - } - $coreExtensionsToLoad = $this->config['coreExtensionsToLoad']; - $testbase->setUpInstanceCoreLinks($instancePath, [], $coreExtensionsToLoad); - $testExtensionsToLoad = $this->config['testExtensionsToLoad']; - $testbase->linkTestExtensionsToInstance($instancePath, $testExtensionsToLoad); - $testbase->linkPathsInTestInstance($instancePath, $this->config['pathsToLinkInTestInstance']); - $localConfiguration['DB'] = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration($this->config); - $dbDriver = $localConfiguration['DB']['Connections']['Default']['driver']; - $originalDatabaseName = ''; - if ($dbDriver !== 'pdo_sqlite') { - $this->output->debug('Database Connection: ' . json_encode($localConfiguration['DB'])); - $originalDatabaseName = $localConfiguration['DB']['Connections']['Default']['dbname']; - // Append the unique identifier to the base database name to end up with a single database per test case - $localConfiguration['DB']['Connections']['Default']['dbname'] = $originalDatabaseName . '_at'; - $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration); - if ($dbDriver === 'mysqli' || $dbDriver === 'pdo_mysql') { - $localConfiguration['DB']['Connections']['Default']['charset'] = 'utf8mb4'; - $localConfiguration['DB']['Connections']['Default']['defaultTableOptions']['charset'] = 'utf8mb4'; - $localConfiguration['DB']['Connections']['Default']['defaultTableOptions']['collation'] = 'utf8mb4_unicode_ci'; - } - } else { - // sqlite dbs of all tests are stored in a dir parallel to instance roots. Allows defining this path as tmpfs. - $this->output->debug('Database Connection: ' . json_encode($localConfiguration['DB'])); - $testbase->createDirectory(dirname($instancePath) . '/acceptance-sqlite-dbs'); - $dbPathSqlite = dirname($instancePath) . '/acceptance-sqlite-dbs/test_acceptance.sqlite'; - $localConfiguration['DB']['Connections']['Default']['path'] = $dbPathSqlite; - } - // Set some hard coded base settings for the instance. Those could be overruled by - // $this->config['configurationToUseInTestInstance ']if needed again. - $localConfiguration['BE']['debug'] = true; - $localConfiguration['BE']['installToolPassword'] = '$P$notnotnotnotnotnot.validvalidva'; - $localConfiguration['SYS']['displayErrors'] = true; - $localConfiguration['SYS']['devIPmask'] = '*'; - $localConfiguration['SYS']['exceptionalErrors'] = E_ALL; - $localConfiguration['SYS']['errorHandlerErrors'] = E_ALL; - $localConfiguration['SYS']['trustedHostsPattern'] = '.*'; - $localConfiguration['SYS']['encryptionKey'] = 'iAmInvalid'; - // @todo: This sql_mode should be enabled as soon as styleguide and dataHandler can cope with it - //$localConfiguration['SYS']['setDBinit'] = 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';'; - $localConfiguration['GFX']['processor'] = 'GraphicsMagick'; - $testbase->setUpLocalConfiguration($instancePath, $localConfiguration, $this->config['configurationToUseInTestInstance']); - $frameworkExtensionPaths = []; - $testbase->setUpPackageStates($instancePath, [], $coreExtensionsToLoad, $testExtensionsToLoad, $frameworkExtensionPaths); - $this->output->debug('Loaded Extensions: ' . json_encode(array_merge($coreExtensionsToLoad, $testExtensionsToLoad))); - $container = $testbase->setUpBasicTypo3Bootstrap($instancePath); - if ($dbDriver !== 'pdo_sqlite') { - $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName); - } else { - $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['path'], $originalDatabaseName); - } - $testbase->loadExtensionTables(); - $testbase->createDatabaseStructure($container); - - // Unregister core error handler again, which has been initialized by - // $testbase->setUpBasicTypo3Bootstrap($instancePath); for DB schema - // migration. - // @todo: See which other possible state should be dropped here again (singletons, ...?) - restore_error_handler(); - - $this->applyDefaultCopyInstanceFilesConfiguration(); - $copyInstanceFilesCreateTargetPaths = (bool)($this->config['copyInstanceFilesCreateTargetPath'] ?? true); - $copyInstanceFiles = $this->config['copyInstanceFiles'] ?? []; - if (is_array($copyInstanceFiles) && $copyInstanceFiles !== []) { - $testbase->copyInstanceFiles($instancePath, $copyInstanceFiles, $copyInstanceFilesCreateTargetPaths); - } - - // Unset a closure or phpunit kicks in with a 'serialization of \Closure is not allowed' - // Alternative solution: - // unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']['extbase']); - $suite = $suiteEvent->getSuite(); - $suite->backupGlobals(false); - - foreach ($this->config['csvDatabaseFixtures'] as $fixture) { - DataSet::import($fixture); - } - } - - /** - * Method executed after each test - */ - public function cleanupTypo3Environment() - { - if (!$this->config['typo3Cleanup']) { - return; - } - // Reset uc db field of be_user "admin" to null to reduce - // possible side effects between single tests. - GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable('be_users') - ->update('be_users', ['uc' => null], ['uid' => 1]); - } - - private function applyDefaultCopyInstanceFilesConfiguration(): void - { - if ($this->hasExtension('typo3/cms-backend')) { - ArrayUtility::mergeRecursiveWithOverrule( - $this->config, - [ - 'copyInstanceFiles' => [ - // Create favicon.ico to suppress potential javascript errors in console - // which are caused by calling a non html in the browser, e.g. seo sitemap xml - 'typo3/sysext/backend/Resources/Public/Icons/favicon.ico' => [ - 'favicon.ico', - ], - ], - ] - ); - } - if ($this->hasExtension('typo3/cms-install')) { - ArrayUtility::mergeRecursiveWithOverrule( - $this->config, - [ - 'copyInstanceFiles' => [ - // Provide some files into the test instance normally added by installer - 'typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/root-htaccess' => [ - '.htaccess', - ], - 'typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/resources-root-htaccess' => [ - 'fileadmin/.htaccess', - ], - 'typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-htaccess' => [ - 'fileadmin/_temp_/.htaccess', - ], - 'typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/fileadmin-temp-index.html' => [ - 'fileadmin/_temp_/index.html', - ], - 'typo3/sysext/install/Resources/Private/FolderStructureTemplateFiles/typo3temp-var-htaccess' => [ - 'typo3temp/var/.htaccess', - ], - ], - ] - ); - } - } - - /** - * Verify if extension is available in the system and within the acceptance test instance. - */ - protected function hasExtension(string $extensionKeyOrComposerPackageName): bool - { - $instanceExtensions = $this->getInstanceExtensionKeys( - $this->config['coreExtensionsToLoad'], - $this->config['testExtensionsToLoad'], - ); - $packageInfo = (new ComposerPackageManager())->getPackageInfo($extensionKeyOrComposerPackageName); - if ($packageInfo === null) { - return false; - } - return $packageInfo->getExtensionKey() !== '' && in_array($packageInfo->getExtensionKey(), $instanceExtensions, true); - } - - /** - * Gather list of extension keys available within created test instance - * based on `coreExtensionsToLoad` and `testExtensionToLoad` config. - */ - private function getInstanceExtensionKeys( - array $coreExtensionsToLoad, - array $testExtensionsToLoad, - ): array { - $composerPackageManager = new ComposerPackageManager(); - if ($coreExtensionsToLoad === []) { - // Fallback to all system extensions needed for TYPO3 acceptanceInstall tests. - $coreExtensionsToLoad = $composerPackageManager->getSystemExtensionExtensionKeys(); - } - $result = []; - foreach ($coreExtensionsToLoad as $extensionKeyOrComposerPackageName) { - $packageInfo = $composerPackageManager->getPackageInfo($extensionKeyOrComposerPackageName); - if ($packageInfo === null || $packageInfo->getExtensionKey() === '') { - continue; - } - $result[] = $packageInfo->getExtensionKey(); - } - foreach ($testExtensionsToLoad as $extensionKeyOrComposerPackageName) { - $packageInfo = $composerPackageManager->getPackageInfo($extensionKeyOrComposerPackageName); - if ($packageInfo === null || $packageInfo->getExtensionKey() === '') { - continue; - } - $result[] = $packageInfo->getExtensionKey(); - } - return $result; - } -} diff --git a/Classes/Core/Acceptance/Helper/AbstractModalDialog.php b/Classes/Core/Acceptance/Helper/AbstractModalDialog.php deleted file mode 100644 index b3d1f7da..00000000 --- a/Classes/Core/Acceptance/Helper/AbstractModalDialog.php +++ /dev/null @@ -1,82 +0,0 @@ -tester; - $this->canSeeDialog(); - $I->click($buttonLinkLocator, self::$openedModalButtonContainerSelector); - $I->waitForElementNotVisible(self::$openedModalSelector); - } - - /** - * Check if modal dialog is visible in top frame - */ - public function canSeeDialog() - { - $I = $this->tester; - $I->switchToIFrame(); - $I->waitForElement(self::$openedModalSelector); - // I will wait two seconds to prevent failing tests - $I->wait(2); - } -} diff --git a/Classes/Core/Acceptance/Helper/AbstractPageTree.php b/Classes/Core/Acceptance/Helper/AbstractPageTree.php deleted file mode 100644 index 03a84709..00000000 --- a/Classes/Core/Acceptance/Helper/AbstractPageTree.php +++ /dev/null @@ -1,108 +0,0 @@ - .node'; - public static $treeItemAnchorSelector = 'text.node-name'; - - /** - * @var \AcceptanceTester - */ - protected $tester; - - /** - * Open the given hierarchical path in the pagetree and click the last page. - * - * Example to open "styleguide -> elements basic" page: - * [ - * 'styleguide TCA demo', - * 'elements basic', - * ] - * - * @param string[] $path - */ - public function openPath(array $path) - { - $context = $this->getPageTreeElement(); - foreach ($path as $pageName) { - $context = $this->ensureTreeNodeIsOpen($pageName, $context); - } - $context->findElement(\Facebook\WebDriver\WebDriverBy::cssSelector(static::$treeItemAnchorSelector))->click(); - } - - /** - * Check if the pagetree is visible end return the web element object - * - * @return RemoteWebElement - */ - public function getPageTreeElement() - { - $I = $this->tester; - $I->switchToIFrame(); - return $I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { - return $webdriver->findElement(\Facebook\WebDriver\WebDriverBy::cssSelector(static::$pageTreeSelector)); - }); - } - - /** - * Search for an element with the given link text in the provided context. - * - * @param string $nodeText - * @param RemoteWebElement $context - * @return RemoteWebElement - */ - protected function ensureTreeNodeIsOpen(string $nodeText, RemoteWebElement $context) - { - $I = $this->tester; - $I->see($nodeText, static::$treeItemSelector); - - /** @var RemoteWebElement $context */ - $context = $I->executeInSelenium(function () use ( - $nodeText, - $context - ) { - return $context->findElement(\Facebook\WebDriver\WebDriverBy::xpath('.//following-sibling::*//*[text()=\'' . $nodeText . '\']/..')); - }); - - try { - $context->findElement(\Facebook\WebDriver\WebDriverBy::cssSelector('.chevron.collapsed'))->click(); - } catch (\Facebook\WebDriver\Exception\NoSuchElementException $e) { - // element not found so it may be already opened... - } catch (\Facebook\WebDriver\Exception\ElementNotVisibleException $e) { - // element not found so it may be already opened... - } catch (\Facebook\WebDriver\Exception\ElementNotInteractableException $e) { - // another possible exception if the chevron isn't there ... depends on facebook driver version - } - - return $context; - } -} diff --git a/Classes/Core/Acceptance/Helper/Acceptance.php b/Classes/Core/Acceptance/Helper/Acceptance.php deleted file mode 100644 index 054ce336..00000000 --- a/Classes/Core/Acceptance/Helper/Acceptance.php +++ /dev/null @@ -1,132 +0,0 @@ -getAction() === 'click') { - $this->debug('Waiting for nprogress to hide...'); - $this->getModule('WebDriver')->waitForElementNotVisible('#nprogress', 10); - } - } - - /** - * Check for browser console errors after each step - * There is also an option to use _after() instead, but it causes Codeception to stop execution - * and mark test still as success (the failure is caught on PHPUnit level) - * - * @param \Codeception\Step $step - */ - public function _afterStep(Step $step) - { - if ($this->isBrowserConsoleSupported()) { - $this->assertEmptyBrowserConsole(); - } - } - - /** - * Selenium's logging interface is not yet supported by geckodriver, so browser console tests are disabled - * at the moment. However, custom parsing of geckodriver's console output can be implemented. - * - * @return bool - * - * @see https://github.com/mozilla/geckodriver/issues/284 (issue) - * @see https://github.com/mozilla/geckodriver/issues/284#issuecomment-477677764 (custom implementation I) - * @see https://github.com/nightwatchjs/nightwatch/issues/2217#issuecomment-541139435 (custom implementation II) - */ - protected function isBrowserConsoleSupported() - { - return $this->getWebDriver()->_getConfig('browser') !== 'firefox'; - } - - /** - * Check browser console for errors and fail - * - * See Codeception\Module\WebDriver::logJSErrors - */ - public function assertEmptyBrowserConsole() - { - $webDriver = $this->getModule('WebDriver'); - $browserLogEntries = $webDriver->webDriver->manage()->getLog('browser'); - - $messages = []; - foreach ($browserLogEntries as $logEntry) { - // We fail only on errors. Warnings and info messages are OK. - if (isset($logEntry['level']) === true - && isset($logEntry['message']) === true - && $this->isJSError($logEntry['level'], $logEntry['message']) - ) { - // Timestamp is in milliseconds, but date() requires seconds. - $time = date('H:i:s', (int)($logEntry['timestamp'] / 1000)); - // Append the milliseconds to the end of the time string - $ms = $logEntry['timestamp'] % 1000; - $messages[] = "{$time}.{$ms} {$logEntry['level']} - {$logEntry['message']}"; - } - } - if (empty($messages)) { - return; - } - $messages = array_merge(['Found following JavaScript errors in the browser console:'], $messages); - $message = implode(PHP_EOL, $messages); - $this->fail($message); - } - - /** - * COPIED FROM Codeception\Module\WebDriver - * - * Determines if the log entry is an error. - * The decision is made depending on browser and log-level. - * - * @param string $logEntryLevel - * @param string $message - * @return bool - */ - protected function isJSError($logEntryLevel, $message) - { - return $logEntryLevel === 'SEVERE' - && !str_contains($message, 'ERR_PROXY_CONNECTION_FAILED') - && !str_contains($message, 'was loaded over an insecure connection. This file should be served over HTTPS.') - ; - } - - /** - * @return WebDriver - */ - protected function getWebDriver() - { - return $this->getModule('WebDriver'); - } -} diff --git a/Classes/Core/Acceptance/Helper/Login.php b/Classes/Core/Acceptance/Helper/Login.php deleted file mode 100644 index 5ab5cb05..00000000 --- a/Classes/Core/Acceptance/Helper/Login.php +++ /dev/null @@ -1,166 +0,0 @@ - [], - ]; - - /** - * Set a backend user session cookie and load the backend entrypoint. - * - * 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 - * @throws ConfigurationException - */ - public function useExistingSession(string $role, float|int $waitTime = 0.5) - { - $webDriver = $this->getWebDriver(); - $newUserSessionId = $this->getUserSessionIdByRole($role); - - $hasSession = $this->_loadSession(); - if ($hasSession && $newUserSessionId !== '' && $newUserSessionId !== $this->getUserSessionId()) { - $webDriver->amOnPage('/typo3'); - $webDriver->wait($waitTime); - $this->_deleteSession(); - $hasSession = false; - } - - if (!$hasSession) { - $webDriver->amOnPage('/typo3'); - $webDriver->wait($waitTime); - $webDriver->waitForElement('body[data-typo3-login-ready]'); - $this->_createSession($newUserSessionId); - } - - // Reload the page to have a logged in backend. - $webDriver->amOnPage('/typo3'); - $webDriver->wait($waitTime); - - // 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)); - } - - /** - * @param string $role - * @return string - * @throws ConfigurationException - */ - protected function getUserSessionIdByRole($role) - { - if (empty($role)) { - return ''; - } - - if (!isset($this->_getConfig('sessions')[$role])) { - throw new ConfigurationException(sprintf( - 'Backend user session ID cannot be resolved for role "%s": ' . - 'Set session ID explicitly in configuration of module Login.', - $role - ), 1627554106); - } - - return $this->_getConfig('sessions')[$role]; - } - - /** - * @return bool - */ - public function _loadSession() - { - return $this->getWebDriver()->loadSessionSnapshot('login', false); - } - - public function _deleteSession() - { - $webDriver = $this->getWebDriver(); - $webDriver->webDriver->manage()->deleteCookieNamed('be_typo_user'); - $webDriver->webDriver->manage()->deleteCookieNamed('be_lastLoginProvider'); - $webDriver->deleteSessionSnapshot('login'); - } - - /** - * @param string $userSessionId - */ - public function _createSession($userSessionId) - { - $sessionJwt = self::encodeHashSignedJwt( - [ - 'identifier' => $userSessionId, - 'time' => (new \DateTimeImmutable())->format(\DateTimeImmutable::RFC3339), - ], - // relies on $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] - self::createSigningKeyFromEncryptionKey(UserSession::class) - ); - $webDriver = $this->getWebDriver(); - $webDriver->setCookie('be_typo_user', $sessionJwt, [], false); - $webDriver->setCookie('be_lastLoginProvider', '1433416747', [], false); - $webDriver->saveSessionSnapshot('login'); - } - - /** - * @return string - */ - protected function getUserSessionId() - { - $userSessionId = $this->getWebDriver()->grabCookie('be_typo_user'); - return $userSessionId ?? ''; - } - - /** - * @return WebDriver - * @throws \Codeception\Exception\ModuleException - */ - protected function getWebDriver() - { - return $this->getModule('WebDriver'); - } -} diff --git a/Classes/Core/Acceptance/Helper/Topbar.php b/Classes/Core/Acceptance/Helper/Topbar.php deleted file mode 100644 index 715e8eed..00000000 --- a/Classes/Core/Acceptance/Helper/Topbar.php +++ /dev/null @@ -1,49 +0,0 @@ -waitForElementNotVisible('#nprogress', 120); - $I->switchToIFrame(); - } - - /** - * Helper method switching to main content frame, the one with main module and top bar - */ - public function switchToContentFrame(): void - { - $I = $this; - $I->waitForElementNotVisible('#nprogress', 120); - $I->switchToIFrame('list_frame'); - $I->waitForElementNotVisible('#nprogress', 120); - } - - /** - * Move the module menu of the main frame to the middle of the given element matched by the given locator. - * - * ``` php - * scrollModuleMenuTo(['id' => 'file'], 0, -$moduleMenuPaddingTop); - * ?> - * ``` - * - * @param array $toSelector - * @param int $offsetX - * @param int $offsetY - */ - public function scrollModuleMenuTo(array $toSelector, int $offsetX = 0, int $offsetY = 0): void - { - $scrollingElement = 'document.getElementsByClassName("scaffold-modulemenu")[0]'; - $this->scrollFrameTo($scrollingElement, $toSelector, $offsetX, $offsetY); - } - - /** - * Move the module menu of the main frame to top. - */ - public function scrollModuleMenuToTop(): void - { - $scrollingElement = 'document.getElementsByClassName("scaffold-modulemenu")[0]'; - $this->scrollFrameToTop($scrollingElement); - } - - /** - * Move the module menu of the main frame to the bottom. - */ - public function scrollModuleMenuToBottom(): void - { - $scrollingElement = 'document.getElementsByClassName("scaffold-modulemenu")[0]'; - $this->scrollFrameToBottom($scrollingElement); - } - - /** - * Move the page tree of the main frame to the middle of the given element matched by the given locator. - * - * ``` php - * scrollPageTreeTo(['css' => '.node.identifier-0_32'], 0, -$pageTreePadding); - * ?> - * ``` - * - * @param array $toSelector - * @param int $offsetX - * @param int $offsetY - */ - public function scrollPageTreeTo(array $toSelector, int $offsetX = 0, int $offsetY = 0): void - { - $scrollingElement = 'document.getElementById("typo3-pagetree-tree")'; - $this->scrollFrameTo($scrollingElement, $toSelector, $offsetX, $offsetY); - } - /** - * Move the page tree of the main frame to top. - */ - public function scrollPageTreeToTop(): void - { - $scrollingElement = 'document.getElementById("typo3-pagetree-tree")'; - $this->scrollFrameToTop($scrollingElement); - } - - /** - * Move the page tree of the main frame to the bottom. - */ - public function scrollPageTreeToBottom(): void - { - $scrollingElement = 'document.getElementById("typo3-pagetree-tree")'; - $this->scrollFrameToBottom($scrollingElement); - } - - /** - * Move the module body of the content frame to the middle of the given element matched by the given locator. - * - * ``` php - * scrollModuleBodyTo(['css' => '.t3js-impexp-preview'], 0, -$moduleBodyPadding); - * ?> - * ``` - * - * @param array $toSelector - * @param int $offsetX - * @param int $offsetY - */ - public function scrollModuleBodyTo(array $toSelector, int $offsetX = 0, int $offsetY = 0): void - { - $scrollingElement = 'document.getElementsByClassName("module-body")[0]'; - $this->scrollFrameTo($scrollingElement, $toSelector, $offsetX, $offsetY); - } - - /** - * Move the module body of the content frame to top. - */ - public function scrollModuleBodyToTop(): void - { - $scrollingElement = 'document.getElementsByClassName("module-body")[0]'; - $this->scrollFrameToTop($scrollingElement); - } - - /** - * Move the module body of the content frame to the bottom. - */ - public function scrollModuleBodyToBottom(): void - { - $scrollingElement = 'document.getElementsByClassName("module-body")[0]'; - $this->scrollFrameToBottom($scrollingElement); - } - - /** - * Move the TYPO3 backend frame to the middle of the given element matched by the given locator. - * Extra shift, calculated from the top-left corner of the element, - * can be set by passing $offsetX and $offsetY parameters. - * - * @param string $scrollingElement - * @param array $toSelector - * @param int $offsetX - * @param int $offsetY - * - * @see \Codeception\Module\WebDriver::scrollTo - */ - protected function scrollFrameTo(string $scrollingElement, array $toSelector, int $offsetX = 0, int $offsetY = 0): void - { - $I = $this; - $I->executeInSelenium( - function (RemoteWebDriver $webDriver) use ($scrollingElement, $toSelector, $offsetX, $offsetY) { - $el = $webDriver->findElement($this->getStrictLocator($toSelector)); - $x = $el->getLocation()->getX() + $offsetX; - $y = $el->getLocation()->getY() + $offsetY; - $webDriver->executeScript("$scrollingElement.scrollTo($x, $y)"); - } - ); - } - - /** - * Move the TYPO3 backend frame to top. - * - * @param string $scrollingElement - */ - protected function scrollFrameToTop(string $scrollingElement): void - { - $I = $this; - $I->executeInSelenium( - function (RemoteWebDriver $webDriver) use ($scrollingElement) { - $webDriver->executeScript("$scrollingElement.scrollTop = 0"); - } - ); - } - - /** - * Move the TYPO3 backend frame to the bottom. - * - * @param string $scrollingElement - */ - protected function scrollFrameToBottom(string $scrollingElement): void - { - $I = $this; - $I->executeInSelenium( - function (RemoteWebDriver $webDriver) use ($scrollingElement) { - $webDriver->executeScript("$scrollingElement.scrollTop = $scrollingElement.scrollHeight"); - } - ); - } - - /** - * @param array $by - * @return WebDriverBy - * - * @see \Codeception\Module\WebDriver::getStrictLocator - */ - protected function getStrictLocator(array $by): WebDriverBy - { - $type = key($by); - $locator = $by[$type]; - switch ($type) { - case 'id': - return WebDriverBy::id($locator); - case 'name': - return WebDriverBy::name($locator); - case 'css': - return WebDriverBy::cssSelector($locator); - case 'xpath': - return WebDriverBy::xpath($locator); - case 'link': - return WebDriverBy::linkText($locator); - case 'class': - return WebDriverBy::className($locator); - default: - throw new MalformedLocatorException( - "$type => $locator", - 'Strict locator can be either xpath, css, id, link, class, name: ' - ); - } - } -} diff --git a/Classes/Core/Functional/Framework/DataHandling/DataSet.php b/Classes/Core/Functional/Framework/DataHandling/DataSet.php index 02273a20..e079ddc0 100644 --- a/Classes/Core/Functional/Framework/DataHandling/DataSet.php +++ b/Classes/Core/Functional/Framework/DataHandling/DataSet.php @@ -25,7 +25,7 @@ /** * Internal worker class managing .csv fixture file contents. * - * This is used in functional and acceptance tests to load .csv fixture files + * This is used in functional tests to load .csv fixture files * into database and to assert current database state with .csv fixture files. * * A .csv file can include table rows from multiple tables. @@ -45,7 +45,7 @@ * ,1,1,512,0,0,0,0,0,0,0,0,"Regular Element #2" * * @internal Directly using this class is discouraged, it may change any time. - * Use API methods like importCSVDataSet() (in functional & acceptance tests) + * Use API methods like importCSVDataSet() (in functional tests) * and assertCSVDataSet() (in functional tests) instead. */ final readonly class DataSet @@ -61,7 +61,7 @@ private function __construct( * Read a file and import it. * * @param string $path Absolute path to the CSV file containing the data set to load - * @internal API is exposed using importCSVDataSet() in FunctionalTestCase and BackendEnvironment acceptance test base class. + * @internal API is exposed using importCSVDataSet() in FunctionalTestCase test base class. */ public static function import(string $path): void { diff --git a/Classes/Core/Testbase.php b/Classes/Core/Testbase.php index 1c440e4f..0a9ead77 100644 --- a/Classes/Core/Testbase.php +++ b/Classes/Core/Testbase.php @@ -40,13 +40,12 @@ use TYPO3\TestingFramework\Composer\PackageInfo; /** - * This is a helper class used by unit, functional and acceptance test - * environment builders. + * This is a helper class used by unit and functional test environment builders. * It contains methods to create test environments. * * This class is for internal use only and may change wihtout further notice. * - * Use the classes `UnitTestCase`, `FunctionalTestCase` or `AcceptanceCoreEnvironment` + * Use the classes `UnitTestCase`, `FunctionalTestCase` * to indirectly benefit from this class in own extensions. */ class Testbase @@ -81,7 +80,7 @@ public function defineSitePath(): void /** * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root. - * For functional / acceptance tests only + * For functional tests only * If ORIGINAL_ROOT already is defined, this method is a no-op. */ public function defineOriginalRootPath(): void @@ -182,7 +181,7 @@ public function removeOldInstanceIfExists(string $instancePath): void /** * Link TYPO3 CMS core from "parent" instance. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param string[] $defaultCoreExtensionsToLoad Default core extensions to load (extension-key or package name) @@ -200,6 +199,7 @@ public function setUpInstanceCoreLinks( $linksToSet = []; $coreExtensions = array_unique(array_merge($defaultCoreExtensionsToLoad, $coreExtensionsToLoad)); // Fallback to all system extensions needed for TYPO3 acceptanceInstall tests. + // @todo: Still needed? if ($coreExtensions === []) { $coreExtensions = $this->composerPackageManager->getSystemExtensionExtensionKeys(); } @@ -226,6 +226,7 @@ public function setUpInstanceCoreLinks( // We can't just link the entry scripts here, because acceptance tests will make use of them // and we need Composer Mode to be turned off, thus they need to be rewritten to use the SystemEnvironmentBuilder // of the testing framework. + // @todo: What is still needed here? $installPhpExists = file_exists($instancePath . '/typo3/sysext/install/Resources/Private/Php/install.php'); $entryPointsToSet = [ [ @@ -311,7 +312,7 @@ public function provideInstance(array $additionalHtaccessFiles = []): void /** * Link test extensions to the typo3conf/ext folder of the instance. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param non-empty-string[] $extensionPaths Contains paths to extensions relative to document root @@ -358,7 +359,7 @@ public function linkTestExtensionsToInstance(string $instancePath, array $extens /** * Link framework extensions to the typo3conf/ext folder of the instance. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param non-empty-string[] $extensionPaths Contains paths to extensions relative to document root @@ -389,7 +390,7 @@ public function linkFrameworkExtensionsToInstance(string $instancePath, array $e /** * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the * test instance fileadmin folder. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root @@ -422,7 +423,7 @@ public function linkPathsInTestInstance(string $instancePath, array $pathsToLink * be used in case the references paths shall be modified inside the * testing instance which might not be possible with symbolic links. * - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath * @param array $pathsToProvideInTestInstance @@ -459,7 +460,7 @@ public function providePathsInTestInstance(string $instancePath, array $pathsToP } /** - * Database settings for functional and acceptance tests can be either set by + * Database settings for functional tests can be either set by * environment variables (recommended). The method fetches these. * * A unique name will be added to the database name later. @@ -467,6 +468,7 @@ public function providePathsInTestInstance(string $instancePath, array $pathsToP * @param array $config Incoming config arguments, used especially in acceptance test setups * @throws Exception * @return array [DB][host], [DB][username], ... + * @todo Is the $config argument still needed with codeception based tests being gone? */ public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration(array $config = []): array { @@ -549,7 +551,7 @@ public function testDatabaseNameIsNotTooLong(string $originalDatabaseName, array /** * Create settings.php file of the test instance. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param array $configuration Base configuration array @@ -582,7 +584,7 @@ public function setUpLocalConfiguration(string $instancePath, array $configurati * Compile typo3conf/PackageStates.php or var/build/PackageArtifact.php * containing default packages like core, a test specific list of additional core extensions, * and a list of test extensions. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance * @param non-empty-string[] $defaultCoreExtensionsToLoad Default list of core extensions to load @@ -737,7 +739,7 @@ public function setUpTestDatabase(string $databaseName, string $originalDatabase /** * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards. - * For functional and acceptance tests. + * For functional tests. * * @param non-empty-string $instancePath Absolute path to test instance */ @@ -876,7 +878,7 @@ private function truncateAllTablesForOtherDatabases(): void /** * Load ext_tables.php files. - * For functional and acceptance tests. + * For functional tests. */ public function loadExtensionTables(): void { @@ -887,7 +889,7 @@ public function loadExtensionTables(): void /** * Create tables and import static rows. - * For functional and acceptance tests. + * For functional tests. */ public function createDatabaseStructure(ContainerInterface $container): void { @@ -993,7 +995,7 @@ public function getPackagesPath(): string /** * Returns the absolute path the TYPO3 document root with trailing slash. - * This is the "original" document root, not the "instance" root for functional / acceptance tests. + * This is the "original" document root, not the "instance" root for functional tests. * * @return string the TYPO3 document root using Unix path separators */ diff --git a/README.md b/README.md index 148dc4a6..a40e68ee 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ # TYPO3 testing framework for core and extensions A straight and slim set of classes and configuration to test TYPO3 extensions. This framework is -used by the core, too and maintained by the core team as a base to execute unit, functional -and acceptance tests within the TYPO3 extension ecosystem. +used by the core, too and maintained by the core team as a base to execute unit and functional +tests within the TYPO3 extension ecosystem. ## Installation diff --git a/composer.json b/composer.json index ee956fc5..0603b1ec 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "typo3/testing-framework", - "description": "The TYPO3 testing framework provides base classes for unit, functional and acceptance testing.", + "description": "The TYPO3 testing framework provides base classes for unit and functional testing.", "keywords": [ "typo3", "testing",