From 36fea7a100d73f18eca4b688fe118e8ffc91ba09 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Wed, 6 Feb 2019 14:48:15 +0100 Subject: [PATCH 01/31] [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 5edb3e5e..52d2b903 100644 --- a/Classes/Core/Functional/Framework/DataHandling/DataSet.php +++ b/Classes/Core/Functional/Framework/DataHandling/DataSet.php @@ -199,6 +199,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 @@ -240,8 +255,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'); @@ -253,15 +269,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 bea53bf24f2ead83be32691dacf841dca9558c99 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 8 Feb 2019 15:42:16 +0100 Subject: [PATCH 02/31] [TASK] Reset sqlite auto increment sequences if re-using db --- Classes/Core/Testbase.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Testbase.php b/Classes/Core/Testbase.php index a7b77859..61fa83bd 100644 --- a/Classes/Core/Testbase.php +++ b/Classes/Core/Testbase.php @@ -18,6 +18,7 @@ use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServerPlatform; use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\Core\ClassLoadingInformation; @@ -795,7 +796,8 @@ public function importXmlDatabaseFixture($path) */ public static function resetTableSequences(Connection $connection, string $tableName) { - if ($connection->getDatabasePlatform() instanceof PostgreSqlPlatform) { + $platform = $connection->getDatabasePlatform(); + if ($platform instanceof PostgreSqlPlatform) { $queryBuilder = $connection->createQueryBuilder(); $queryBuilder->getRestrictions()->removeAll(); $row = $queryBuilder->select('PGT.schemaname', 'S.relname', 'C.attname', 'T.relname AS tablename') @@ -827,6 +829,14 @@ public static function resetTableSequences(Connection $connection, string $table ) ); } + } elseif ($platform instanceof SqlitePlatform) { + // Drop eventually existing sqlite sequence for this table + $connection->exec( + sprintf( + 'DELETE FROM sqlite_sequence WHERE name=%s', + $connection->quote($tableName) + ) + ); } } From 4c997e4550308102244b5f1d844fb88b46d3aac6 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 1 Mar 2019 12:17:09 +0100 Subject: [PATCH 03/31] [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 919d54a3..2bd09499 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require": { "phpunit/phpunit": "^7.1", - "mikey179/vfsStream": "~1.6.0", + "mikey179/vfsstream": "~1.6.0", "typo3fluid/fluid": "^2.5", "typo3/cms-core": "^9.3", "typo3/cms-backend": "^9.3", From ff84f2b78ee28be3d7b3cd2f63a87de059c5abeb Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Fri, 1 Mar 2019 16:31:55 +0100 Subject: [PATCH 04/31] [TASK] Need at least phpunit 7.5.6 composer require phpunit/phpunit:^7.5.6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2bd09499..bba26767 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "issues": "https://github.com/TYPO3/testing-framework/issues" }, "require": { - "phpunit/phpunit": "^7.1", + "phpunit/phpunit": "^7.5.6", "mikey179/vfsstream": "~1.6.0", "typo3fluid/fluid": "^2.5", "typo3/cms-core": "^9.3", From 2cc4d197426e80fdde66a579b02ceca3f4fa2b16 Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Sat, 18 May 2019 12:36:46 +0200 Subject: [PATCH 05/31] [TASK] Update phpunit to version 8 composer require phpunit/phpunit:^8 --- Classes/Core/Functional/FunctionalTestCase.php | 2 +- Classes/Core/Unit/UnitTestCase.php | 8 ++++---- Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php | 3 ++- composer.json | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index 60437353..88de0274 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -240,7 +240,7 @@ abstract class FunctionalTestCase extends BaseTestCase * @return void * @throws \Doctrine\DBAL\DBALException */ - protected function setUp() + protected function setUp(): void { if (!defined('ORIGINAL_ROOT')) { $this->markTestSkipped('Functional tests must be called through phpunit on CLI'); diff --git a/Classes/Core/Unit/UnitTestCase.php b/Classes/Core/Unit/UnitTestCase.php index 4381baec..da95f276 100644 --- a/Classes/Core/Unit/UnitTestCase.php +++ b/Classes/Core/Unit/UnitTestCase.php @@ -92,7 +92,7 @@ abstract class UnitTestCase extends BaseTestCase /** * Set error reporting to always fail on E_NOTICE */ - public static function setUpBeforeClass() + public static function setUpBeforeClass(): void { $errorReporting = self::$backupErrorReporting = error_reporting(); // Always fail on notice level errors @@ -102,7 +102,7 @@ public static function setUpBeforeClass() /** * Reset error reporting to original state */ - public static function tearDownAfterClass() + public static function tearDownAfterClass(): void { error_reporting(self::$backupErrorReporting); } @@ -110,7 +110,7 @@ public static function tearDownAfterClass() /** * Generic setUp() */ - protected function setUp() + protected function setUp(): void { if ($this->backupEnvironment === true) { $this->backupEnvironment(); @@ -130,7 +130,7 @@ protected function setUp() * @throws \RuntimeException * @return void */ - protected function tearDown() + protected function tearDown(): void { // Restore Environment::class is asked for if ($this->backupEnvironment === true) { diff --git a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php index 3cd9499d..baffdce3 100644 --- a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php +++ b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php @@ -76,8 +76,9 @@ abstract class ViewHelperBaseTestcase extends \TYPO3\TestingFramework\Core\Unit\ /** * @return void */ - protected function setUp() + protected function setUp(): void { + parent::setUp(); $this->viewHelperVariableContainer = $this->prophesize(ViewHelperVariableContainer::class); $this->templateVariableContainer = $this->createMock(StandardVariableProvider::class); $this->uriBuilder = $this->createMock(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class); diff --git a/composer.json b/composer.json index bba26767..86d11ffe 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "issues": "https://github.com/TYPO3/testing-framework/issues" }, "require": { - "phpunit/phpunit": "^7.5.6", + "phpunit/phpunit": "^7.5.6 || ^8", "mikey179/vfsstream": "~1.6.0", "typo3fluid/fluid": "^2.5", "typo3/cms-core": "^9.3", @@ -36,7 +36,7 @@ "typo3/cms-recordlist": "^9.3" }, "suggest": { - "codeception/codeception": "^2.4", + "codeception/codeception": "^2.4 || ^3", "typo3/cms-styleguide": "^9.0" }, "config": { From b130f30d57eec75683d90ec43e2e4b54c6c4c98c Mon Sep 17 00:00:00 2001 From: Anja Date: Sat, 18 May 2019 20:12:04 +0200 Subject: [PATCH 06/31] Fix typo in composer suggestion the suggested codeception version is 2.5.4, not 2.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 86d11ffe..1a339866 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "typo3/cms-recordlist": "^9.3" }, "suggest": { - "codeception/codeception": "^2.4 || ^3", + "codeception/codeception": "^2.5.4 || ^3", "typo3/cms-styleguide": "^9.0" }, "config": { From dd373dab7b261cfa8e7a223e391ff2e117f6a436 Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Tue, 21 May 2019 09:31:18 +0200 Subject: [PATCH 07/31] Revert "Update phpunit to version 8" This reverts commits b130f30d57eec75683d90ec43e2e4b54c6c4c98c and 2cc4d197426e80fdde66a579b02ceca3f4fa2b16. This caused a breaking change for not prepared users. --- Classes/Core/Functional/FunctionalTestCase.php | 2 +- Classes/Core/Unit/UnitTestCase.php | 8 ++++---- Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php | 3 +-- composer.json | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index 88de0274..60437353 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -240,7 +240,7 @@ abstract class FunctionalTestCase extends BaseTestCase * @return void * @throws \Doctrine\DBAL\DBALException */ - protected function setUp(): void + protected function setUp() { if (!defined('ORIGINAL_ROOT')) { $this->markTestSkipped('Functional tests must be called through phpunit on CLI'); diff --git a/Classes/Core/Unit/UnitTestCase.php b/Classes/Core/Unit/UnitTestCase.php index da95f276..4381baec 100644 --- a/Classes/Core/Unit/UnitTestCase.php +++ b/Classes/Core/Unit/UnitTestCase.php @@ -92,7 +92,7 @@ abstract class UnitTestCase extends BaseTestCase /** * Set error reporting to always fail on E_NOTICE */ - public static function setUpBeforeClass(): void + public static function setUpBeforeClass() { $errorReporting = self::$backupErrorReporting = error_reporting(); // Always fail on notice level errors @@ -102,7 +102,7 @@ public static function setUpBeforeClass(): void /** * Reset error reporting to original state */ - public static function tearDownAfterClass(): void + public static function tearDownAfterClass() { error_reporting(self::$backupErrorReporting); } @@ -110,7 +110,7 @@ public static function tearDownAfterClass(): void /** * Generic setUp() */ - protected function setUp(): void + protected function setUp() { if ($this->backupEnvironment === true) { $this->backupEnvironment(); @@ -130,7 +130,7 @@ protected function setUp(): void * @throws \RuntimeException * @return void */ - protected function tearDown(): void + protected function tearDown() { // Restore Environment::class is asked for if ($this->backupEnvironment === true) { diff --git a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php index baffdce3..3cd9499d 100644 --- a/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php +++ b/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php @@ -76,9 +76,8 @@ abstract class ViewHelperBaseTestcase extends \TYPO3\TestingFramework\Core\Unit\ /** * @return void */ - protected function setUp(): void + protected function setUp() { - parent::setUp(); $this->viewHelperVariableContainer = $this->prophesize(ViewHelperVariableContainer::class); $this->templateVariableContainer = $this->createMock(StandardVariableProvider::class); $this->uriBuilder = $this->createMock(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class); diff --git a/composer.json b/composer.json index 1a339866..bba26767 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "issues": "https://github.com/TYPO3/testing-framework/issues" }, "require": { - "phpunit/phpunit": "^7.5.6 || ^8", + "phpunit/phpunit": "^7.5.6", "mikey179/vfsstream": "~1.6.0", "typo3fluid/fluid": "^2.5", "typo3/cms-core": "^9.3", @@ -36,7 +36,7 @@ "typo3/cms-recordlist": "^9.3" }, "suggest": { - "codeception/codeception": "^2.5.4 || ^3", + "codeception/codeception": "^2.4", "typo3/cms-styleguide": "^9.0" }, "config": { From 3284898ad044772384f3de71d7abc995e0f5356c Mon Sep 17 00:00:00 2001 From: Anja Date: Wed, 4 Dec 2019 22:38:02 +0100 Subject: [PATCH 08/31] [TASK] Update mikey179/vfsstream to version 1.6.8 (#163) used command: composer require mikey179/vfsstream:"~1.6.8" --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bba26767..9c0efcda 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ }, "require": { "phpunit/phpunit": "^7.5.6", - "mikey179/vfsstream": "~1.6.0", + "mikey179/vfsstream": "~1.6.8", "typo3fluid/fluid": "^2.5", "typo3/cms-core": "^9.3", "typo3/cms-backend": "^9.3", From 9295b87d6e7aaadae03219e9417da72da7d1e064 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Sun, 2 Feb 2020 11:56:07 +0100 Subject: [PATCH 09/31] [BUGFIX] Remove restrictions when using TYPO3 QueryBuilder for snapshots --- .../DataHandling/Snapshot/DatabaseAccessor.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Classes/Core/Functional/Framework/DataHandling/Snapshot/DatabaseAccessor.php b/Classes/Core/Functional/Framework/DataHandling/Snapshot/DatabaseAccessor.php index e613ad44..2bd93117 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Snapshot/DatabaseAccessor.php +++ b/Classes/Core/Functional/Framework/DataHandling/Snapshot/DatabaseAccessor.php @@ -16,8 +16,10 @@ */ use Doctrine\DBAL\FetchMode; +use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder; use Doctrine\DBAL\Schema\Table; -use TYPO3\CMS\Core\Database\Connection; +use Doctrine\DBAL\Connection; +use TYPO3\CMS\Core\Database\Query\QueryBuilder as TYPO3QueryBuilder; class DatabaseAccessor { @@ -90,7 +92,7 @@ public function import(array $import) */ private function exportTable(string $tableName): array { - return $this->connection->createQueryBuilder() + return $this->createQueryBuilder() ->select('*')->from($tableName) ->execute()->fetchAll(FetchMode::ASSOCIATIVE); } @@ -141,4 +143,16 @@ function (string $columnName) use ($table) { ); return array_combine($columnNames, $columnTypes); } + + /** + * @return DoctrineQueryBuilder|TYPO3QueryBuilder + */ + private function createQueryBuilder() + { + $queryBuilder = $this->connection->createQueryBuilder(); + if ($queryBuilder instanceof TYPO3QueryBuilder) { + $queryBuilder->getRestrictions()->removeAll(); + } + return $queryBuilder; + } } From 39f075663e9160ed38af54933912985895230cd1 Mon Sep 17 00:00:00 2001 From: Tomas Norre Mikkelsen Date: Sun, 29 Mar 2020 11:35:28 +0200 Subject: [PATCH 10/31] [BUGFIX] Use correct namespace for WebDriver (#172) --- Classes/Core/Acceptance/Helper/AbstractPageTree.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Classes/Core/Acceptance/Helper/AbstractPageTree.php b/Classes/Core/Acceptance/Helper/AbstractPageTree.php index caee65f8..389cfc83 100644 --- a/Classes/Core/Acceptance/Helper/AbstractPageTree.php +++ b/Classes/Core/Acceptance/Helper/AbstractPageTree.php @@ -52,7 +52,7 @@ public function openPath(array $path) foreach ($path as $pageName) { $context = $this->ensureTreeNodeIsOpen($pageName, $context); } - $context->findElement(\WebDriverBy::cssSelector(self::$treeItemAnchorSelector))->click(); + $context->findElement(WebDriverBy::cssSelector(self::$treeItemAnchorSelector))->click(); } /** @@ -65,7 +65,7 @@ public function getPageTreeElement() $I = $this->tester; $I->switchToIFrame(); return $I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { - return $webdriver->findElement(\WebDriverBy::cssSelector(self::$pageTreeSelector)); + return $webdriver->findElement(WebDriverBy::cssSelector(self::$pageTreeSelector)); }); } @@ -84,11 +84,11 @@ 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::xpath('//*[text()=\'' . $nodeText . '\']/..')); }); try { - $context->findElement(\WebDriverBy::cssSelector('.chevron.collapsed'))->click(); + $context->findElement(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) { From 9797120bc2f34cbf7e01ea7b7713929d125c08a6 Mon Sep 17 00:00:00 2001 From: Tymoteusz Motylewski Date: Mon, 23 Mar 2020 10:00:26 +0100 Subject: [PATCH 11/31] [TASK] Allow switching backend user in the test (#171) * Allow switching backend user in the test Sometimes it's useful to be able to switch backend user in the test (e.g. import data with admin, and then execute test with regular user) To achieve that a new method is introduced setUpBackendUser which sets up backend user which is already in the db. * Do not import fixture in setUpBackendUser * Remove unneeded empty line --- Classes/Core/Functional/FunctionalTestCase.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Classes/Core/Functional/FunctionalTestCase.php b/Classes/Core/Functional/FunctionalTestCase.php index 60437353..0c061e33 100644 --- a/Classes/Core/Functional/FunctionalTestCase.php +++ b/Classes/Core/Functional/FunctionalTestCase.php @@ -377,6 +377,18 @@ protected function setUpBackendUserFromFixture($userUid) { $this->importDataSet($this->backendUserFixture); + return $this->setUpBackendUser($userUid); + } + + /** + * Sets up Backend User which is already available in db + * + * @param int $userUid + * @return BackendUserAuthentication + * @throws Exception + */ + protected function setUpBackendUser($userUid): BackendUserAuthentication + { $queryBuilder = $this->getConnectionPool() ->getQueryBuilderForTable('be_users'); $queryBuilder->getRestrictions()->removeAll(); From e151b1492c770dce3352c7509919e142abe5a371 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Mon, 6 Apr 2020 16:52:28 +0200 Subject: [PATCH 12/31] [TASK] Add support for basic action in YAML scenarios Related: #155 --- .../Scenario/DataHandlerFactory.php | 53 +++++++++++++++++++ .../Scenario/DataHandlerWriter.php | 42 +++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php index e6fa07a0..7d2b6e40 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php @@ -41,6 +41,11 @@ class DataHandlerFactory */ private $dataMapPerWorkspace = []; + /** + * @var array + */ + private $commandMapPerWorkspace = []; + /** * @var bool[] */ @@ -82,6 +87,14 @@ public function getDataMapPerWorkspace(): array return $this->dataMapPerWorkspace; } + /** + * @return array + */ + public function getCommandMapPerWorkspace(): array + { + return $this->commandMapPerWorkspace; + } + /** * @return bool[] */ @@ -136,6 +149,9 @@ private function processEntityItem( $tableName = $entityConfiguration->getTableName(); $newId = StringUtility::getUniqueId('NEW'); $this->setInDataMap($tableName, $newId, $values, (int)$workspaceId); + if (isset($itemSettings['actions'])) { + $this->setInCommandMap($tableName, $newId, $nodeId, $itemSettings['actions'], (int)$workspaceId); + } foreach ($itemSettings['versionVariants'] ?? [] as $versionVariantSettings) { $this->processVersionVariantItem( @@ -200,6 +216,9 @@ private function processLanguageVariantItem( $tableName = $entityConfiguration->getTableName(); $newId = StringUtility::getUniqueId('NEW'); $this->setInDataMap($tableName, $newId, $values, 0); + if (isset($itemSettings['actions'])) { + $this->setInCommandMap($tableName, $newId, $nodeId, $itemSettings['actions'], (int)$workspaceId); + } foreach ($itemSettings['languageVariants'] ?? [] as $variantItemSettings) { $this->processLanguageVariantItem( @@ -230,6 +249,9 @@ private function processVersionVariantItem( $tableName = $entityConfiguration->getTableName(); $this->setInDataMap($tableName, $ancestorId, $values, (int)$values['workspace']); + if (isset($itemSettings['actions'])) { + $this->setInCommandMap($tableName, $ancestorId, $nodeId, $itemSettings['actions'], (int)$values['workspace']); + } } /** @@ -439,6 +461,37 @@ private function setInDataMap( $this->dataMapPerWorkspace[$workspaceId][$tableName][$identifier] = $values; } + private function setInCommandMap( + string $tableName, + string $identifier, + ?string $nodeId, + array $actionItems, + int $workspaceId = 0 + ): void { + if (empty($actionItems)) { + return; + } + // @todo implement `immediate` actions -> needs to split dataMap & commandMap in logical sections + foreach ($actionItems as $actionItem) { + $action = $actionItem['action'] ?? null; + $type = $actionItem['type'] ?? null; + $target = $actionItem['target'] ?? null; + if ($action === 'move') { + if ($type === 'toPage' && $target !== null) { + $this->commandMapPerWorkspace[$workspaceId][$tableName][$identifier]['move'] = $target; + } elseif ($type === 'toTop' && $nodeId !== null) { + $this->commandMapPerWorkspace[$workspaceId][$tableName][$identifier]['move'] = $nodeId; + } elseif ($type === 'afterRecord' && $target !== null) { + $this->commandMapPerWorkspace[$workspaceId][$tableName][$identifier]['move'] = '-' . $target; + } + } elseif ($action === 'delete') { + $this->commandMapPerWorkspace[$workspaceId][$tableName][$identifier]['delete'] = true; + } elseif ($action === 'discard' && $workspaceId > 0) { + $this->commandMapPerWorkspace[$workspaceId][$tableName][$identifier]['clearWSID'] = true; + } + } + } + /** * @param int $workspaceId * @param string $tableName diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php index c193885f..865b9642 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php @@ -80,6 +80,17 @@ public function invokeFactory(DataHandlerFactory $factory) $this->dataHandler->errorLog ); } + foreach ($factory->getCommandMapPerWorkspace() as $workspaceId => $commandMap) { + $commandMap = $this->updateCommandMap($commandMap); + $backendUser = clone $this->backendUser; + $backendUser->workspace = $workspaceId; + $this->dataHandler->start([], $commandMap, $backendUser); + $this->dataHandler->process_cmdmap(); + $this->errors = array_merge( + $this->errors, + $this->dataHandler->errorLog + ); + } } /** @@ -110,4 +121,35 @@ private function updateDataMap(array $dataMap): array } return $updatedTableDataMap; } + + /** + * @param array $commandMap + * @return array + */ + private function updateCommandMap(array $commandMap): array + { + $updatedTableCommandMap = []; + foreach ($commandMap as $tableName => $tableDataMap) { + foreach ($tableDataMap as $key => $values) { + $key = $this->dataHandler->substNEWwithIDs[$key] ?? $key; + $values = array_map( + function ($value) { + if (!is_string($value)) { + return $value; + } + if (strpos($value, 'NEW') === 0) { + return $this->dataHandler->substNEWwithIDs[$value] ?? $value; + } + if (strpos($value, '-NEW') === 0) { + return $this->dataHandler->substNEWwithIDs[substr($value, 1)] ?? $value; + } + return $value; + }, + $values + ); + $updatedTableCommandMap[$tableName][$key] = $values; + } + } + return $updatedTableCommandMap; + } } From 1df287bc5a2bc95293d276c4e644ea099c7a363d Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Tue, 10 Sep 2019 23:19:17 +0200 Subject: [PATCH 13/31] [TASK] Throw exception in case suggested id is redeclared --- .../DataHandling/Scenario/DataHandlerFactory.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php index 7d2b6e40..521064ba 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php @@ -374,6 +374,16 @@ private function addSuggestedId( int $suggestedId ): void { $identifier = $entityConfiguration->getTableName() . ':' . $suggestedId; + if (isset($this->suggestedIds[$identifier])) { + throw new \LogicException( + sprintf( + 'Cannot redeclare identifier "%s" with "%d"', + $identifier, + $suggestedId + ), + 1568146788 + ); + } $this->suggestedIds[$identifier] = true; } From 7302baf90d810f25c9f41e80a330f07004566008 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Tue, 10 Sep 2019 23:20:14 +0200 Subject: [PATCH 14/31] [BUGFIX] Consider workspace version when creating language variants --- .../Framework/DataHandling/Scenario/DataHandlerFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php index 521064ba..5e2db7e1 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php @@ -215,7 +215,8 @@ private function processLanguageVariantItem( $tableName = $entityConfiguration->getTableName(); $newId = StringUtility::getUniqueId('NEW'); - $this->setInDataMap($tableName, $newId, $values, 0); + $workspaceId = $itemSettings['version']['workspace'] ?? 0; + $this->setInDataMap($tableName, $newId, $values, (int)$workspaceId); if (isset($itemSettings['actions'])) { $this->setInCommandMap($tableName, $newId, $nodeId, $itemSettings['actions'], (int)$workspaceId); } From bcc6b38766908e9ed0614a3622a77a4baefd99d3 Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Tue, 10 Sep 2019 23:30:20 +0200 Subject: [PATCH 15/31] [EXPERIMENTAL] Adjust dynamic id handling * regular records are fine to be incremented by one * versions most probably are incremented by two (for placeholders) --- .../Scenario/DataHandlerFactory.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php index 5e2db7e1..778641d2 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerFactory.php @@ -315,7 +315,14 @@ private function processEntityValues( $parentColumnName = $entityConfiguration->getParentColumnName(); $nodeColumnName = $entityConfiguration->getNodeColumnName(); - $suggestedId = $staticId > 0 ? $staticId : $this->incrementDynamicId($entityConfiguration); + // @todo probably dynamic assignment is a bad idea & we should just use auto incremented values... + $incrementValue = !empty($itemSettings['version']) ? 2 : 1; + if ($staticId > 0) { + $suggestedId = $staticId; + $this->incrementDynamicId($entityConfiguration, $incrementValue - 1); + } else { + $suggestedId = $this->incrementDynamicId($entityConfiguration, $incrementValue); + } $this->addSuggestedId($entityConfiguration, $suggestedId); $values = $entityConfiguration->processValues($itemSettings[$sourceProperty]); $values['uid'] = $suggestedId; @@ -423,12 +430,16 @@ private function addStaticId( * @return int */ private function incrementDynamicId( - EntityConfiguration $entityConfiguration + EntityConfiguration $entityConfiguration, + int $incrementValue = 1 ): int { if (!isset($this->dynamicIdsPerEntity[$entityConfiguration->getName()])) { $this->dynamicIdsPerEntity[$entityConfiguration->getName()] = static::DYNAMIC_ID; } - return ++$this->dynamicIdsPerEntity[$entityConfiguration->getName()]; + $result = $this->dynamicIdsPerEntity[$entityConfiguration->getName()]; + // increment for next(!) assingment, since current process might create version or language variants + $this->dynamicIdsPerEntity[$entityConfiguration->getName()] += $incrementValue; + return $result; } /** From b7d8fe0be201bec9c18b6c5d915f719a5cabd8fd Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Sat, 2 May 2020 14:48:34 +0200 Subject: [PATCH 16/31] [TASK] Ensure login module is completely loaded and processed --- Classes/Core/Acceptance/Helper/Login.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Acceptance/Helper/Login.php b/Classes/Core/Acceptance/Helper/Login.php index 10b47e30..60578b4e 100644 --- a/Classes/Core/Acceptance/Helper/Login.php +++ b/Classes/Core/Acceptance/Helper/Login.php @@ -38,8 +38,10 @@ class Login extends Module */ public function useExistingSession($role = '') { + /** @var Module\WebDriver $wd */ $wd = $this->getModule('WebDriver'); $wd->amOnPage('/typo3/index.php'); + $wd->waitForElement('body[data-typo3-login-ready]'); $sessionCookie = ''; if ($role) { @@ -73,4 +75,4 @@ public function useExistingSession($role = '') // 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 +} From cf386945e562929fdce161557ac89f76a0291c9c Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Sat, 1 Aug 2020 16:56:05 +0200 Subject: [PATCH 17/31] [TASK] Provide opportunity to write a site config for codeception tests In order to test multi language features, we need a more sophisticated site configuration than the autogenerated one. --- .../Helper/AbstractSiteConfiguration.php | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php diff --git a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php new file mode 100644 index 00000000..a4c008be --- /dev/null +++ b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php @@ -0,0 +1,66 @@ + ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'], + 'DK' => ['id' => 1, 'title' => 'Dansk', 'locale' => 'da_DK.UTF8'], + ]; + + /** + * @var AcceptanceTester + * + * currently unused, but let's keep it for the time being. It will come in handy. + */ + protected $tester; + + public function adjustSiteConfiguration(): void + { + $sitesDir = __DIR__ . '/../../../../../../../typo3temp/var/tests/acceptance/typo3conf/sites'; + $siteConfiguration = new SiteConfiguration($sitesDir); + $scandir = scandir($sitesDir); + if (!empty($scandir)) { + $identifer = end(array_diff($scandir, ['.', '..'])); + } else { + $identifer = 'local-testing'; + } + + $configuration = $this->buildSiteConfiguration(1, '/'); + $configuration += [ + 'langugages' => [ + $this->buildDefaultLanguageConfiguration('EN', '/en/'), + $this->buildLanguageConfiguration('DK', '/dk/') + ], + ]; + + $siteConfiguration = new SiteConfiguration($sitesDir); + $siteConfiguration->write($identifer, $configuration); + } +} From 3ef255037623c3bbd7a44f6744fbeffd6e84a321 Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Tue, 4 Aug 2020 18:11:45 +0200 Subject: [PATCH 18/31] [TASK] Use predefined path constant instead directory walking --- Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php index a4c008be..e5028b3a 100644 --- a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php +++ b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php @@ -43,8 +43,7 @@ abstract class AbstractSiteConfiguration public function adjustSiteConfiguration(): void { - $sitesDir = __DIR__ . '/../../../../../../../typo3temp/var/tests/acceptance/typo3conf/sites'; - $siteConfiguration = new SiteConfiguration($sitesDir); + $sitesDir = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance/typo3conf/sites'; $scandir = scandir($sitesDir); if (!empty($scandir)) { $identifer = end(array_diff($scandir, ['.', '..'])); From c704d55e5ca85f95122d9efcc53559014abf7b8d Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Tue, 11 Aug 2020 16:37:23 +0200 Subject: [PATCH 19/31] [TASK] Provide third language for site configuration --- Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php index e5028b3a..2c2d8c4e 100644 --- a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php +++ b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php @@ -32,6 +32,7 @@ abstract class AbstractSiteConfiguration protected const LANGUAGE_PRESETS = [ 'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'], 'DK' => ['id' => 1, 'title' => 'Dansk', 'locale' => 'da_DK.UTF8'], + 'DK' => ['id' => 2, 'title' => 'German', 'locale' => 'de_DE.UTF8'], ]; /** @@ -55,7 +56,8 @@ public function adjustSiteConfiguration(): void $configuration += [ 'langugages' => [ $this->buildDefaultLanguageConfiguration('EN', '/en/'), - $this->buildLanguageConfiguration('DK', '/dk/') + $this->buildLanguageConfiguration('DK', '/dk/'), + $this->buildLanguageConfiguration('DE', '/de/'), ], ]; From a3d9950467dbe7d9fd894e9a7b8fb8671c3c40da Mon Sep 17 00:00:00 2001 From: Anja Leichsenring Date: Tue, 11 Aug 2020 16:49:18 +0200 Subject: [PATCH 20/31] [BUGFIX] Fix double language key caused by copy&paste new language --- Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php index 2c2d8c4e..de1a8cf8 100644 --- a/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php +++ b/Classes/Core/Acceptance/Helper/AbstractSiteConfiguration.php @@ -32,7 +32,7 @@ abstract class AbstractSiteConfiguration protected const LANGUAGE_PRESETS = [ 'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'], 'DK' => ['id' => 1, 'title' => 'Dansk', 'locale' => 'da_DK.UTF8'], - 'DK' => ['id' => 2, 'title' => 'German', 'locale' => 'de_DE.UTF8'], + 'DE' => ['id' => 2, 'title' => 'German', 'locale' => 'de_DE.UTF8'], ]; /** From af6e1e57f18365a5c1ad0c239427b5ed816157b2 Mon Sep 17 00:00:00 2001 From: Benni Mack Date: Thu, 3 Sep 2020 11:02:26 +0200 Subject: [PATCH 21/31] [BUGFIX] Consider negative new records in workspace datahandler tests --- .../DataHandling/Scenario/DataHandlerWriter.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php index 865b9642..ac9b46a0 100644 --- a/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php +++ b/Classes/Core/Functional/Framework/DataHandling/Scenario/DataHandlerWriter.php @@ -110,9 +110,22 @@ private function updateDataMap(array $dataMap): array $updatedTableDataMap = []; foreach ($dataMap as $tableName => $tableDataMap) { foreach ($tableDataMap as $key => $values) { - $pageId = $values['pid'] ?? null; $key = $this->dataHandler->substNEWwithIDs[$key] ?? $key; - $values['pid'] = $this->dataHandler->substNEWwithIDs[$pageId] ?? $pageId; + $values = array_map( + function ($value) { + if (!is_string($value)) { + return $value; + } + if (strpos($value, 'NEW') === 0) { + return $this->dataHandler->substNEWwithIDs[$value] ?? $value; + } + if (strpos($value, '-NEW') === 0) { + return $this->dataHandler->substNEWwithIDs[substr($value, 1)] ?? $value; + } + return $value; + }, + $values + ); if ((string)$key === (string)(int)$key) { unset($values['pid']); } From 28d10c8c6198e172ac3bfcc268320a9d0792074c Mon Sep 17 00:00:00 2001 From: Oliver Hader Date: Tue, 8 Sep 2020 00:53:24 +0200 Subject: [PATCH 22/31] [TASK] Add possibility to define allowed browser console errors Allowed console errors are only valid for one test execution. Thus, these exceptions need to be declared for every test case. ``` use TYPO3\TestingFramework\Core\Acceptance\Helper\Acceptance; class SomeCest { public function someTest(Scenario $scenario) { $acceptance = $scenario->current('modules')['\\' . Acceptance::class] ?? null; $acceptnance->allowBrowserError('/Found invalid token [[:digit:]]{40}/i'); ... ``` --- Classes/Core/Acceptance/Helper/Acceptance.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Classes/Core/Acceptance/Helper/Acceptance.php b/Classes/Core/Acceptance/Helper/Acceptance.php index 51458932..403dc7f9 100644 --- a/Classes/Core/Acceptance/Helper/Acceptance.php +++ b/Classes/Core/Acceptance/Helper/Acceptance.php @@ -17,12 +17,21 @@ use Codeception\Module; use Codeception\Step; +use Codeception\TestInterface; /** * Helper class to verify javascript browser console does not throw errors. */ class Acceptance extends Module { + private $allowedBrowserErrors = []; + + public function _before(TestInterface $test) + { + parent::_before($test); + $this->allowedBrowserErrors = []; + } + /** * Wait for backend progress bar to finish / disappear before "click" steps are performed. * @@ -48,6 +57,21 @@ public function _afterStep(Step $step) $this->assertEmptyBrowserConsole(); } + public function allowBrowserError(string $pattern): void + { + $this->allowedBrowserErrors[] = $pattern; + } + + public function isAllowedBrowserError(string $message): bool + { + foreach ($this->allowedBrowserErrors as $pattern) { + if (preg_match($pattern, $message)) { + return true; + } + } + return false; + } + /** * Check browser console for errors and fail * @@ -64,6 +88,7 @@ public function assertEmptyBrowserConsole() if (true === isset($logEntry['level']) && true === isset($logEntry['message']) && $this->isJSError($logEntry['level'], $logEntry['message']) + && $this->isAllowedBrowserError((string)$logEntry['message']) === false ) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', (int)($logEntry['timestamp'] / 1000)); From cd5cb561153a343716df51c99cd667b37451e5b0 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 11 Nov 2020 15:51:47 +0100 Subject: [PATCH 23/31] [TASK] Migrate from travis-ci to github actions (#213) --- .github/workflows/ci.yml | 24 ++++ .gitignore | 1 + .travis.yml | 28 ---- Build/Scripts/runTests.sh | 170 ++++++++++++++++++++++++ Build/testing-docker/docker-compose.yml | 55 ++++++++ README.md | 2 - 6 files changed, 250 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100755 Build/Scripts/runTests.sh create mode 100644 Build/testing-docker/docker-compose.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2e2e05a2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: [push, pull_request] + +jobs: + + testsuite: + name: all tests + runs-on: ubuntu-latest + strategy: + matrix: + php: [ '7.2', '7.3', '7.4' ] + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Composer install + run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s composerInstall + + - name: Lint PHP + run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s lint + + - name: Unit Tests + run: Build/Scripts/runTests.sh -p ${{ matrix.php }} -s unit diff --git a/.gitignore b/.gitignore index 71cef2d3..30187ae1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea/ composer.lock public/ +Build/testing-docker/.env diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e95db0e5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -language: php - -php: - - 7.2 - - 7.3 - -cache: - directories: - - $HOME/.composer/cache - -install: - - composer install - -script: - - > - echo; - echo "Running unit tests"; - echo; - echo; - .Build/bin/phpunit Tests/Unit/; - - - > - echo; - echo "Running php lint"; - echo; - echo; - find . -name \*.php ! -path "./.Build/*" ! -path "./public/*" -exec php -l {} >/dev/null \; - diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh new file mode 100755 index 00000000..d6391564 --- /dev/null +++ b/Build/Scripts/runTests.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash + +# +# Test runner based on docker and docker-compose. +# + +# Function to write a .env file in Build/testing-docker/local +# This is read by docker-compose and vars defined here are +# used in Build/testing-docker/docker-compose.yml +setUpDockerComposeDotEnv() { + # Delete possibly existing local .env file if exists + [ -e .env ] && rm .env + # Set up a new .env file for docker-compose + echo "COMPOSE_PROJECT_NAME=local" >> .env + # To prevent access rights of files created by the testing, the docker image later + # runs with the same user that is currently executing the script. docker-compose can't + # use $UID directly itself since it is a shell variable and not an env variable, so + # we have to set it explicitly here. + echo "HOST_UID=`id -u`" >> .env + # Your local home directory for composer and npm caching + echo "HOST_HOME=${HOME}" >> .env + # Your local user + echo "ROOT_DIR"=${ROOT_DIR} >> .env + echo "HOST_USER=${USER}" >> .env + echo "DOCKER_PHP_IMAGE=${DOCKER_PHP_IMAGE}" >> .env + echo "SCRIPT_VERBOSE=${SCRIPT_VERBOSE}" >> .env +} + +# Load help text into $HELP +read -r -d '' HELP < + Specifies which test suite to run + - composerInstall: "composer install" + - lint: PHP linting + - unit (default): PHP unit tests + + -p <7.2|7.3|7.4> + Specifies the PHP minor version to be used + - 7.2 (default): use PHP 7.2 + - 7.3: use PHP 7.3 + - 7.4: use PHP 7.4 + + -v + Enable verbose script output. Shows variables and docker commands. + + -h + Show this help. + +Examples: + # Run unit tests using default PHP version + ./Build/Scripts/runTests.sh + + # Run unit tests using PHP 7.4 + ./Build/Scripts/runTests.sh -p 7.4 +EOF + +# Test if docker-compose exists, else exit out with error +if ! type "docker-compose" > /dev/null; then + echo "This script relies on docker and docker-compose. Please install" >&2 + exit 1 +fi + +# Go to the directory this script is located, so everything else is relative +# to this dir, no matter from where this script is called. +THIS_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd "$THIS_SCRIPT_DIR" || exit 1 + +# Go to directory that contains the local docker-compose.yml file +cd ../testing-docker || exit 1 + +# Option defaults +ROOT_DIR=`readlink -f ${PWD}/../../` +TEST_SUITE="unit" +PHP_VERSION="7.2" +SCRIPT_VERBOSE=0 + +# Option parsing +# Reset in case getopts has been used previously in the shell +OPTIND=1 +# Array for invalid options +INVALID_OPTIONS=(); +# Simple option parsing based on getopts (! not getopt) +while getopts ":s:p:hv" OPT; do + case ${OPT} in + s) + TEST_SUITE=${OPTARG} + ;; + p) + PHP_VERSION=${OPTARG} + ;; + h) + echo "${HELP}" + exit 0 + ;; + v) + SCRIPT_VERBOSE=1 + ;; + \?) + INVALID_OPTIONS+=(${OPTARG}) + ;; + :) + INVALID_OPTIONS+=(${OPTARG}) + ;; + esac +done + +# Exit on invalid options +if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then + echo "Invalid option(s):" >&2 + for I in "${INVALID_OPTIONS[@]}"; do + echo "-"${I} >&2 + done + echo >&2 + echo "${HELP}" >&2 + exit 1 +fi + +# Move "7.2" to "php72", the latter is the docker container name +DOCKER_PHP_IMAGE=`echo "php${PHP_VERSION}" | sed -e 's/\.//'` + +if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x +fi + +# Suite execution +case ${TEST_SUITE} in + cgl) + # Active dry-run for cgl needs not "-n" but specific options + if [[ ! -z ${CGLCHECK_DRY_RUN} ]]; then + CGLCHECK_DRY_RUN="--dry-run --diff --diff-format udiff" + fi + setUpDockerComposeDotEnv + docker-compose run cgl + SUITE_EXIT_CODE=$? + docker-compose down + ;; + composerInstall) + setUpDockerComposeDotEnv + docker-compose run composer_install + SUITE_EXIT_CODE=$? + docker-compose down + ;; + lint) + setUpDockerComposeDotEnv + docker-compose run lint + SUITE_EXIT_CODE=$? + docker-compose down + ;; + unit) + setUpDockerComposeDotEnv + docker-compose run unit + SUITE_EXIT_CODE=$? + docker-compose down + ;; + *) + echo "Invalid -s option argument ${TEST_SUITE}" >&2 + echo >&2 + echo "${HELP}" >&2 + exit 1 +esac + +exit $SUITE_EXIT_CODE \ No newline at end of file diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml new file mode 100644 index 00000000..4236a978 --- /dev/null +++ b/Build/testing-docker/docker-compose.yml @@ -0,0 +1,55 @@ +version: '2.3' +services: + composer_install: + image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + user: ${HOST_UID} + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + - ${HOST_HOME}:${HOST_HOME} + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro + working_dir: ${ROOT_DIR} + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + composer install --no-progress --no-interaction; + " + lint: + image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + user: ${HOST_UID} + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro + working_dir: ${ROOT_DIR} + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + find . -name \\*.php ! -path "./.Build/\\*" ! -path "./public/\\*" -print0 | xargs -0 -n1 -P4 php -n -c /etc/php/cli-no-xdebug/php.ini -l >/dev/null + " + + unit: + image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + user: ${HOST_UID} + volumes: + - ${ROOT_DIR}:${ROOT_DIR} + - ${HOST_HOME}:${HOST_HOME} + - /etc/passwd:/etc/passwd:ro + - /etc/group:/etc/group:ro + working_dir: ${ROOT_DIR}/.Build + command: > + /bin/sh -c " + if [ ${SCRIPT_VERBOSE} -eq 1 ]; then + set -x + fi + php -v | grep '^PHP'; + cd ..; + php -n -c /etc/php/cli-no-xdebug/php.ini \ + .Build/bin/phpunit Tests/Unit/; + " \ No newline at end of file diff --git a/README.md b/README.md index 3b1eac47..ade9457c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -[![Build Status](https://travis-ci.org/TYPO3/testing-framework.svg?branch=master)](https://travis-ci.org/TYPO3/testing-framework) - # TYPO3 testing framework for core and extensions A straight and slim set of classes and configuration to test TYPO3 extensions. This framework is From 1925b38cebc0335f97989e91dfb93ebb61d0bc13 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 10 Feb 2021 19:20:08 +0100 Subject: [PATCH 24/31] [BUGFIX] Deterministic sorting it test splitter --- Resources/Core/Build/Scripts/splitAcceptanceTests.php | 1 + Resources/Core/Build/Scripts/splitFunctionalTests.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Resources/Core/Build/Scripts/splitAcceptanceTests.php b/Resources/Core/Build/Scripts/splitAcceptanceTests.php index 9128ccdf..888ae2d2 100755 --- a/Resources/Core/Build/Scripts/splitAcceptanceTests.php +++ b/Resources/Core/Build/Scripts/splitAcceptanceTests.php @@ -82,6 +82,7 @@ public function execute() ->files() ->in(__DIR__ . '/../../../../../../../typo3/sysext/core/Tests/Acceptance/Backend') ->name('/Cest\.php$/') + ->sortByName() ; $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); diff --git a/Resources/Core/Build/Scripts/splitFunctionalTests.php b/Resources/Core/Build/Scripts/splitFunctionalTests.php index 576d3ac4..bc999a3c 100755 --- a/Resources/Core/Build/Scripts/splitFunctionalTests.php +++ b/Resources/Core/Build/Scripts/splitFunctionalTests.php @@ -82,6 +82,7 @@ public function execute() ->files() ->in(__DIR__ . '/../../../../../../../typo3/sysext/*/Tests/Functional') ->name('/Test\.php$/') + ->sortByName() ; $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7); From 06ea0233066560b5fc6f5a764357100e8640c5b1 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Wed, 17 Feb 2021 23:45:21 +0100 Subject: [PATCH 25/31] [TASK] Switch to typo3/core-testing-* images for testing --- Build/testing-docker/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index 4236a978..d880fdf1 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -1,7 +1,7 @@ version: '2.3' services: composer_install: - image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest user: ${HOST_UID} volumes: - ${ROOT_DIR}:${ROOT_DIR} @@ -18,7 +18,7 @@ services: composer install --no-progress --no-interaction; " lint: - image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest user: ${HOST_UID} volumes: - ${ROOT_DIR}:${ROOT_DIR} @@ -35,7 +35,7 @@ services: " unit: - image: typo3gmbh/${DOCKER_PHP_IMAGE}:latest + image: typo3/core-testing-${DOCKER_PHP_IMAGE}:latest user: ${HOST_UID} volumes: - ${ROOT_DIR}:${ROOT_DIR} From ca6e2c26414da3770e1a7b2ddbf4ed96ed6afe93 Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Sun, 28 Feb 2021 00:46:36 +0100 Subject: [PATCH 26/31] [TASK] Adapt to xdebug 3 in test setup --- Build/testing-docker/docker-compose.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Build/testing-docker/docker-compose.yml b/Build/testing-docker/docker-compose.yml index d880fdf1..df3f2e19 100644 --- a/Build/testing-docker/docker-compose.yml +++ b/Build/testing-docker/docker-compose.yml @@ -31,7 +31,7 @@ services: set -x fi php -v | grep '^PHP'; - find . -name \\*.php ! -path "./.Build/\\*" ! -path "./public/\\*" -print0 | xargs -0 -n1 -P4 php -n -c /etc/php/cli-no-xdebug/php.ini -l >/dev/null + find . -name \\*.php ! -path "./.Build/\\*" ! -path "./public/\\*" -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null " unit: @@ -50,6 +50,5 @@ services: fi php -v | grep '^PHP'; cd ..; - php -n -c /etc/php/cli-no-xdebug/php.ini \ - .Build/bin/phpunit Tests/Unit/; - " \ No newline at end of file + php -dxdebug.mode=off .Build/bin/phpunit Tests/Unit/; + " From 2da9734946da93c62e09a053bf4dfb5ebb5b4b57 Mon Sep 17 00:00:00 2001 From: Maik Kempe Date: Wed, 30 Dec 2020 12:11:04 +0100 Subject: [PATCH 27/31] [BUGFIX] Backported #189 to `9` branch. --- .../Core/Functional/Framework/Frontend/RequestBootstrap.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php index a9eabb7c..57b488e4 100644 --- a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php +++ b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php @@ -60,8 +60,7 @@ public function __construct(string $documentRoot, array $requestArguments = null private function initialize() { - $directory = dirname(realpath($this->documentRoot . '/index.php')); - $this->classLoader = require_once $directory . '/vendor/autoload.php'; + $this->classLoader = require_once __DIR__ . '/../../../../../../../autoload.php'; } /** From f707b0a2ee33ad276e18db19352f7159279edace Mon Sep 17 00:00:00 2001 From: Maik Kempe Date: Wed, 30 Dec 2020 17:25:41 +0100 Subject: [PATCH 28/31] [BUGFIX] Backported #190 to `9` branch. --- .../Core/Functional/Framework/Frontend/RequestBootstrap.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php index 57b488e4..8217fa12 100644 --- a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php +++ b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php @@ -159,6 +159,10 @@ public function executeAndOutput() \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_FE ); $container = \TYPO3\CMS\Core\Core\Bootstrap::init($this->classLoader); + if (\TYPO3\CMS\Core\Core\Environment::isComposerMode() && + \TYPO3\CMS\Core\Core\ClassLoadingInformation::isClassLoadingInformationAvailable()) { + \TYPO3\CMS\Core\Core\ClassLoadingInformation::registerClassLoadingInformation(); + } ArrayUtility::mergeRecursiveWithOverrule( $GLOBALS, $this->context->getGlobalSettings() ?? [] From 37734b9c6aa4df2f5f1953981751ec4d8703b683 Mon Sep 17 00:00:00 2001 From: Alexander Nitsche Date: Fri, 6 Aug 2021 13:10:52 +0200 Subject: [PATCH 29/31] [TASK] Support firefox headless (#252) --- Classes/Core/Acceptance/Helper/Acceptance.php | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Classes/Core/Acceptance/Helper/Acceptance.php b/Classes/Core/Acceptance/Helper/Acceptance.php index 403dc7f9..a852a809 100644 --- a/Classes/Core/Acceptance/Helper/Acceptance.php +++ b/Classes/Core/Acceptance/Helper/Acceptance.php @@ -16,6 +16,7 @@ */ use Codeception\Module; +use Codeception\Module\WebDriver; use Codeception\Step; use Codeception\TestInterface; @@ -54,7 +55,24 @@ public function _beforeStep(Step $step) */ public function _afterStep(Step $step) { - $this->assertEmptyBrowserConsole(); + 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'; } public function allowBrowserError(string $pattern): void @@ -119,4 +137,12 @@ protected function isJSError($logEntryLevel, $message) { return $logEntryLevel === 'SEVERE' && strpos($message, 'ERR_PROXY_CONNECTION_FAILED') === false; } + + /** + * @return WebDriver + */ + protected function getWebDriver() + { + return $this->getModule('WebDriver'); + } } From e0e95753e265a072be7f0d0fadc1224de86ebab6 Mon Sep 17 00:00:00 2001 From: Alexander Nitsche Date: Thu, 29 Jul 2021 14:58:41 +0200 Subject: [PATCH 30/31] [BUGFIX] Support switching of TYPO3 backend user in Login The `$role` parameter of the `useExistingSession($role)` action was ignored if another backend user was already logged in. In this example ``` $I->useExistingSession('admin'); $I->useExistingSession('editor'); ``` the user "admin" was not changed to "editor" but the user "admin" remained logged in. With this patch, the action ensures that the requested user is logged in. --- Classes/Core/Acceptance/Helper/Login.php | 127 +++++++++++++++++------ 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/Classes/Core/Acceptance/Helper/Login.php b/Classes/Core/Acceptance/Helper/Login.php index 60578b4e..69b86c6a 100644 --- a/Classes/Core/Acceptance/Helper/Login.php +++ b/Classes/Core/Acceptance/Helper/Login.php @@ -17,9 +17,11 @@ use Codeception\Exception\ConfigurationException; use Codeception\Module; +use Codeception\Module\WebDriver; +use Codeception\Util\Locator; /** - * Helper class to log in backend users and load backend + * Helper class to log in backend users and load backend. */ class Login extends Module { @@ -31,48 +33,107 @@ class Login extends Module ]; /** - * Set a session cookie and load backend index.php + * Set a backend user session cookie and load the backend index.php. * - * @param string $role + * 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. * @throws ConfigurationException */ public function useExistingSession($role = '') { - /** @var Module\WebDriver $wd */ - $wd = $this->getModule('WebDriver'); - $wd->amOnPage('/typo3/index.php'); - $wd->waitForElement('body[data-typo3-login-ready]'); - - $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]; + $webDriver = $this->getWebDriver(); + + $newUserSessionId = $this->getUserSessionIdByRole($role); + + $hasSession = $this->_loadSession(); + if ($hasSession && $newUserSessionId !== '' && $newUserSessionId !== $this->getUserSessionId()) { + $this->_deleteSession(); + $hasSession = false; } - // @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) { + if (!$hasSession) { + $webDriver->amOnPage('/typo3/index.php'); + $webDriver->waitForElement('body[data-typo3-login-ready]'); + $this->_createSession($newUserSessionId); } - 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. + $webDriver->amOnPage('/typo3/index.php'); + + // Ensure main content frame is fully loaded, otherwise there are load-race-conditions .. + $webDriver->waitForElement('iframe[name="list_frame"]'); + $webDriver->switchToIFrame('list_frame'); + $webDriver->waitForElement(Locator::firstElement('div.module')); + // .. and switch back to main frame. + $webDriver->switchToIFrame(); + } + + /** + * @param string $role + * @return string + * @throws ConfigurationException + */ + protected function getUserSessionIdByRole($role) + { + if (empty($role)) { + return ''; } - // reload the page to have a logged in backend - $wd->amOnPage('/typo3/index.php'); + 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); + } - // 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(); + return $this->_getConfig('sessions')[$role]; + } + + /** + * @return bool + */ + public function _loadSession() + { + return $this->getWebDriver()->loadSessionSnapshot('login'); + } + + public function _deleteSession() + { + $webDriver = $this->getWebDriver(); + $webDriver->resetCookie('be_typo_user'); + $webDriver->resetCookie('be_lastLoginProvider'); + $webDriver->deleteSessionSnapshot('login'); + } + + /** + * @param string $userSessionId + */ + public function _createSession($userSessionId) + { + $webDriver = $this->getWebDriver(); + $webDriver->setCookie('be_typo_user', $userSessionId); + $webDriver->setCookie('be_lastLoginProvider', '1433416747'); + $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'); } } From 7a49adb3b065bf51b99621c238c0a1a509fe1f3b Mon Sep 17 00:00:00 2001 From: Alexander Nitsche Date: Thu, 29 Jul 2021 16:38:16 +0200 Subject: [PATCH 31/31] [BUGFIX] Delete false cookies "be_typo_user" and "be_lastLoginProvider" The "be_typo_user" and "be_lastLoginProvider" cookies may already be set in the bootstrapping process of the acceptance tests. They pose a problem because they are created with a different cookie path that still takes precedence over the cookies set in the method Login::_createSession. The cookies that already exist are therefore deleted as a precaution. --- Classes/Core/Acceptance/Helper/Login.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Classes/Core/Acceptance/Helper/Login.php b/Classes/Core/Acceptance/Helper/Login.php index 69b86c6a..138c1e8a 100644 --- a/Classes/Core/Acceptance/Helper/Login.php +++ b/Classes/Core/Acceptance/Helper/Login.php @@ -109,11 +109,18 @@ public function _deleteSession() } /** + * Note: The "be_typo_user" and "be_lastLoginProvider" cookies may already be set in the bootstrapping process of + * the acceptance tests. They pose a problem because they are created with a different cookie path that still takes + * precedence over the cookies set in this method. The cookies that already exist are therefore deleted as a + * precaution. + * * @param string $userSessionId */ public function _createSession($userSessionId) { $webDriver = $this->getWebDriver(); + $webDriver->resetCookie('be_typo_user'); + $webDriver->resetCookie('be_lastLoginProvider'); $webDriver->setCookie('be_typo_user', $userSessionId); $webDriver->setCookie('be_lastLoginProvider', '1433416747'); $webDriver->saveSessionSnapshot('login');