From 1a704055386fd7d14038c14576fad0cec79aeb54 Mon Sep 17 00:00:00 2001 From: ochorocho Date: Fri, 29 Apr 2022 14:41:32 +0200 Subject: [PATCH] [FEATURE] Add command 'make:acceptance' The command 'make:acceptance' has been added to create basic files and config to run acceptance tests. --- Build/php-cs-fixer.php | 2 +- Classes/Command/AcceptanceTestsCommand.php | 168 ++++++++++++++++++ Classes/PackageResolver.php | 5 + Configuration/Services.yaml | 7 + .../Acceptance/Application.suite.yml | 23 +++ .../Acceptance/Application/ExampleCest.php | 30 ++++ .../Acceptance/Fixtures/be_sessions.xml | 19 ++ .../Acceptance/Fixtures/pages.xml | 24 +++ .../Acceptance/Fixtures/sys_template.xml | 16 ++ .../Acceptance/Support/ApplicationTester.php | 17 ++ .../Extension/ExtensionEnvironment.php | 52 ++++++ .../AcceptanceTests/codeception.yml | 13 ++ 12 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 Classes/Command/AcceptanceTestsCommand.php create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application.suite.yml create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application/ExampleCest.php create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/be_sessions.xml create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/pages.xml create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/sys_template.xml create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Support/ApplicationTester.php create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Support/Extension/ExtensionEnvironment.php create mode 100644 Resources/Private/CodeTemplates/AcceptanceTests/codeception.yml diff --git a/Build/php-cs-fixer.php b/Build/php-cs-fixer.php index 4493c7a..8ec8474 100644 --- a/Build/php-cs-fixer.php +++ b/Build/php-cs-fixer.php @@ -1,5 +1,5 @@ getFinder()->exclude(['var']); +$config->getFinder()->exclude(['var', 'Resources/Private/CodeTemplates']); return $config; diff --git a/Classes/Command/AcceptanceTestsCommand.php b/Classes/Command/AcceptanceTestsCommand.php new file mode 100644 index 0000000..d09e0a3 --- /dev/null +++ b/Classes/Command/AcceptanceTestsCommand.php @@ -0,0 +1,168 @@ +setDescription('Prepare extensions to run acceptance tests'); + $this->filesystem = GeneralUtility::makeInstance(Filesystem::class); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $this->io = new SymfonyStyle($input, $output); + $packages = $this->getPackageResolver()->getPackageManager()->getActivePackages(); + + $choices = array_reduce($packages, function ($result, PackageInterface $package) { + if ($package->getPackageMetaData()->getPackageType() === 'typo3-cms-extension') { + $packageKey = $package->getPackageKey(); + $result[$packageKey] = $packageKey; + } + return $result; + }, []); + + $selectedPackageName = $this->io->choice('Select a package to create acceptance tests for', $choices); + $this->package = $this->getPackageResolver()->resolvePackage($selectedPackageName); + + $packageKey = $this->package->getPackageKey(); + $this->io->writeln('Selected package: ' . $packageKey); + $finder = GeneralUtility::makeInstance(Finder::class); + + $targetPackage = $this->package->getPackagePath(); + $codeTemplatePath = '/Resources/Private/CodeTemplates/AcceptanceTests'; + $templatePath = $this->getPackageResolver()->resolvePackage('b13/make')->getPackagePath() . $codeTemplatePath; + + $this->filesystem->mkdir([ + $targetPackage . '/Tests/Acceptance', + $targetPackage . '/Tests/Acceptance/Fixtures', + $targetPackage . '/Tests/Acceptance/Application', + $targetPackage . '/Tests/Acceptance/Support/Extension' + ]); + + // Create public folder which is required for e.g. acceptance tests to work + $publicFolderPath = $targetPackage . '/Resources/Public'; + if (!is_dir($publicFolderPath)) { + $createPublic = $this->io->confirm('Resource/Public is necessary e.g. for acceptance tests. Do you want to create it now?', true); + if ($createPublic) { + $this->filesystem->mkdir([$publicFolderPath]); + // Ensure the folder will be detected by git and committed + $this->filesystem->touch([$publicFolderPath . '/.gitkeep']); + } + } + + $files = $finder->in($templatePath)->files(); + + foreach ($files as $file) { + $target = $targetPackage . 'Tests' . explode('AcceptanceTests', $file->getRealPath())[1]; + + if (!is_file($target)) { + $content = $file->getContents(); + $this->substituteMarkersAndSave($content, $target); + } else { + $overwrite = $this->io->confirm('File exists ' . basename($target) . '. Do you want to overwrite it?'); + if ($overwrite) { + $content = $file->getContents(); + $this->substituteMarkersAndSave($content, $target); + } + } + } + + if ($this->updateComposerFile($targetPackage)) { + $this->io->writeln('Updated composer.json for EXT:' . $packageKey . ''); + } else { + $this->io->writeln('Failed to update composer.json for EXT:' . $packageKey . ''); + } + + return 0; + } + + /** + * Extend/prepare composer.json of the extension + * for acceptance tests + * + * @throws \JsonException + */ + protected function updateComposerFile(string $packagePath): bool + { + $composerFile = $packagePath . '/composer.json'; + $composerJson = file_get_contents($composerFile); + $composer = json_decode($composerJson, true, 512, JSON_THROW_ON_ERROR); + $namespace = rtrim($this->getNamespace(), '\\'); + + // @todo: if a value already exists ask for permission to change it?! + $composer['require-dev']['codeception/codeception'] = '^4.1'; + $composer['require-dev']['codeception/module-asserts'] = '^1.2'; + $composer['require-dev']['codeception/module-webdriver'] = '^1.1'; + $composer['require-dev']['typo3/testing-framework'] = '^6.16.2'; + + $composer['autoload-dev']['psr-4'][$namespace . '\\Tests\\'] = 'Tests/'; + + $composer['config']['vendor-dir'] = '.Build/vendor'; + $composer['config']['bin-dir'] = '.Build/bin'; + + $composer['extra']['typo3/cms']['app-dir'] = '.Build'; + $composer['extra']['typo3/cms']['web-dir'] = '.Build/Web'; + + return GeneralUtility::writeFile($composerFile, json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), true); + } + + /** + * Substitute marker values and save file to extension_key/Tests/ + * + * @param string $content + * @param string $target + */ + protected function substituteMarkersAndSave(string $content, string $target): void + { + $markerService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); + $templateContent = $markerService->substituteMarker($content, '{{NAMESPACE}}', rtrim($this->getNamespace(), '\\')); + $templateContent = $markerService->substituteMarker($templateContent, '{{EXTENSION_KEY}}', $this->package->getPackageKey()); + + try { + $this->filesystem->dumpFile($target, $templateContent); + } catch (IOException $exception) { + $this->io->writeln('Failed to save file in ' . $target . ''); + } + } + + protected function getPackageResolver(): PackageResolver + { + return GeneralUtility::makeInstance(PackageResolver::class); + } + + protected function getNamespace(): string + { + return (string)key((array)($this->package->getValueFromComposerManifest('autoload')->{'psr-4'} ?? [])); + } +} diff --git a/Classes/PackageResolver.php b/Classes/PackageResolver.php index e8d4c50..6a97697 100644 --- a/Classes/PackageResolver.php +++ b/Classes/PackageResolver.php @@ -37,4 +37,9 @@ public function resolvePackage(string $extensionkey): ?PackageInterface return null; } } + + public function getPackageManager(): PackageManager + { + return $this->packageManager; + } } diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index a61c425..894d8b1 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -44,3 +44,10 @@ services: command: 'make:middleware' description: 'Create a PSR-15 middleware' schedulable: false + + B13\Make\Command\AcceptanceTestsCommand: + tags: + - name: 'console.command' + command: 'make:acceptance' + description: 'Create files required to run acceptance tests' + schedulable: false diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application.suite.yml b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application.suite.yml new file mode 100644 index 0000000..810a6cd --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application.suite.yml @@ -0,0 +1,23 @@ +class_name: ApplicationTester +modules: + enabled: + - WebDriver: + url: http://web:8000/typo3temp/var/tests/acceptance + browser: chrome + wait: 1 + host: chrome + window_size: 1400x800 + - \TYPO3\TestingFramework\Core\Acceptance\Helper\Acceptance + - \TYPO3\TestingFramework\Core\Acceptance\Helper\Login: + sessions: + # This sessions must exist in the database fixture to get a logged in state. + editor: ff83dfd81e20b34c27d3e97771a4525a + admin: 886526ce72b86870739cc41991144ec1 + - Asserts + +extensions: + enabled: + - {{NAMESPACE}}\Tests\Acceptance\Support\Extension\ExtensionEnvironment + +groups: + AcceptanceTests-Job-*: AcceptanceTests-Job-* diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application/ExampleCest.php b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application/ExampleCest.php new file mode 100644 index 0000000..5af86a6 --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Application/ExampleCest.php @@ -0,0 +1,30 @@ +useExistingSession('admin'); + $I->switchToMainFrame(); + } + + /** + * @param ApplicationTester $I + * @throws \Exception + */ + public function seeTestExample(ApplicationTester $I): void + { + $I->click('Page'); + $I->switchToContentFrame(); + $I->see('Web>Page module', '.callout-info'); + } +} diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/be_sessions.xml b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/be_sessions.xml new file mode 100644 index 0000000..4160903 --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/be_sessions.xml @@ -0,0 +1,19 @@ + + + + + 9869d429fc72742a476d5073d006d45dfb732962d9c024423efafef537e1c5bd + [DISABLED] + 1 + 1777777777 + + + + + f4c02f70058e79a8e7b523a266d4291007deacba6b2ca2536dd72d2fbb23696a + [DISABLED] + 2 + 1777777777 + + + diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/pages.xml b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/pages.xml new file mode 100644 index 0000000..43a50f7 --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/pages.xml @@ -0,0 +1,24 @@ + + + + + 1 + 0 + Home + + 1 + 0 + 1 + 15 + + + 2 + 1 + 128 + Subpage + subpage + 0 + 1 + 15 + + diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/sys_template.xml b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/sys_template.xml new file mode 100644 index 0000000..7d9b169 --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Fixtures/sys_template.xml @@ -0,0 +1,16 @@ + + + + 1 + 1 + Root Template + 0 + 1 + 3 + +page = PAGE +page.10 = TEXT +page.10.data = page:title + + + diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Support/ApplicationTester.php b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Support/ApplicationTester.php new file mode 100644 index 0000000..2704bd8 --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/Acceptance/Support/ApplicationTester.php @@ -0,0 +1,17 @@ + [ + 'core', + 'extbase', + 'filelist', + 'setup', + 'frontend', + 'fluid', + 'recordlist', + 'backend', + 'install', + ], + 'testExtensionsToLoad' => [ + 'typo3conf/ext/{{EXTENSION_KEY}}', + ], + 'xmlDatabaseFixtures' => [ + 'PACKAGE:typo3/testing-framework/Resources/Core/Acceptance/Fixtures/be_users.xml', + 'PACKAGE:../Web/typo3conf/ext/{{EXTENSION_KEY}}/Tests/Acceptance/Fixtures/pages.xml', + 'PACKAGE:../Web/typo3conf/ext/{{EXTENSION_KEY}}/Tests/Acceptance/Fixtures/be_sessions.xml', + 'PACKAGE:../Web/typo3conf/ext/{{EXTENSION_KEY}}/Tests/Acceptance/Fixtures/sys_template.xml', + ], + 'configurationToUseInTestInstance' => [ + 'MAIL' => [ + 'transport' => NullTransport::class, + ], + ], + + // Link files/folders required for your acceptance tests to run + // 'pathsToLinkInTestInstance' => [ + // 'typo3conf/ext/{{EXTENSION_KEY}}/Tests/Acceptance/Fixtures/sites' => 'typo3conf/sites', + // ] + ]; +} diff --git a/Resources/Private/CodeTemplates/AcceptanceTests/codeception.yml b/Resources/Private/CodeTemplates/AcceptanceTests/codeception.yml new file mode 100644 index 0000000..6509bba --- /dev/null +++ b/Resources/Private/CodeTemplates/AcceptanceTests/codeception.yml @@ -0,0 +1,13 @@ +namespace: {{NAMESPACE}}\Tests\Acceptance\Support +paths: + tests: Acceptance + data: . + log: ../.Build/Web/typo3temp/var/tests/AcceptanceReports + support: Acceptance/Support +settings: + colors: true + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed + - Codeception\Extension\Recorder