From 3bf1c5bb6fbe08bf61e300dc63f2482a2c8426d2 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Sun, 3 Dec 2017 15:44:37 +0100 Subject: [PATCH 01/19] Revert "[TASK] Change page tree selectors in acceptance tests to match new SVG tree (#44)" This reverts commit 3d31fd19f2dc4b93525403ae3bbb4b560a424318. The patch is for core master (v9) only and must not be part of 1.2.x tags / 8 branch. --- Classes/Core/Acceptance/Support/Page/PageTree.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Classes/Core/Acceptance/Support/Page/PageTree.php b/Classes/Core/Acceptance/Support/Page/PageTree.php index f8d03601..159c5082 100644 --- a/Classes/Core/Acceptance/Support/Page/PageTree.php +++ b/Classes/Core/Acceptance/Support/Page/PageTree.php @@ -26,8 +26,8 @@ class PageTree // Selectors public static $pageTreeFrameSelector = '#typo3-pagetree'; public static $pageTreeSelector = '#typo3-pagetree-treeContainer'; - public static $treeItemSelector = 'g.nodes > .node'; - public static $treeItemAnchorSelector = 'text.node-name'; + public static $treeItemSelector = '.x-tree-node-ct > .x-tree-node'; + public static $treeItemAnchorSelector = '.x-tree-node-anchor'; /** * @var AcceptanceTester @@ -45,7 +45,7 @@ public function __construct(\AcceptanceTester $I) /** * Open the given hierarchical path in the pagetree and click the last page. * - * Example to open "styleguide -> elements basic" page: + * Example to open "styleuide -> elements basic" page: * [ * 'styleguide TCA demo', * 'elements basic', @@ -91,11 +91,13 @@ protected function ensureTreeNodeIsOpen(string $nodeText, RemoteWebElement $cont /** @var RemoteWebElement $context */ $context = $I->executeInSelenium(function () use ($nodeText, $context ) { - return $context->findElement(\WebDriverBy::xpath('//*[text()=\'' . $nodeText . '\']/..')); + return $context->findElement(\WebDriverBy::linkText($nodeText))->findElement( + WebDriverBy::xpath('ancestor::li[@class="x-tree-node"][1]') + ); }); try { - $context->findElement(\WebDriverBy::cssSelector('.chevron.collapsed'))->click(); + $context->findElement(\WebDriverBy::cssSelector('.x-tree-elbow-end-plus'))->click(); } catch (\Facebook\WebDriver\Exception\NoSuchElementException $e) { // element not found so it may be already opened... } From 6d0e0c11f2e8f1aae85d0b528d5086fd69a7ff3f Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Sun, 3 Dec 2017 15:49:01 +0100 Subject: [PATCH 02/19] [BUGFIX] Properly mock ReflectionService->getMethodParameters() The method must return an array. Properly mock that in ViewHelperBaseTestcase. --- Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php index 4292aac4..3207b8e4 100644 --- a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php +++ b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php @@ -118,6 +118,7 @@ protected function injectDependenciesIntoViewHelper(ViewHelperInterface $viewHel // as well as support for testing on core source before/after https://review.typo3.org/#/c/52796/ if (method_exists($viewHelper, 'injectReflectionService')) { $reflectionServiceProphecy = $this->prophesize(ReflectionService::class); + $reflectionServiceProphecy->getMethodParameters(Argument::cetera())->willReturn([]); $viewHelper->injectReflectionService($reflectionServiceProphecy->reveal()); } } From 2a33ec5a625f72bbe37bea651606b01b8d9794d0 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Thu, 18 Jan 2018 11:44:39 +0100 Subject: [PATCH 03/19] [TASK] Update composer.json license definition --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d3e0cc60..1aa4fef5 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "tests" ], "homepage": "https://typo3.org/", - "license": "GPL-2.0+", + "license": "GPL-2.0-or-later", "authors": [ { "name": "TYPO3 CMS Core Team", From 85e274954b597c9bd94a936ab04b7a40d46a0410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20N=C3=A4gler?= Date: Tue, 13 Feb 2018 17:14:26 +0100 Subject: [PATCH 04/19] [BUGFIX] Fix broken modal tests by waiting some seconds before click --- Classes/Core/Acceptance/Support/Helper/ModalDialog.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Classes/Core/Acceptance/Support/Helper/ModalDialog.php b/Classes/Core/Acceptance/Support/Helper/ModalDialog.php index 087fd9a7..0fc98174 100644 --- a/Classes/Core/Acceptance/Support/Helper/ModalDialog.php +++ b/Classes/Core/Acceptance/Support/Helper/ModalDialog.php @@ -77,5 +77,7 @@ public function canSeeDialog() $I = $this->tester; $I->switchToIFrame(); $I->waitForElement(self::$openedModalSelector); + // I will wait two seconds to prevent failing tests + $I->wait(2); } } From 134b932ffb0e79bac6c413caffaec57723de9e27 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Tue, 23 Jan 2018 14:27:40 +0100 Subject: [PATCH 05/19] [TASK] Add phpunit project files to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 56f45a1d..c9cf0fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ # Files auto-generated by codeception Resources/Core/Build/Configuration/Acceptance/Support/_generated/* +# phpunit project files +.idea/ From 2b7481998ee81cf6eb004e5615d3db699c014856 Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Sat, 17 Feb 2018 11:21:18 +0100 Subject: [PATCH 06/19] [TASK] Cleanup test environment for each installation test After installation tests start from a clean slate, each test must take care of cleaning up all leftovers from former test runs. --- .../Acceptance/AcceptanceInstallMysqlCoreEnvironment.php | 5 ++--- .../Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php b/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php index b60342b1..5e3bc31c 100644 --- a/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php +++ b/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php @@ -32,7 +32,7 @@ class AcceptanceInstallMysqlCoreEnvironment extends Extension * Events to listen to */ public static $events = [ - Events::SUITE_BEFORE => 'bootstrapTypo3Environment', + Events::TEST_BEFORE => 'bootstrapTypo3Environment', ]; /** @@ -41,9 +41,8 @@ class AcceptanceInstallMysqlCoreEnvironment extends Extension * 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) + public function bootstrapTypo3Environment() { $testbase = new Testbase(); $testbase->enableDisplayErrors(); diff --git a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php index c7ee9ca9..3aa0f4e4 100644 --- a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php +++ b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php @@ -32,7 +32,7 @@ class AcceptanceInstallPgsqlCoreEnvironment extends Extension * Events to listen to */ public static $events = [ - Events::SUITE_BEFORE => 'bootstrapTypo3Environment', + Events::TEST_BEFORE => 'bootstrapTypo3Environment', ]; /** @@ -41,9 +41,8 @@ class AcceptanceInstallPgsqlCoreEnvironment extends Extension * 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) + public function bootstrapTypo3Environment() { $testbase = new Testbase(); $testbase->enableDisplayErrors(); From e1e456ae30137cb7b74134a5f9b7c58ad2244fd9 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Sat, 17 Feb 2018 20:06:51 +0100 Subject: [PATCH 07/19] [TASK] Trivial cleanup --- .../Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php | 1 - .../Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php | 1 - 2 files changed, 2 deletions(-) diff --git a/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php b/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php index 5e3bc31c..d5235d98 100644 --- a/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php +++ b/Classes/Core/Acceptance/AcceptanceInstallMysqlCoreEnvironment.php @@ -40,7 +40,6 @@ class AcceptanceInstallMysqlCoreEnvironment extends Extension * * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance, * create a database and create database schema. - * */ public function bootstrapTypo3Environment() { diff --git a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php index 3aa0f4e4..1e3428a1 100644 --- a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php +++ b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php @@ -40,7 +40,6 @@ class AcceptanceInstallPgsqlCoreEnvironment extends Extension * * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance, * create a database and create database schema. - * */ public function bootstrapTypo3Environment() { From 4432bd9e4e39fb980481b35c5d4ec0054c427d28 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Mon, 7 May 2018 11:46:43 +0200 Subject: [PATCH 08/19] [TASK] Allow to set a port for postgres installer acceptance tests --- .../Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php index 1e3428a1..ffab7b15 100644 --- a/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php +++ b/Classes/Core/Acceptance/AcceptanceInstallPgsqlCoreEnvironment.php @@ -59,6 +59,9 @@ public function bootstrapTypo3Environment() 'password' => getenv('typo3DatabasePassword'), 'user' => getenv('typo3DatabaseUsername'), ]; + if (!empty(getenv('typo3DatabasePort'))) { + $connectionParameters['port'] = getenv('typo3DatabasePort'); + } $schemaManager = DriverManager::getConnection($connectionParameters)->getSchemaManager(); $databaseName = getenv('typo3DatabaseName') . '_atipgsql'; if (in_array($databaseName, $schemaManager->listDatabases(), true)) { From dd19899367604b1944758dd64ee5a63450e400a5 Mon Sep 17 00:00:00 2001 From: Anja Date: Tue, 8 May 2018 22:48:24 +0200 Subject: [PATCH 09/19] [TASK] Safeguard switchToIFrame function (#65) Before switching to a new frame, make sure the performed action has been finished. In Backend, the nprogress bar status can be used for that. --- .../Acceptance/Support/AcceptanceTester.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php b/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php index 54392cea..03b52b09 100644 --- a/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php +++ b/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php @@ -54,4 +54,19 @@ public function useExistingSession() // reload the page to have a logged in backend $I->amOnPage('/typo3/index.php'); } + + /** + * overrides generated function at _generated\AcceptanceTesterActions + * + * put a wait state for the progress bar being finished _before_ switching to another frame + * + * @param string $name + * @return mixed|null + * @throws Exception + */ + public function switchToIFrame($name = null) { + $this->getScenario()->runStep(new \Codeception\Step\Action('waitForElementNotVisible', ['div#nprogress'])); + return $this->getScenario()->runStep(new \Codeception\Step\Action('switchToIFrame', func_get_args())); + } + } From bbe4f7106a81f7eab74b8bd11cc475e455c854c1 Mon Sep 17 00:00:00 2001 From: Anja Date: Wed, 4 Jul 2018 10:22:25 +0200 Subject: [PATCH 10/19] [TASK] Simplify useExistingSession setup (#66) Almost all Cests include the fully loaded routine right after the useExistingSession call. In order to ease the setup for new Cests, include the routine into useExistingSession --- .../Configuration/Acceptance/Support/AcceptanceTester.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php b/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php index 03b52b09..c5d1aba2 100644 --- a/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php +++ b/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php @@ -53,6 +53,10 @@ public function useExistingSession() // reload the page to have a logged in backend $I->amOnPage('/typo3/index.php'); + + // Ensure main content frame is fully loaded, otherwise there are load-race-conditions + $I->switchToIFrame('list_frame'); + $I->waitForText('Web Content Management System'); } /** From dfed706443fd9008b321a13d6d2522da3ba3dd73 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Sat, 1 Sep 2018 15:52:19 +0200 Subject: [PATCH 11/19] [TASK] flushInternalRuntimeCaches() in unit test tearDown() --- Classes/Core/Unit/UnitTestCase.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Classes/Core/Unit/UnitTestCase.php b/Classes/Core/Unit/UnitTestCase.php index a8f20d09..f55894d7 100644 --- a/Classes/Core/Unit/UnitTestCase.php +++ b/Classes/Core/Unit/UnitTestCase.php @@ -59,6 +59,10 @@ abstract class UnitTestCase extends BaseTestCase */ protected function tearDown() { + // Flush the two static $indpEnvCache and $idnaStringCache + // between test runs to prevent side effects from these caches. + GeneralUtility::flushInternalRuntimeCaches(); + // Unset properties of test classes to safe memory $reflection = new \ReflectionObject($this); foreach ($reflection->getProperties() as $property) { From e541dfeaa2950022890ddb689d1f2422d97c2be0 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 17 Oct 2018 17:42:32 +0200 Subject: [PATCH 12/19] Backport acceptance test changes from master to v8 (#105) master branch received lots of changes regarding acceptance testing, while the v8 branch remained untouched. The patch ports the changes back to 8 branch in order to activate ac tests in core v8 again. --- .../Extension/BackendCoreEnvironment.php | 351 ++++++++++++++++++ .../Extension/InstallMysqlCoreEnvironment.php | 121 ++++++ .../InstallPostgresqlCoreEnvironment.php | 128 +++++++ .../Acceptance/Helper/AbstractModalDialog.php | 74 ++++ .../Acceptance/Helper/AbstractPageTree.php | 100 +++++ Classes/Core/Acceptance/Helper/Login.php | 76 ++++ Classes/Core/Acceptance/Helper/Topbar.php | 43 +++ Classes/Core/Acceptance/Step/FrameSteps.php | 44 +++ .../Build/Scripts/splitAcceptanceTests.sh | 9 +- 9 files changed, 942 insertions(+), 4 deletions(-) create mode 100644 Classes/Core/Acceptance/Extension/BackendCoreEnvironment.php create mode 100644 Classes/Core/Acceptance/Extension/InstallMysqlCoreEnvironment.php create mode 100644 Classes/Core/Acceptance/Extension/InstallPostgresqlCoreEnvironment.php create mode 100644 Classes/Core/Acceptance/Helper/AbstractModalDialog.php create mode 100644 Classes/Core/Acceptance/Helper/AbstractPageTree.php create mode 100644 Classes/Core/Acceptance/Helper/Login.php create mode 100644 Classes/Core/Acceptance/Helper/Topbar.php create mode 100644 Classes/Core/Acceptance/Step/FrameSteps.php diff --git a/Classes/Core/Acceptance/Extension/BackendCoreEnvironment.php b/Classes/Core/Acceptance/Extension/BackendCoreEnvironment.php new file mode 100644 index 00000000..52ea6727 --- /dev/null +++ b/Classes/Core/Acceptance/Extension/BackendCoreEnvironment.php @@ -0,0 +1,351 @@ + 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', + * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' => + * 'uploads/tx_myownext' + * ); + * + * 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 + * /uploads + * + * 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' => [], + + /** + * XML database fixtures to be loaded into database. + * + * Given paths are expected to be relative to your document root. + * + * @var array + */ + 'xmlDatabaseFixtures' => [ + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/be_users.xml', + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/be_sessions.xml', + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/be_groups.xml', + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/sys_category.xml', + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml', + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml', + ], + ]; + + /** + * Events to listen to + */ + public static $events = [ + Events::SUITE_BEFORE => 'bootstrapTypo3Environment', + Events::TEST_AFTER => 'cleanupTypo3Environment' + ]; + + /** + * 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() + { + $env = getenv('typo3Setup'); + $this->config['typo3Setup'] = is_string($env) + ? (trim($env) === 'false' ? false : (bool)$env) + : $this->config['typo3Setup']; + $env = getenv('typoCleanup'); + $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->enableDisplayErrors(); + $testbase->defineBaseConstants(); + $testbase->defineOriginalRootPath(); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient'); + + $instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'; + putenv('TYPO3_PATH_ROOT=' . $instancePath); + + $testbase->defineTypo3ModeBe(); + $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'); + $testbase->createDirectory($instancePath . '/uploads'); + // Additionally requested directories + foreach ($this->config['additionalFoldersToCreate'] as $directory) { + $testbase->createDirectory($instancePath . '/' . $directory); + } + $testbase->createLastRunTextfile($instancePath); + $testbase->setUpInstanceCoreLinks($instancePath); + // ext:styleguide is always loaded + $testExtensionsToLoad = array_merge( + [ 'typo3conf/ext/styleguide' ], + $this->config['testExtensionsToLoad'] + ); + $testbase->linkTestExtensionsToInstance($instancePath, $testExtensionsToLoad); + $testbase->linkPathsInTestInstance($instancePath, $this->config['pathsToLinkInTestInstance']); + $localConfiguration['DB'] = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration($this->config); + $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'; + + $this->output->debug('Database Connection: ' . json_encode($localConfiguration['DB'])); + $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration); + // 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']['lockHashKeyWords'] = ''; + $localConfiguration['BE']['installToolPassword'] = '$P$notnotnotnotnotnot.validvalidva'; + $localConfiguration['BE']['loginSecurityLevel'] = 'rsa'; + $localConfiguration['SYS']['isInitialInstallationInProgress'] = false; + $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true; + $localConfiguration['SYS']['displayErrors'] = false; + $localConfiguration['SYS']['debugExceptionHandler'] = ''; + $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['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = NullBackend::class; + $testbase->setUpLocalConfiguration($instancePath, $localConfiguration, $this->config['configurationToUseInTestInstance']); + $defaultCoreExtensionsToLoad = [ + 'core', + 'beuser', + 'extbase', + 'fluid', + 'filelist', + 'extensionmanager', + 'lang', + 'setup', + 'rsaauth', + 'saltedpasswords', + 'backend', + 'about', + 'belog', + 'install', + 't3skin', + 'frontend', + 'recordlist', + 'reports', + 'sv', + 'scheduler', + 'tstemplate', + ]; + $frameworkExtensionPaths = []; + $testbase->setUpPackageStates($instancePath, $defaultCoreExtensionsToLoad, $this->config['coreExtensionsToLoad'], $testExtensionsToLoad, $frameworkExtensionPaths); + $this->output->debug('Loaded Extensions: ' . json_encode(array_merge($defaultCoreExtensionsToLoad, $this->config['coreExtensionsToLoad'], $testExtensionsToLoad))); + $testbase->setUpBasicTypo3Bootstrap($instancePath); + $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName); + $testbase->loadExtensionTables(); + $testbase->createDatabaseStructure(); + + // 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->setBackupGlobals(false); + + foreach ($this->config['xmlDatabaseFixtures'] as $fixture) { + $testbase->importXmlDatabaseFixture($fixture); + } + + // styleguide generator uses DataHandler for some parts. DataHandler needs an initialized BE user + // with admin right and the live workspace. + Bootstrap::getInstance()->initializeBackendUser(); + $GLOBALS['BE_USER']->user['admin'] = 1; + $GLOBALS['BE_USER']->user['uid'] = 1; + $GLOBALS['BE_USER']->workspace = 0; + Bootstrap::getInstance()->initializeLanguageObject(); + + $styleguideGenerator = new Generator(); + $styleguideGenerator->create(); + + // @todo: Find out why that is needed to execute the first test successfully + $this->cleanupTypo3Environment(); + } + + /** + * Method executed after each test + * + * @return void + */ + 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]); + } +} diff --git a/Classes/Core/Acceptance/Extension/InstallMysqlCoreEnvironment.php b/Classes/Core/Acceptance/Extension/InstallMysqlCoreEnvironment.php new file mode 100644 index 00000000..154ab25e --- /dev/null +++ b/Classes/Core/Acceptance/Extension/InstallMysqlCoreEnvironment.php @@ -0,0 +1,121 @@ + '127.0.0.1', + 'typo3InstallMysqlDatabasePassword' => '', + 'typo3InstallMysqlDatabaseUsername' => 'root', + 'typo3InstallMysqlDatabaseName' => 'core_install', + ]; + + /** + * Override configuration from ENV if needed + */ + public function _initialize() + { + $env = getenv('typo3InstallMysqlDatabaseHost'); + $this->config['typo3InstallMysqlDatabaseHost'] = is_string($env) + ? trim($env) + : trim($this->config['typo3InstallMysqlDatabaseHost']); + + $env = getenv('typo3InstallMysqlDatabasePassword'); + $this->config['typo3InstallMysqlDatabasePassword'] = is_string($env) + ? trim($env) + : trim($this->config['typo3InstallMysqlDatabasePassword']); + + $env = getenv('typo3InstallMysqlDatabaseUsername'); + $this->config['typo3InstallMysqlDatabaseUsername'] = is_string($env) + ? trim($env) + : $this->config['typo3InstallMysqlDatabaseUsername']; + + $env = getenv('typo3InstallMysqlDatabaseName'); + $this->config['typo3InstallMysqlDatabaseName'] = (is_string($env) && !empty($env)) + ? mb_strtolower(trim($env)) + : mb_strtolower(trim($this->config['typo3InstallMysqlDatabaseName'])); + + if (empty($this->config['typo3InstallMysqlDatabaseName'])) { + throw new \RuntimeException('No database name given', 1530827194); + } + } + + /** + * Events to listen to + */ + public static $events = [ + Events::TEST_BEFORE => 'bootstrapTypo3Environment', + ]; + + /** + * Handle SUITE_BEFORE event. + * + * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance, + * create a database and create database schema. + */ + public function bootstrapTypo3Environment(TestEvent $event) + { + $testbase = new Testbase(); + $testbase->enableDisplayErrors(); + $testbase->defineBaseConstants(); + $testbase->defineOriginalRootPath(); + $testbase->setTypo3TestingContext(); + + $instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'; + $testbase->removeOldInstanceIfExists($instancePath); + putenv('TYPO3_PATH_ROOT=' . $instancePath); + + // Drop db from a previous run if exists + $connectionParameters = [ + 'driver' => 'mysqli', + 'host' => $this->config['typo3InstallMysqlDatabaseHost'], + 'port' => 3306, + 'password' => $this->config['typo3InstallMysqlDatabasePassword'], + 'user' => $this->config['typo3InstallMysqlDatabaseUsername'], + ]; + $this->output->debug("Connecting to MySQL: " . json_encode($connectionParameters)); + $databaseName = $this->config['typo3InstallMysqlDatabaseName']; + $schemaManager = DriverManager::getConnection($connectionParameters)->getSchemaManager(); + $this->output->debug("Database: $databaseName"); + if (in_array($databaseName, $schemaManager->listDatabases(), true)) { + $this->output->debug("Dropping database $databaseName"); + $schemaManager->dropDatabase($databaseName); + } + + $testbase->createDirectory($instancePath); + $testbase->setUpInstanceCoreLinks($instancePath); + touch($instancePath . '/FIRST_INSTALL'); + + // Have config available in test + $event->getTest()->getMetadata()->setCurrent($this->config); + } +} diff --git a/Classes/Core/Acceptance/Extension/InstallPostgresqlCoreEnvironment.php b/Classes/Core/Acceptance/Extension/InstallPostgresqlCoreEnvironment.php new file mode 100644 index 00000000..bdd44c62 --- /dev/null +++ b/Classes/Core/Acceptance/Extension/InstallPostgresqlCoreEnvironment.php @@ -0,0 +1,128 @@ + '127.0.0.1', + 'typo3InstallPostgresqlDatabasePort' => 5432, + 'typo3InstallPostgresqlDatabasePassword' => '', + 'typo3InstallPostgresqlDatabaseUsername' => '', + 'typo3InstallPostgresqlDatabaseName' => 'core_install', + ]; + + /** + * Override configuration from ENV if needed + */ + public function _initialize() + { + $env = getenv('typo3InstallPostgresqlDatabaseHost'); + $this->config['typo3InstallPostgresqlDatabaseHost'] = is_string($env) + ? trim($env) + : trim($this->config['typo3InstallPostgresqlDatabaseHost']); + + $env = getenv('typo3InstallPostgresqlDatabasePort'); + $this->config['typo3InstallPostgresqlDatabasePort'] = is_string($env) + ? (int)$env + : (int)$this->config['typo3InstallPostgresqlDatabasePort']; + + $env = getenv('typo3InstallPostgresqlDatabasePassword'); + $this->config['typo3InstallPostgresqlDatabasePassword'] = is_string($env) + ? trim($env) + : trim($this->config['typo3InstallPostgresqlDatabasePassword']); + + $env = getenv('typo3InstallPostgresqlDatabaseUsername'); + $this->config['typo3InstallPostgresqlDatabaseUsername'] = is_string($env) + ? trim($env) + : $this->config['typo3InstallPostgresqlDatabaseUsername']; + + $env = getenv('typo3InstallPostgresqlDatabaseName'); + $this->config['typo3InstallPostgresqlDatabaseName'] = (is_string($env) && !empty($env)) + ? mb_strtolower(trim($env)) + : mb_strtolower(trim($this->config['typo3InstallPostgresqlDatabaseName'])); + + if (empty($this->config['typo3InstallPostgresqlDatabaseName'])) { + throw new \RuntimeException('No database name given', 1530827194); + } + } + + /** + * Events to listen to + */ + public static $events = [ + Events::TEST_BEFORE => 'bootstrapTypo3Environment', + ]; + + /** + * Handle SUITE_BEFORE event. + * + * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance, + * create a database and create database schema. + */ + public function bootstrapTypo3Environment(TestEvent $event) + { + $testbase = new Testbase(); + $testbase->enableDisplayErrors(); + $testbase->defineBaseConstants(); + $testbase->defineOriginalRootPath(); + $testbase->setTypo3TestingContext(); + + $instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance'; + $testbase->removeOldInstanceIfExists($instancePath); + putenv('TYPO3_PATH_ROOT=' . $instancePath); + + // Drop db from a previous run if exists + $connectionParameters = [ + 'driver' => 'pdo_pgsql', + 'host' => $this->config['typo3InstallPostgresqlDatabaseHost'], + 'port' => $this->config['typo3InstallPostgresqlDatabasePort'], + 'password' => $this->config['typo3InstallPostgresqlDatabasePassword'], + 'user' => $this->config['typo3InstallPostgresqlDatabaseUsername'], + ]; + $this->output->debug("Connecting to PgSQL: " . json_encode($connectionParameters)); + $schemaManager = DriverManager::getConnection($connectionParameters)->getSchemaManager(); + $databaseName = $this->config['typo3InstallPostgresqlDatabaseName']; + $this->output->debug("Database: $databaseName"); + if (in_array($databaseName, $schemaManager->listDatabases(), true)) { + $this->output->debug("Dropping database $databaseName"); + $schemaManager->dropDatabase($databaseName); + } + $schemaManager->createDatabase($databaseName); + + $testbase->createDirectory($instancePath); + $testbase->setUpInstanceCoreLinks($instancePath); + touch($instancePath . '/FIRST_INSTALL'); + + // Have config available in test + $event->getTest()->getMetadata()->setCurrent($this->config); + } +} diff --git a/Classes/Core/Acceptance/Helper/AbstractModalDialog.php b/Classes/Core/Acceptance/Helper/AbstractModalDialog.php new file mode 100644 index 00000000..deb92c55 --- /dev/null +++ b/Classes/Core/Acceptance/Helper/AbstractModalDialog.php @@ -0,0 +1,74 @@ +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 new file mode 100644 index 00000000..8b2bed2e --- /dev/null +++ b/Classes/Core/Acceptance/Helper/AbstractPageTree.php @@ -0,0 +1,100 @@ + .x-tree-node'; + public static $treeItemAnchorSelector = '.x-tree-node-anchor'; + + /** + * @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(\WebDriverBy::cssSelector(self::$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(\WebDriverBy::cssSelector(self::$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, self::$treeItemSelector); + + /** @var RemoteWebElement $context */ + $context = $I->executeInSelenium(function () use ($nodeText, $context + ) { + return $context->findElement(\WebDriverBy::linkText($nodeText))->findElement( + WebDriverBy::xpath('ancestor::li[@class="x-tree-node"][1]') + ); + }); + + try { + $context->findElement(\WebDriverBy::cssSelector('.x-tree-elbow-end-plus'))->click(); + } catch (\Facebook\WebDriver\Exception\NoSuchElementException $e) { + // element not found so it may be already opened... + } + + return $context; + } +} diff --git a/Classes/Core/Acceptance/Helper/Login.php b/Classes/Core/Acceptance/Helper/Login.php new file mode 100644 index 00000000..10b47e30 --- /dev/null +++ b/Classes/Core/Acceptance/Helper/Login.php @@ -0,0 +1,76 @@ + [] + ]; + + /** + * Set a session cookie and load backend index.php + * + * @param string $role + * @throws ConfigurationException + */ + public function useExistingSession($role = '') + { + $wd = $this->getModule('WebDriver'); + $wd->amOnPage('/typo3/index.php'); + + $sessionCookie = ''; + if ($role) { + if (!isset($this->config['sessions'][$role])) { + throw new ConfigurationException("Helper\Login doesn't have `sessions` defined for $role"); + } + $sessionCookie = $this->config['sessions'][$role]; + } + + // @todo: There is a bug in PhantomJS / firefox (?) where adding a cookie fails. + // This bug will be fixed in the next PhantomJS version but i also found + // this workaround. First reset / delete the cookie and than set it and catch + // the webdriver exception as the cookie has been set successful. + try { + $wd->resetCookie('be_typo_user'); + $wd->setCookie('be_typo_user', $sessionCookie); + } catch (\Facebook\WebDriver\Exception\UnableToSetCookieException $e) { + } + try { + $wd->resetCookie('be_lastLoginProvider'); + $wd->setCookie('be_lastLoginProvider', '1433416747'); + } catch (\Facebook\WebDriver\Exception\UnableToSetCookieException $e) { + } + + // reload the page to have a logged in backend + $wd->amOnPage('/typo3/index.php'); + + // Ensure main content frame is fully loaded, otherwise there are load-race-conditions + $wd->switchToIFrame('list_frame'); + $wd->waitForText('Web Content Management System'); + // And switch back to main frame preparing a click to main module for the following main test case + $wd->switchToIFrame(); + } +} \ No newline at end of file diff --git a/Classes/Core/Acceptance/Helper/Topbar.php b/Classes/Core/Acceptance/Helper/Topbar.php new file mode 100644 index 00000000..7dc01a2b --- /dev/null +++ b/Classes/Core/Acceptance/Helper/Topbar.php @@ -0,0 +1,43 @@ +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); + } +} \ No newline at end of file diff --git a/Resources/Core/Build/Scripts/splitAcceptanceTests.sh b/Resources/Core/Build/Scripts/splitAcceptanceTests.sh index 03c100d6..553ba726 100755 --- a/Resources/Core/Build/Scripts/splitAcceptanceTests.sh +++ b/Resources/Core/Build/Scripts/splitAcceptanceTests.sh @@ -36,7 +36,7 @@ if [ -f buildTemp/testFilesWeighted.txt ]; then fi # A list of all acceptance test files -find . -name \*Cest.php -path \*typo3/sysext/*/Tests/Acceptance* > buildTemp/testFiles.txt +find . -name \*Cest.php -path \*typo3/sysext/*/Tests/Acceptance/Backend* > buildTemp/testFiles.txt # File with test files of format "42 ./path/to/file" while read testFile; do @@ -47,7 +47,7 @@ done < buildTemp/testFiles.txt # Sort list of files numeric cat buildTemp/testFilesWithNumberOfTestFiles.txt | sort -n -r > buildTemp/testFilesWeighted.txt -groupFilePath="vendor/typo3/testing-framework/Resources/Core/Build/Configuration/Acceptance" +groupFilePath="typo3/sysext/core/Tests/Acceptance" # Config file boilerplate per job for (( i=1; i<=${numberOfAcceptanceTestJobs}; i++)); do if [ -f ${groupFilePath}/AcceptanceTests-Job-${i} ]; then @@ -60,7 +60,7 @@ counter=0 direction=ascending while read testFileWeighted; do # test file only, without leading ./ - testFile=`echo ${testFileWeighted} | cut -f2 -d" " | cut -f2-40 -d"/"` + testFile=`echo ${testFileWeighted} | cut -f2 -d" " | cut -f6-40 -d"/"` # Goal: with 3 jobs, have: # file #0 to job #0 (asc) @@ -84,7 +84,8 @@ while read testFileWeighted; do direction=descending fi fi - echo "../../../../../../${testFile}" >> ${groupFilePath}/AcceptanceTests-Job-$(( targetJobNumberForFile + 1 )) + + echo ${testFile} >> ${groupFilePath}/AcceptanceTests-Job-$(( targetJobNumberForFile + 1 )) (( counter ++ )) done < buildTemp/testFilesWeighted.txt From bed1483d1bbb22ba8f60beb9eb689d3ada33e295 Mon Sep 17 00:00:00 2001 From: Georg Ringer Date: Tue, 20 Nov 2018 21:41:55 +0100 Subject: [PATCH 13/19] [TASK] Provide ExtensionTestEnvironment for 8branch (#112) --- Classes/Composer/ExtensionTestEnvironment.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Classes/Composer/ExtensionTestEnvironment.php diff --git a/Classes/Composer/ExtensionTestEnvironment.php b/Classes/Composer/ExtensionTestEnvironment.php new file mode 100644 index 00000000..3debf677 --- /dev/null +++ b/Classes/Composer/ExtensionTestEnvironment.php @@ -0,0 +1,87 @@ +/typo3conf/ext/". + * + * This class is added as composer "script" in TYPO3 extensions: + * + * "scripts": { + * "post-autoload-dump": [ + * "@prepare-extension-test-environment" + * ], + * "prepare-extension-test-structure": [ + * "TYPO3\TestingFramework\Composer\ExtensionTestEnvironment::prepare" + * ] + * }, + * + * It additionally needs the "extension key" (that will become the directory name in + * typo3conf/ext) and the name of the target directory in the extra section. Example for + * a extension "my_cool_extension": + * + * "extra": { + * "typo3/cms": { + * "web-dir": ".Build/Web", + * "extension-key": "my_cool_extension" + * } + * } + */ +final class ExtensionTestEnvironment +{ + /** + * Link directory that contains the composer.json file as + * .//typo3conf/ext/. + * + * @param Event $event + */ + public static function prepare(Event $event) + { + $composerConfigExtraSection = $event->getComposer()->getPackage()->getExtra(); + if (empty($composerConfigExtraSection['typo3/cms']['extension-key']) + || empty($composerConfigExtraSection['typo3/cms']['web-dir']) + ) { + throw new \RuntimeException( + 'This script needs properties in composer.json:' + . '"extra" "typo3/cms" "extension-key"' + . ' and "extra" "typo3/cms" "web-dir"', + 1540644486 + ); + } + $extensionKey = $composerConfigExtraSection['typo3/cms']['extension-key']; + $webDir = $composerConfigExtraSection['typo3/cms']['web-dir']; + $typo3confExt = __DIR__ . '/../../../../../../' . $webDir . '/typo3conf/ext'; + if (!is_dir($typo3confExt) && + !mkdir($typo3confExt, 0775, true) && + !is_dir($typo3confExt) + ) { + throw new \RuntimeException( + sprintf('Directory "%s" could not be created', $typo3confExt), + 1540650485 + ); + } + if (!is_link($typo3confExt . '/' . $extensionKey)) { + symlink(dirname(__DIR__, 6) . '/', $typo3confExt . '/' . $extensionKey); + } + } +} From 575fffdd3575500b9f0440f1c0428c67afc5782a Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 14 Dec 2018 15:14:22 +0100 Subject: [PATCH 14/19] [TASK] Remove not actively used irc info from composer.json --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 1aa4fef5..e08a4798 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ "support": { "general": "https://typo3.org/support/", "issues": "https://forge.typo3.org", - "irc": "irc://irc.freenode.net/#typo3-cms", "news": "nntp://lists.typo3.org" }, "require": { From 8cd35bfc6472c876147734655501d3001062f343 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 14 Dec 2018 15:21:08 +0100 Subject: [PATCH 15/19] [TASK] Issues are tracked on github --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e08a4798..e22982a6 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ ], "support": { "general": "https://typo3.org/support/", - "issues": "https://forge.typo3.org", - "news": "nntp://lists.typo3.org" + "issues": "https://github.com/TYPO3/testing-framework/issues" }, "require": { "phpunit/phpunit": "^6.2", From 23a892f8bde844b3234a4c6335c33458e600aae0 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Wed, 6 Feb 2019 14:48:15 +0100 Subject: [PATCH 16/19] [TASK] Introduce DataSet CSV padding --- .../Framework/DataHandling/DataSet.php | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Classes/Core/Functional/Framework/DataHandling/DataSet.php b/Classes/Core/Functional/Framework/DataHandling/DataSet.php index dc832327..53eca41f 100644 --- a/Classes/Core/Functional/Framework/DataHandling/DataSet.php +++ b/Classes/Core/Functional/Framework/DataHandling/DataSet.php @@ -192,6 +192,21 @@ public function getTableNames(): array return array_keys($this->data); } + /** + * @return int + */ + public function getMaximumPadding(): int + { + $maximums = array_map( + function (array $tableData) { + return count($tableData['fields'] ?? []); + }, + array_values($this->data) + ); + // adding additional index since field values are indented by one + return max($maximums) + 1; + } + /** * @param string $tableName * @return NULL|array @@ -233,8 +248,9 @@ public function getElements(string $tableName) /** * @param string $fileName + * @param null|int $padding */ - public function persist(string $fileName) + public function persist(string $fileName, int $padding = null) { $fileHandle = fopen($fileName, 'w'); @@ -246,15 +262,32 @@ public function persist(string $fileName) $fields = $tableData['fields']; array_unshift($fields, ''); - fputcsv($fileHandle, [$tableName]); - fputcsv($fileHandle, $fields); + fputcsv($fileHandle, $this->pad([$tableName], $padding)); + fputcsv($fileHandle, $this->pad($fields, $padding)); foreach ($tableData['elements'] as $element) { array_unshift($element, ''); - fputcsv($fileHandle, $element); + fputcsv($fileHandle, $this->pad($element, $padding)); } } fclose($fileHandle); } + + /** + * @param array $values + * @param null|int $padding + * @return array + */ + protected function pad(array $values, int $padding = null): array + { + if ($padding === null) { + return $values; + } + + return array_merge( + $values, + array_fill(0, $padding - count($values), '') + ); + } } From e963d700721b46249b9fa3e4aaaf806872160465 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 1 Mar 2019 12:17:09 +0100 Subject: [PATCH 17/19] [TASK] Use lowercase mikey179/vfsStream in composer.json to fix composer deprecation --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e22982a6..a557f98a 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require": { "phpunit/phpunit": "^6.2", - "mikey179/vfsStream": "~1.6.0", + "mikey179/vfsstream": "~1.6.0", "typo3fluid/fluid": "^2.2", "typo3/cms-core": "^8.5 || ^9", "typo3/cms-backend": "^8.5 || ^9", From e2a7c80b44e990d0c636c7aa54fc5e377312d360 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Mon, 29 Oct 2018 11:38:02 +0100 Subject: [PATCH 18/19] [TASK] Functional $pathsToProvideInTestInstance improvements FunctionalTestCase property $pathsToProvideInTestInstance handles creation of target parent directory if it does not exist and can copy directory structures recursive. Closes: #107 --- .../Core/Functional/FunctionalTestCase.php | 19 ++++++--- Classes/Core/Testbase.php | 41 ++++++++++++++++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index e29f4f2e..e279afd9 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -134,24 +134,31 @@ abstract class FunctionalTestCase extends BaseTestCase * 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', * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' => * 'uploads/tx_myownext' - * ); + * ] * * To be able to link from my_own_ext the extension path needs also to be registered in * property $testExtensionsToLoad * - * @var array + * @var string[] */ protected $pathsToLinkInTestInstance = []; /** * Similar to $pathsToLinkInTestInstance, with the difference that given * paths are really duplicated and provided in the instance - instead of - * using symbolic links. + * using symbolic links. Examples: + * + * [ + * // Copy an entire directory recursive to fileadmin + * 'typo3/sysext/lowlevel/Tests/Functional/Fixtures/testImages/' => 'fileadmin/', + * // Copy a single file into some deep destination directory + * 'typo3/sysext/lowlevel/Tests/Functional/Fixtures/testImage/someImage.jpg' => 'fileadmin/_processed_/0/a/someImage.jpg', + * ] * * @var string[] */ @@ -183,9 +190,9 @@ abstract class FunctionalTestCase extends BaseTestCase * 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 */ diff --git a/Classes/Core/Testbase.php b/Classes/Core/Testbase.php index 3c11764a..02e29f9a 100644 --- a/Classes/Core/Testbase.php +++ b/Classes/Core/Testbase.php @@ -331,7 +331,16 @@ public function providePathsInTestInstance(string $instancePath, array $pathsToP ); } $destinationPath = $instancePath . '/' . ltrim($designationIdentifier, '/'); - $success = copy($sourcePath, $destinationPath); + $destinationParentPath = dirname($destinationPath); + if (is_file($sourcePath)) { + if (!is_dir($destinationParentPath)) { + // Create parent dir if it does not exist yet + mkdir($destinationParentPath, 0775, true); + } + $success = copy($sourcePath, $destinationPath); + } else { + $success = $this->copyRecursive($sourcePath, $destinationPath); + } if (!$success) { throw new Exception( 'Can not copy the path ' . $sourcePath . ' to ' . $destinationPath, @@ -782,6 +791,36 @@ public static function resetTableSequences(Connection $connection, string $table } } + /** + * Copy a directory structure $from a source $to a destination, + * + * @param $from Absolute source path + * @param $to Absolute target path + * @return bool True if all went well + */ + protected function copyRecursive($from, $to) { + $dir = opendir($from); + if (!file_exists($to)) { + mkdir($to, 0775, true); + } + $result = true; + while (false !== ($file = readdir($dir))) { + if ($file == '.' || $file == '..') { + continue; + } + if (is_dir($from . DIRECTORY_SEPARATOR . $file)) { + $success = $this->copyRecursive($from . DIRECTORY_SEPARATOR . $file, $to . DIRECTORY_SEPARATOR . $file); + $result = $result & $success; + } + else { + $success = copy($from . DIRECTORY_SEPARATOR . $file, $to . DIRECTORY_SEPARATOR . $file); + $result = $result & $success; + } + } + closedir($dir); + return $result; + } + /** * Get Path to vendor dir * Since we are installed in vendor dir, we can safely assume the path of the vendor From edecd825fb8331a59fd7491e2cd19d5a9b70cc1f Mon Sep 17 00:00:00 2001 From: Markus Klein Date: Thu, 5 Dec 2019 16:36:16 +0100 Subject: [PATCH 19/19] [TASK] Make functional tests composer compatible If the instance to test is a composer-based installation, the vendor folder is usually not situated in the docroot, but one level above. Add a second check for this case. --- Classes/Core/Testbase.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Testbase.php b/Classes/Core/Testbase.php index 02e29f9a..77e8fb0f 100644 --- a/Classes/Core/Testbase.php +++ b/Classes/Core/Testbase.php @@ -580,7 +580,12 @@ public function setUpBasicTypo3Bootstrap($instancePath) $_SERVER['PWD'] = $instancePath; $_SERVER['argv'][0] = 'index.php'; - $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php'; + $classLoaderPath = rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php'; + if (!file_exists($classLoaderPath)) { + // the root composer.json might be outside the public instance path + $classLoaderPath = rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../../vendor/autoload.php'; + } + $classLoader = require $classLoaderPath; Bootstrap::getInstance() ->initializeClassLoader($classLoader) ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)