diff --git a/CHANGELOG.md b/CHANGELOG.md index 54189d745..84cc2cfad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ Magento Functional Testing Framework Changelog ================================================ +2.5.2 +----- + +* Traceability + * Allure report enhanced to display file path of tests. +* Maintainability + * Added support to read MFTF test entities from `dev/tests/acceptance/tests/functional///*` + * Removed path deprecation warning from `ModuleResolver`. + * Refactored problem methods to reduce cyclomatic complexity. + +### Fixes +* Fixed issue with builds due to absence of AcceptanceTester class. + +### GitHub Issues/Pull requests: +* [#348](https://github.com/magento/magento2-functional-testing-framework/pull/348) -- executeInSelenium command fixed to prevent escaping double quotes. 2.5.1 ----- diff --git a/bin/mftf b/bin/mftf index b978d99ee..355a851c8 100755 --- a/bin/mftf +++ b/bin/mftf @@ -29,7 +29,7 @@ try { try { $application = new Symfony\Component\Console\Application(); $application->setName('Magento Functional Testing Framework CLI'); - $application->setVersion('2.5.1'); + $application->setVersion('2.5.2'); /** @var \Magento\FunctionalTestingFramework\Console\CommandListInterface $commandList */ $commandList = new \Magento\FunctionalTestingFramework\Console\CommandList; foreach ($commandList->getCommands() as $command) { diff --git a/composer.json b/composer.json index f734d29cc..bc2592d97 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.5.1", + "version": "2.5.2", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { @@ -13,6 +13,7 @@ "ext-curl": "*", "allure-framework/allure-codeception": "~1.3.0", "codeception/codeception": "~2.3.4 || ~2.4.0 ", + "composer/composer": "^1.4", "consolidation/robo": "^1.0.0", "csharpru/vault-php": "~3.5.3", "csharpru/vault-php-guzzle6-transport": "^2.0", diff --git a/composer.lock b/composer.lock index a92b8b788..2439f83ef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eed8abcb8179f4cf41aa0780ea5d9f8e", + "content-hash": "5d566c152481274493082bdabb128c70", "packages": [ { "name": "allure-framework/allure-codeception", @@ -250,7 +250,7 @@ { "name": "Tobias Nyholm", "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/nyholm" + "homepage": "https://github.com/Nyholm" } ], "description": "Library of all the php-cache adapters", @@ -388,6 +388,308 @@ "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", "time": "2018-05-17T09:31:08+00:00" }, + { + "name": "composer/ca-bundle", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "reference": "10bb96592168a0f8e8f6dcde3532d9fa50b0b527", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2019-08-30T08:44:50+00:00" + }, + { + "name": "composer/composer", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.1", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0" + }, + "conflict": { + "symfony/console": "2.8.38" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7", + "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2019-08-02T18:55:33+00:00" + }, + { + "name": "composer/semver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5 || ^5.0.5", + "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "time": "2019-03-19T17:25:45+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "time": "2019-07-29T10:31:59+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.3.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2019-05-27T17:52:04+00:00" + }, { "name": "consolidation/annotated-command", "version": "2.9.1", @@ -1859,6 +2161,72 @@ ], "time": "2019-04-17T08:12:16+00:00" }, + { + "name": "justinrainbow/json-schema", + "version": "5.2.9", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "44c6787311242a979fa15c704327c20e7221a0e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/44c6787311242a979fa15c704327c20e7221a0e4", + "reference": "44c6787311242a979fa15c704327c20e7221a0e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2019-09-25T14:49:45+00:00" + }, { "name": "league/container", "version": "2.4.1", @@ -3960,6 +4328,99 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "seld/jsonlint", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/d15f59a67ff805a44c50ea0516d2341740f81a38", + "reference": "d15f59a67ff805a44c50ea0516d2341740f81a38", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "time": "2018-01-24T12:46:19+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phra" + ], + "time": "2015-10-13T18:44:15+00:00" + }, { "name": "symfony/browser-kit", "version": "v3.4.18", diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php new file mode 100644 index 000000000..e4977184f --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerInstallTest.php @@ -0,0 +1,62 @@ +composer = new ComposerInstall($composerJson); + } + + /** + * Test isMftfTestPackage() + */ + public function testIsMftfTestPackage() + { + $this->assertTrue($this->composer->isMftfTestPackage('magento/module2-functional-test')); + } + + /** + * Test isMagentoPackage() + */ + public function testIsMagentoPackage() + { + $this->assertTrue($this->composer->isMagentoPackage('magento/module-authorization')); + } + + /** + * Test isInstalledPackageOfType() + */ + public function testIsInstalledPackageOfType() + { + $this->assertTrue($this->composer->isInstalledPackageOfType('composer/composer', 'library')); + } + + /** + * Test getInstalledTestPackages() + */ + public function testGetInstalledTestPackages() + { + $output = $this->composer->getInstalledTestPackages(); + $this->assertCount(1, $output); + $this->assertArrayHasKey('magento/module2-functional-test', $output); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php new file mode 100644 index 000000000..4802f0c33 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/ComposerPackageTest.php @@ -0,0 +1,170 @@ +composer = new ComposerPackage($composerJson); + } + + /** + * Test getName() + */ + public function testGetName() + { + $expected = 'magento/module2-functional-test'; + $this->assertEquals($expected, $this->composer->getName()); + } + + /** + * Test getType() + */ + public function testGetType() + { + $expected = 'magento2-functional-test-module'; + $this->assertEquals($expected, $this->composer->getType()); + } + + /** + * Test getVersion() + */ + public function testGetVersion() + { + $expected = '1.0.0'; + $this->assertEquals($expected, $this->composer->getVersion()); + } + + /** + * Test getDescription() + */ + public function testGetDescription() + { + $expected = 'MFTF tests for magento'; + $this->assertEquals($expected, $this->composer->getDescription()); + } + + /** + * Test getRequires() + */ + public function testGetRequires() + { + $expected = 'magento/magento2-functional-testing-framework'; + $output = $this->composer->getRequires(); + $this->assertCount(1, $output); + $this->assertArrayHasKey($expected, $output); + } + + /** + * Test getDevRequires() + */ + public function testGetDevRequires() + { + $expected = ['phpunit/phpunit']; + $this->assertEquals($expected, array_keys($this->composer->getDevRequires())); + } + + /** + * Test getSuggests() + */ + public function testGetSuggests() + { + $expected = [ + 'magento/module-one', + 'magento/module-module-x', + 'magento/module-two', + 'magento/module-module-y', + 'magento/module-module-z', + 'magento/module-three', + 'magento/module-four' + ]; + $this->assertEquals($expected, array_keys($this->composer->getSuggests())); + } + + /** + * Test getSuggestedMagentoModules() + */ + public function testGetSuggestedMagentoModules() + { + $expected = [ + 'Magento_ModuleX', + 'Magento_ModuleY', + 'Magento_ModuleZ' + ]; + $this->assertEquals($expected, $this->composer->getSuggestedMagentoModules()); + } + + /** + * Test isMftfTestPackage() + */ + public function testIsMftfTestPackage() + { + $this->assertTrue($this->composer->isMftfTestPackage()); + } + + /** + * Test getRequiresForPackage() + */ + public function testGetRequiresForPackage() + { + $expected = [ + 'php', + 'ext-curl', + 'allure-framework/allure-codeception', + 'codeception/codeception', + 'consolidation/robo', + 'csharpru/vault-php', + 'csharpru/vault-php-guzzle6-transport', + 'flow/jsonpath', + 'fzaninotto/faker', + 'monolog/monolog', + 'mustache/mustache', + 'symfony/process', + 'vlucas/phpdotenv' + ]; + $this->assertEquals( + $expected, + array_keys($this->composer->getRequiresForPackage('magento/magento2-functional-testing-framework', '2.5.0')) + ); + } + + /** + * Test isPackageRequiredInComposerJson() + */ + public function testIsPackageRequiredInComposerJson() + { + $this->assertTrue( + $this->composer->isPackageRequiredInComposerJson('magento/magento2-functional-testing-framework') + ); + } + + /** + * Test getRootPackage() + */ + public function testGetRootPackage() + { + $this->assertInstanceOf( + RootPackage::class, + $this->composer->getRootPackage() + ); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json new file mode 100644 index 000000000..c21401af7 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.json @@ -0,0 +1,21 @@ +{ + "name": "magento/module2-functional-test", + "type": "magento2-functional-test-module", + "description": "MFTF tests for magento", + "version": "1.0.0", + "require": { + "magento/magento2-functional-testing-framework": "^2.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.5.0 || ~7.0.0" + }, + "suggest": { + "magento/module-one": "name: Magento_ModuleY, version: ~100.0.0", + "magento/module-module-x": " type: magento2-module ,name: Magento_ModuleX, version: ~100.0.0", + "magento/module-two": "*", + "magento/module-module-y": "type: magento2-module, name:Magento_ModuleY,version:~100.0.0", + "magento/module-module-z": " type:magento2-module, name: Magento_ModuleZ , version: ~100.0.0", + "magento/module-three": "type: magento2-module, ~100.0.0", + "magento/module-four": "some suggestions" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock new file mode 100644 index 000000000..4493b5615 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/composer.lock @@ -0,0 +1,149 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f40a2fa7e19322bd961f212ad270b0d7", + "packages": [ + { + "name": "magento/module-authorization", + "version": "100.3.2", + "dist": { + "type": "zip", + "url": "https://repo.magento.com/archives/magento/module-authorization/magento-module-authorization-100.3.2.0.zip", + "shasum": "c17ed45c3bffca3bf704d2f4031069ed36b70273" + }, + "require": { + "magento/framework": "102.0.*", + "magento/module-backend": "101.0.*", + "php": "~7.1.3||~7.2.0" + }, + "type": "magento2-module", + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Authorization\\": "" + } + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Authorization module provides access to Magento ACL functionality." + }, + { + "name": "composer/composer", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/semver": "^1.0", + "composer/spdx-licenses": "^1.2", + "composer/xdebug-handler": "^1.1", + "justinrainbow/json-schema": "^3.0 || ^4.0 || ^5.0", + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.0", + "symfony/console": "^2.7 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0" + }, + "conflict": { + "symfony/console": "2.8.38" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7", + "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "time": "2019-08-02T18:55:33+00:00" + }, + { + "name": "magento/module2-functional-test", + "require": { + "composer/composer": "*" + }, + "type": "magento2-functional-test-module", + "version": "1.0.0", + "suggest": { + "magento/module-one": "name: Magento_ModuleY, version: ~100.0.0", + "magento/module-module-x": " type: magento2-module ,name: Magento_ModuleX, version: ~100.0.0", + "magento/module-two": "*", + "magento/module-module-y": "type: magento2-module, name:Magento_ModuleY,version:~100.0.0", + "magento/module-module-z": " type:magento2-module, name: Magento_ModuleZ , version: ~100.0.0", + "magento/module-three": "type: magento2-module, ~100.0.0", + "magento/module-four": "some suggestions" + }, + "description": "magento module2-functional-test", + "keywords": [ + "mftf", + "test" + ], + "time": "2019-03-19T17:25:45+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json new file mode 100644 index 000000000..49e952ee9 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-31-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-a": "type: magento2-module, name: Magento_ModuleA, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json new file mode 100644 index 000000000..97737fca6 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir31/dir41/composer.json @@ -0,0 +1,11 @@ +{ + "name": "magento/module-3141-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-e": "type: magento2-module, name: Magento_ModuleE, version: ~100.0.0", + "magento/module-module-f": "type: magento2-module, name: Magento_ModuleF, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json new file mode 100644 index 000000000..dffbb3bee --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/composer.json @@ -0,0 +1,11 @@ +{ + "name": "magento/module-32-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-b": "type: magento2-module, name: Magento_ModuleB, version: ~100.0.0", + "magento/module-module-c": "type: magento2-module, name: Magento_ModuleC, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json new file mode 100644 index 000000000..f766c226c --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir41/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-3241-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-g": "type: magento2-module, name: Magento_ModuleG, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json new file mode 100644 index 000000000..e51f36515 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Composer/_files/dir1/dir2/dir32/dir42/composer.json @@ -0,0 +1,10 @@ +{ + "name": "magento/module-3242-functional-test", + "type": "magento2-functional-test-module", + "require": { + "magento/magento2-functional-testing-framework": "*" + }, + "suggest": { + "magento/module-module-h": "type: magento2-module, name: Magento_ModuleH, version: ~100.0.0" + } +} \ No newline at end of file diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php index 99125a9e3..5bf29507c 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php @@ -15,9 +15,16 @@ use Magento\FunctionalTestingFramework\Util\MagentoTestCase; use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; +use tests\unit\Util\MockModuleResolverBuilder; class SuiteObjectHandlerTest extends MagentoTestCase { + public function setUp() + { + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); + } + /** * Tests basic parsing and accesors of suite object and suite object supporting classes */ diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php index eb6298afc..29158b0f5 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/SuiteGeneratorTest.php @@ -20,10 +20,10 @@ use tests\unit\Util\SuiteDataArrayBuilder; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; +use tests\unit\Util\MockModuleResolverBuilder; class SuiteGeneratorTest extends MagentoTestCase { - /** * Setup entry append and clear for Suite Generator */ @@ -42,6 +42,8 @@ public static function setUpBeforeClass() public function setUp() { TestLoggingUtil::getInstance()->setMockLoggingUtil(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index a94c41ae4..39e80d556 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -8,7 +8,6 @@ use AspectMock\Test as AspectMock; -use Go\Aop\Aspect; use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; @@ -16,10 +15,10 @@ use Magento\FunctionalTestingFramework\Test\Objects\TestHookObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; -use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\MagentoTestCase; use tests\unit\Util\TestDataArrayBuilder; +use tests\unit\Util\MockModuleResolverBuilder; class TestObjectHandlerTest extends MagentoTestCase { @@ -40,10 +39,13 @@ public function testGetTestObject() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => $mockData]); // run object handler method $toh = TestObjectHandler::getInstance(); + $mockConfig = AspectMock::double(TestObjectHandler::class, ['initTestData' => false]); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); // perform asserts @@ -89,7 +91,8 @@ public function testGetTestObject() ["testActionInTest" => $expectedTestActionObject], [ 'features' => ['NO MODULE DETECTED'], - 'group' => ['test'] + 'group' => ['test'], + 'description' => ['

Test files

'] ], [ TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, @@ -130,6 +133,8 @@ public function testGetTestsByGroup() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => array_merge($includeTest, $excludeTest)]); // execute test method @@ -150,16 +155,20 @@ public function testGetTestsByGroup() public function testGetTestWithModuleName() { // set up Test Data - $moduleExpected = "SomeTestModule"; + $moduleExpected = "SomeModuleName"; + $moduleExpectedTest = $moduleExpected . "Test"; $filepath = DIRECTORY_SEPARATOR . - "user" . + "user" . DIRECTORY_SEPARATOR . "magento2ce" . DIRECTORY_SEPARATOR . "dev" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . "acceptance" . DIRECTORY_SEPARATOR . "tests" . DIRECTORY_SEPARATOR . - $moduleExpected . DIRECTORY_SEPARATOR . - "Tests" . DIRECTORY_SEPARATOR . + "functional" . DIRECTORY_SEPARATOR . + "Vendor" . DIRECTORY_SEPARATOR . + $moduleExpectedTest; + $file = $filepath . DIRECTORY_SEPARATOR . + "Test" . DIRECTORY_SEPARATOR . "text.xml"; // set up mock data $testDataArrayBuilder = new TestDataArrayBuilder(); @@ -169,8 +178,12 @@ public function testGetTestWithModuleName() ->withAfterHook() ->withBeforeHook() ->withTestActions() - ->withFileName($filepath) + ->withFileName($file) ->build(); + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(['Vendor_' . $moduleExpected => $filepath]); + $this->setMockParserOutput(['tests' => $mockData]); // Execute Test Method $toh = TestObjectHandler::getInstance(); @@ -197,6 +210,8 @@ public function testGetTestObjectWithInvalidExtends() ->withBeforeHook() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => $testOne]); $toh = TestObjectHandler::getInstance(); @@ -233,6 +248,8 @@ public function testGetAllTestObjectsWithInvalidExtends() ->withTestActions() ->build(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); $this->setMockParserOutput(['tests' => array_merge($testOne, $testTwo)]); $toh = TestObjectHandler::getInstance(); @@ -260,4 +277,14 @@ private function setMockParserOutput($data) ->make(); // bypass the private constructor AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); } + + /** + * After method functionality + * + * @return void + */ + public function tearDown() + { + AspectMock::clean(); + } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php index ce184e564..d86185b83 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ObjectExtensionUtilTest.php @@ -20,6 +20,7 @@ use PHPUnit\Framework\TestCase; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\TestLoggingUtil; +use tests\unit\Util\MockModuleResolverBuilder; class ObjectExtensionUtilTest extends TestCase { @@ -30,6 +31,8 @@ class ObjectExtensionUtilTest extends TestCase public function setUp() { TestLoggingUtil::getInstance()->setMockLoggingUtil(); + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php new file mode 100644 index 000000000..3173730b3 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ComposerModuleResolverTest.php @@ -0,0 +1,162 @@ +getComposerInstalledTestModules($composerJson); + $this->assertCount(1, $output); + $this->assertEquals($expected, array_pop($output)); + } + + /** + * Test getTestModulesFromPaths() + */ + public function testGetTestModulesFromPaths() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + $expected = [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' => [ + 'Magento_ModuleE', + 'Magento_ModuleF' + ], + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' => [ + 'Magento_ModuleG' + ], + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' => [ + 'Magento_ModuleH' + ], + ]; + + $composer = new ComposerModuleResolver(); + $output = $composer->getTestModulesFromPaths([$baseDir]); + $this->assertEquals($expected, $output); + } + + /** + * Test findComposerJsonFilesAtDepth() + * + * @dataProvider findComposerJsonFilesAtDepthDataProvider + * @param string $dir + * @param integer $depth + * @param array $expected + * @throws \ReflectionException + */ + public function testFindComposerJsonFilesAtDepth($dir, $depth, $expected) + { + $composer = new ComposerModuleResolver(); + $class = new ReflectionClass($composer); + $method = $class->getMethod('findComposerJsonFilesAtDepth'); + $method->setAccessible(true); + $output = $method->invoke($composer, $dir, $depth); + $this->assertEquals($expected, $output); + } + + /** + * Test findAllComposerJsonFiles() + * + * @dataProvider findAllComposerJsonFilesDataProvider + * @param string $dir + * @param array $expected + * @throws \ReflectionException + */ + public function testFindAllComposerJsonFiles($dir, $expected) + { + $composer = new ComposerModuleResolver(); + $class = new ReflectionClass($composer); + $method = $class->getMethod('findAllComposerJsonFiles'); + $method->setAccessible(true); + $output = $method->invoke($composer, $dir); + $this->assertEquals($expected, $output); + } + + /** + * Data provider for testFindComposerJsonFilesAtDepth() + * + * @return array + */ + public function findComposerJsonFilesAtDepthDataProvider() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + return [ + [ + $baseDir, + 0, + [ + $baseDir . DIRECTORY_SEPARATOR . 'composer.json' + ] + ], + [ + $baseDir, + 1, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'composer.json', + ] + ], + [ + $baseDir, + 2, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' . DIRECTORY_SEPARATOR + . 'composer.json', + ] + ] + ]; + } + + /** + * Data provider for testFindAllComposerJsonFiles() + * + * @return array + */ + public function findAllComposerJsonFilesDataProvider() + { + $baseDir = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Composer' . DIRECTORY_SEPARATOR . '_files' + . DIRECTORY_SEPARATOR . 'dir1' . DIRECTORY_SEPARATOR . 'dir2'; + return [ + [ + $baseDir, + [ + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir31' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir41' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'dir42' . DIRECTORY_SEPARATOR + . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'dir32' . DIRECTORY_SEPARATOR . 'composer.json', + $baseDir . DIRECTORY_SEPARATOR . 'composer.json', + ] + ] + ]; + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php index 5efa6384b..05bc99b89 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModulePathExtractorTest.php @@ -7,103 +7,128 @@ namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; use Magento\FunctionalTestingFramework\Util\ModulePathExtractor; -use PHPUnit\Framework\TestCase; +use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use tests\unit\Util\MockModuleResolverBuilder; -class ModulePathExtractorTest extends TestCase +class ModulePathExtractorTest extends MagentoTestCase { - const EXTENSION_PATH = "app" - . DIRECTORY_SEPARATOR - . "code" - . DIRECTORY_SEPARATOR - . "TestExtension" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "Mftf" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; - - const MAGENTO_PATH = "dev" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "acceptance" - . DIRECTORY_SEPARATOR - . "tests" - . DIRECTORY_SEPARATOR - . "functional" - . DIRECTORY_SEPARATOR - . "Magento" - . DIRECTORY_SEPARATOR - . "FunctionalTest" - . DIRECTORY_SEPARATOR - . "[Analytics]" - . DIRECTORY_SEPARATOR - . "Test" - . DIRECTORY_SEPARATOR - . "SomeText.xml"; + /** + * Mock test module paths + * + * @var array + */ + private $mockTestModulePaths = [ + 'Magento_ModuleA' => '/base/path/app/code/Magento/ModuleA/Test/Mftf', + 'VendorB_ModuleB' => '/base/path/app/code/VendorB/ModuleB/Test/Mftf', + 'Magento_ModuleC' => '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest', + 'VendorD_ModuleD' => '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest', + 'SomeModuleE' => '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE', + 'Magento_ModuleF' => '/base/path/vendor/magento/module-modulef/Test/Mftf', + 'VendorG_ModuleG' => '/base/path/vendor/vendorg/module-moduleg-test', + ]; + + /** + * Validate module for app/code path + * + * @throws \Exception + */ + public function testGetModuleAppCode() + { + $mockPath = '/base/path/app/code/Magento/ModuleA/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleA', $extractor->extractModuleName($mockPath)); + } /** - * Validate correct module is returned for dev/tests path + * Validate vendor for app/code path + * * @throws \Exception */ - public function testGetMagentoModule() + public function testGetVendorAppCode() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/app/code/VendorB/ModuleB/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorB', $extractor->getExtensionPath($mockPath)); } /** - * Validate correct module is returned for extension path + * Validate module for dev/tests path + * * @throws \Exception */ - public function testGetExtensionModule() + public function testGetModuleDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - '[Analytics]', - $modulePathExtractor->extractModuleName( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/Magento/ModuleCTest/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleC', $extractor->extractModuleName($mockPath)); } /** - * Validate Magento is returned for dev/tests/acceptance + * Validate vendor for dev/tests path + * * @throws \Exception */ - public function testMagentoModulePath() + public function testGetVendorDevTests() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'Magento', - $modulePathExtractor->getExtensionPath( - self::MAGENTO_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/VendorD/ModuleDTest/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorD', $extractor->getExtensionPath($mockPath)); } /** - * Validate correct extension path is returned + * Validate module with no _ + * * @throws \Exception */ - public function testExtensionModulePath() + public function testGetModule() { - $modulePathExtractor = new ModulePathExtractor(); - $this->assertEquals( - 'TestExtension', - $modulePathExtractor->getExtensionPath( - self::EXTENSION_PATH - ) - ); + $mockPath = '/base/path/dev/tests/acceptance/tests/functional/FunctionalTest/SomeModuleE/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('NO MODULE DETECTED', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate module for vendor/tests path + * + * @throws \Exception + */ + public function testGetModuleVendorDir() + { + $mockPath = '/base/path/vendor/magento/module-modulef/Test/Mftf/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('ModuleF', $extractor->extractModuleName($mockPath)); + } + + /** + * Validate vendor for vendor path + * + * @throws \Exception + */ + public function testGetVendorVendorDir() + { + $mockPath = '/base/path/vendor/vendorg/module-moduleg-test/Test/SomeTest.xml'; + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup($this->mockTestModulePaths); + $extractor = new ModulePathExtractor(); + $this->assertEquals('VendorG', $extractor->getExtensionPath($mockPath)); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php index 248704adc..07e1ee7fa 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/ModuleResolverTest.php @@ -16,6 +16,7 @@ use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Magento\FunctionalTestingFramework\Util\ModuleResolver; use Magento\FunctionalTestingFramework\Util\MagentoTestCase; +use PHPUnit\Runner\Exception; use tests\unit\Util\TestLoggingUtil; class ModuleResolverTest extends MagentoTestCase @@ -56,21 +57,33 @@ public function testGetModulePathsAlreadySet() */ public function testGetModulePathsAggregate() { + $this->mockForceGenerate(false); $this->setMockResolverClass( false, null, null, null, - ["Magento_example" => "example" . DIRECTORY_SEPARATOR . "paths"] + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['sample'], + ], + null, + [ + 'Magento_example' => 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'Magento_sample' => 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample', + ], + null, + null, + [], + [] ); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, [0 => "Magento_example"]); + $this->setMockResolverProperties($resolver, null, [0 => 'Magento_example', 1 => 'Magento_sample']); $this->assertEquals( [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' ], $resolver->getModulesPath() ); @@ -82,22 +95,36 @@ public function testGetModulePathsAggregate() */ public function testGetModulePathsLocations() { + // clear test object handler value to inject parsed content + $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + $this->mockForceGenerate(false); $mockResolver = $this->setMockResolverClass( true, - [0 => "example"], + [], + null, null, + [], + [], + [], null, - ["example" => "example" . DIRECTORY_SEPARATOR . "paths"] + null, + [], + [], + null, + function ($arg) { + return $arg; + }, + function ($arg) { + return $arg; + } ); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null); $this->assertEquals( - [ - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths", - "example" . DIRECTORY_SEPARATOR . "paths" - ], + [], $resolver->getModulesPath() ); @@ -119,27 +146,455 @@ public function testGetModulePathsLocations() 'Test' . DIRECTORY_SEPARATOR .'Mftf' ] ); + $mockResolver->verifyInvoked( + 'globRelevantPaths', + [ + $magentoBaseCodePath + . DIRECTORY_SEPARATOR . "dev" + . DIRECTORY_SEPARATOR . "tests" + . DIRECTORY_SEPARATOR . "acceptance" + . DIRECTORY_SEPARATOR . "tests" + . DIRECTORY_SEPARATOR . "functional" + . DIRECTORY_SEPARATOR . "Magento" + . DIRECTORY_SEPARATOR . "FunctionalTest" + , '' + ] + ); + } + + /** + * Validate aggregateTestModulePathsFromComposerJson + * + * @throws \Exception + */ + public function testAggregateTestModulePathsFromComposerJson() + { + $this->mockForceGenerate(false); + $this->setMockResolverClass( + false, + null, // getEnabledModules + null, // applyCustomMethods + null, // globRelevantWrapper + [], // relevantPath + null, // getCustomModulePaths + null, // getRegisteredModuleList + null, // aggregateTestModulePathsFromComposerJson + [], // aggregateTestModulePathsFromComposerInstaller + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA', + 'Magento_ModuleB' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ], + ], // getComposerJsonTestModulePaths + [] // getComposerInstalledTestModulePaths + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, [0 => 'Magento_ModuleB', 1 => 'Magento_ModuleC']); + $this->assertEquals( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' + ], + $resolver->getModulesPath() + ); + } + + /** + * Validate getComposerJsonTestModulePaths with paths invocation + * + * @throws \Exception + */ + public function testGetComposerJsonTestModulePathsForPathInvocation() + { + $this->mockForceGenerate(false); + $mockResolver = $this->setMockResolverClass( + false, + [], + null, + null, + [], + null, + null, + null, + null, + [], + [] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, null); + $this->assertEquals( + [], + $resolver->getModulesPath() + ); + + // Expected dev tests path + $expectedSearchPaths[] = MAGENTO_BP + . DIRECTORY_SEPARATOR + . 'dev' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'acceptance' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'functional'; + + // Expected test module path + $testModulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + + if (array_search($testModulePath, $expectedSearchPaths) === false) { + $expectedSearchPaths[] = $testModulePath; + } + + $mockResolver->verifyInvoked('getComposerJsonTestModulePaths', [$expectedSearchPaths]); + } + + /** + * Validate aggregateTestModulePathsFromComposerInstaller + * + * @throws \Exception + */ + public function testAggregateTestModulePathsFromComposerInstaller() + { + $this->mockForceGenerate(false); + $this->setMockResolverClass( + false, + null, + null, + null, + [], + null, + null, + null, + null, + [], + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA', + 'Magento_ModuleB' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC' + ], + ] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties( + $resolver, + null, + [0 => 'Magento_ModuleA', 1 => 'Magento_ModuleB', 2 => 'Magento_ModuleC'] + ); + $this->assertEquals( + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' + ], + $resolver->getModulesPath() + ); + } + + /** + * Validate getComposerInstalledTestModulePaths with paths invocation + * + * @throws \Exception + */ + public function testGetComposerInstalledTestModulePathsForPathInvocation() + { + $this->mockForceGenerate(false); + $mockResolver = $this->setMockResolverClass( + false, + [], + null, + null, + [], + null, + null, + null, + null, + [], + [] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties($resolver, null, null); + $this->assertEquals( + [], + $resolver->getModulesPath() + ); + + // Expected file path + $expectedSearchPath = MAGENTO_BP . DIRECTORY_SEPARATOR . 'composer.json'; + + $mockResolver->verifyInvoked('getComposerInstalledTestModulePaths', [$expectedSearchPath]); + } + + /** + * Validate mergeModulePaths() and flipAndFilterModulePathsArray() + * + * @throws \Exception + */ + public function testMergeFlipAndFilterModulePathsNoForceGenerate() + { + $this->mockForceGenerate(false); + $this->setMockResolverClass( + false, + null, + null, + null, + null, + null, + null, + null, + null, + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB' + ], + ], + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathD' => + [ + 'Magento_ModuleD' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathE' => + [ + 'Magento_ModuleE' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC' => + [ + 'Magento_ModuleC', + 'Magento_ModuleB', + ], + ], + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'], + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path1' => ['Magento_Path1'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path2' => ['Magento_Path2'], + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path3' => ['Magento_Path3'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path4' => ['Magento_Path4'], + ] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties( + $resolver, + null, + [ + 0 => 'Magento_Path1', + 1 => 'Magento_Path2', + 2 => 'Magento_Path4', + 3 => 'Magento_Example', + 4 => 'Magento_ModuleB', + 5 => 'Magento_ModuleD', + 6 => 'Magento_Otherexample', + 7 => 'Magento_ModuleC', + ] + ); + $this->assertEquals( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path1', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path2', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'path4', + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathD', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathC', + + ], + $resolver->getModulesPath() + ); + } + + /** + * Validate mergeModulePaths() and flipAndSortModulePathsArray() + * + * @throws \Exception + */ + public function testMergeFlipAndSortModulePathsForceGenerate() + { + $this->mockForceGenerate(true); + $this->setMockResolverClass( + false, + null, + null, + null, + null, + null, + null, + null, + null, + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB', + 'Magento_ModuleC', + ], + ], + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleC', + 'Magento_ModuleD' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleD' + ], + ], + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example' => ['Magento_Example'], + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' => ['Magento_Sample'], + ] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties( + $resolver, + null, + [ + 0 => 'Magento_ModuleB', + 1 => 'Magento_ModuleC', + 2 => 'Magento_ModuleD', + 3 => 'Magento_Example', + 4 => 'Magento_Otherexample' + ] + ); + $this->assertEquals( + [ + 'some' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'example', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB', + 'other' . DIRECTORY_SEPARATOR . 'path' . DIRECTORY_SEPARATOR . 'sample' + ], + $resolver->getModulesPath() + ); + } + + /** + * Validate logging warning in flipAndFilterModulePathsArray() + * + * @throws \Exception + */ + public function testMergeFlipAndFilterModulePathsWithLogging() + { + $this->mockForceGenerate(false); + $this->setMockResolverClass( + false, + null, + null, + null, + [], + null, + null, + null, + null, + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleB' + ], + ], + [ + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathA' => + [ + 'Magento_ModuleA' + ], + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' => + [ + 'Magento_ModuleC' + ], + ] + ); + + $resolver = ModuleResolver::getInstance(); + $this->setMockResolverProperties( + $resolver, + null, + [ + 0 => 'Magento_ModuleA', + 1 => 'Magento_ModuleB', + 2 => 'Magento_ModuleC' + ] + ); + $this->assertEquals( + [ + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathA', + 'composer' . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'pathB', + 'composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'pathB' + ], + $resolver->getModulesPath() + ); + $warnMsg = 'Path: composer' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR; + $warnMsg .= 'pathA is ignored by ModuleResolver. ' . PHP_EOL . 'Path: composer' . DIRECTORY_SEPARATOR; + $warnMsg .= 'json' . DIRECTORY_SEPARATOR . 'pathA is set for Module: Magento_ModuleA' . PHP_EOL; + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + $warnMsg, + [] + ); } /** * Validate custom modules are added * @throws \Exception */ - public function testGetCustomModulePath() + public function testApplyCustomModuleMethods() { - $this->setMockResolverClass(false, ["Magento_TestModule"], null, null, [], ['otherPath']); + $this->setMockResolverClass( + false, + null, + null, + null, + [], + [ 'Magento_Module' => 'otherPath'], + null, + null, + null, + [], + [] + ); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null, null); $this->assertEquals(['otherPath'], $resolver->getModulesPath()); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', 'including custom module', - ['module' => 'otherPath'] + [ 'Magento_Module' => 'otherPath'] ); } /** * Validate blacklisted modules are removed + * Module paths are sorted according to module name in alphabetically ascending order + * * @throws \Exception */ public function testGetModulePathsBlacklist() @@ -149,22 +604,36 @@ public function testGetModulePathsBlacklist() null, null, null, - function ($arg1, $arg2) { - if ($arg2 === "") { - $mockValue = ["somePath" => "somePath"]; - } else { - $mockValue = ["lastPath" => "lastPath"]; - } - return $mockValue; + [], + null, + null, + null, + null, + [], + [], + [ + 'thisPath/some/path4' => ['Some_Module4'], + 'devTests/Magento/path3' => ['Magento_Module3'], + 'appCode/Magento/path2' => ['Magento_Module2'], + 'vendor/amazon/path1' => ['Amazon_Module1'], + ], + function ($arg) { + return $arg; + }, + function ($arg) { + return $arg; } ); $resolver = ModuleResolver::getInstance(); - $this->setMockResolverProperties($resolver, null, null, ["somePath"]); - $this->assertEquals(["lastPath", "lastPath"], $resolver->getModulesPath()); + $this->setMockResolverProperties($resolver, null, null, ['Magento_Module3']); + $this->assertEquals( + ['vendor/amazon/path1', 'appCode/Magento/path2', 'thisPath/some/path4'], + $resolver->getModulesPath() + ); TestLoggingUtil::getInstance()->validateMockLogStatement( 'info', 'excluding module', - ['module' => 'somePath'] + ['module' => 'Magento_Module3'] ); } @@ -178,7 +647,19 @@ public function testGetModulePathsNoAdminToken() $this->mockForceGenerate(false); // Mock ModuleResolver and $enabledModulesPath - $this->setMockResolverClass(false, null, ["example" . DIRECTORY_SEPARATOR . "paths"], []); + $this->setMockResolverClass( + false, + null, + ["example" . DIRECTORY_SEPARATOR . "paths"], + [], + null, + null, + null, + null, + null, + [], + [] + ); $resolver = ModuleResolver::getInstance(); $this->setMockResolverProperties($resolver, null, null); @@ -247,21 +728,39 @@ public function testGetAdminTokenWithBadResponse() * Function used to set mock for parser return and force init method to run between tests. * * @param string $mockToken - * @param array $mockGetModules - * @param string[] $mockCustomMethods - * @param string[] $mockGlob - * @param string[] $mockRelativePaths - * @param string[] $mockCustomModules + * @param array $mockGetEnabledModules + * @param string[] $mockApplyCustomMethods + * @param string[] $mockGlobRelevantWrapper + * @param string[] $mockRelevantPaths + * @param string[] $mockGetCustomModulePaths + * @param string[] $mockGetRegisteredModuleList + * @param string[] $mockAggregateTestModulePathsFromComposerJson + * @param string[] $mockAggregateTestModulePathsFromComposerInstaller + * @param string[] $mockGetComposerJsonTestModulePaths + * @param string[] $mockGetComposerInstalledTestModulePaths + * @param string[] $mockAggregateTestModulePaths + * @param string[] $mockNormalizeModuleNames + * @param string[] $mockFlipAndFilterModulePathsArray + * @param string[] $mockFlipAndSortModulePathsArray * @throws \Exception * @return Verifier ModuleResolver double */ private function setMockResolverClass( $mockToken = null, - $mockGetModules = null, - $mockCustomMethods = null, - $mockGlob = null, - $mockRelativePaths = null, - $mockCustomModules = null + $mockGetEnabledModules = null, + $mockApplyCustomMethods = null, + $mockGlobRelevantWrapper = null, + $mockRelevantPaths = null, + $mockGetCustomModulePaths = null, + $mockGetRegisteredModuleList = null, + $mockAggregateTestModulePathsFromComposerJson = null, + $mockAggregateTestModulePathsFromComposerInstaller = null, + $mockGetComposerJsonTestModulePaths = null, + $mockGetComposerInstalledTestModulePaths = null, + $mockAggregateTestModulePaths = null, + $mockNormalizeModuleNames = null, + $mockFlipAndFilterModulePathsArray = null, + $mockFlipAndSortModulePathsArray = null ) { $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); $property->setAccessible(true); @@ -271,23 +770,49 @@ private function setMockResolverClass( if (isset($mockToken)) { $mockMethods['getAdminToken'] = $mockToken; } - if (isset($mockGetModules)) { - $mockMethods['getEnabledModules'] = $mockGetModules; + if (isset($mockGetEnabledModules)) { + $mockMethods['getEnabledModules'] = $mockGetEnabledModules; } - if (isset($mockCustomMethods)) { - $mockMethods['applyCustomModuleMethods'] = $mockCustomMethods; + if (isset($mockApplyCustomMethods)) { + $mockMethods['applyCustomModuleMethods'] = $mockApplyCustomMethods; } - if (isset($mockGlob)) { - $mockMethods['globRelevantWrapper'] = $mockGlob; + if (isset($mockGlobRelevantWrapper)) { + $mockMethods['globRelevantWrapper'] = $mockGlobRelevantWrapper; } - if (isset($mockRelativePaths)) { - $mockMethods['globRelevantPaths'] = $mockRelativePaths; + if (isset($mockRelevantPaths)) { + $mockMethods['globRelevantPaths'] = $mockRelevantPaths; } - if (isset($mockCustomModules)) { - $mockMethods['getCustomModulePaths'] = $mockCustomModules; + if (isset($mockGetCustomModulePaths)) { + $mockMethods['getCustomModulePaths'] = $mockGetCustomModulePaths; + } + if (isset($mockGetRegisteredModuleList)) { + $mockMethods['getRegisteredModuleList'] = $mockGetRegisteredModuleList; + } + if (isset($mockAggregateTestModulePathsFromComposerJson)) { + $mockMethods['aggregateTestModulePathsFromComposerJson'] = $mockAggregateTestModulePathsFromComposerJson; + } + if (isset($mockAggregateTestModulePathsFromComposerInstaller)) { + $mockMethods['aggregateTestModulePathsFromComposerInstaller'] = + $mockAggregateTestModulePathsFromComposerInstaller; + } + if (isset($mockGetComposerJsonTestModulePaths)) { + $mockMethods['getComposerJsonTestModulePaths'] = $mockGetComposerJsonTestModulePaths; + } + if (isset($mockGetComposerInstalledTestModulePaths)) { + $mockMethods['getComposerInstalledTestModulePaths'] = $mockGetComposerInstalledTestModulePaths; + } + if (isset($mockAggregateTestModulePaths)) { + $mockMethods['aggregateTestModulePaths'] = $mockAggregateTestModulePaths; + } + if (isset($mockNormalizeModuleNames)) { + $mockMethods['normalizeModuleNames'] = $mockNormalizeModuleNames; + } + if (isset($mockFlipAndFilterModulePathsArray)) { + $mockMethods['flipAndFilterModulePathsArray'] = $mockFlipAndFilterModulePathsArray; + } + if (isset($mockFlipAndSortModulePathsArray)) { + $mockMethods['flipAndSortModulePathsArray'] = $mockFlipAndSortModulePathsArray; } -// $mockMethods['printMagentoVersionInfo'] = null; - $mockResolver = AspectMock::double( ModuleResolver::class, $mockMethods diff --git a/dev/tests/unit/Util/MockModuleResolverBuilder.php b/dev/tests/unit/Util/MockModuleResolverBuilder.php new file mode 100644 index 000000000..0e1b6fc31 --- /dev/null +++ b/dev/tests/unit/Util/MockModuleResolverBuilder.php @@ -0,0 +1,61 @@ + '/base/path/some/other/path/Magento/Module']; + + /** + * Mock ModuleResolver builder + * + * @param array $paths + * @return void + * @throws \Exception + */ + public function setup($paths = null) + { + if (empty($paths)) { + $paths = $this->defaultPaths; + } + + $mockConfig = AspectMock::double(MftfApplicationConfig::class, ['forceGenerateEnabled' => false]); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockConfig->make(), 'get' => null])->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + + $property = new \ReflectionProperty(ModuleResolver::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + $mockResolver = AspectMock::double( + ModuleResolver::class, + [ + 'getAdminToken' => false, + 'globRelevantPaths' => [], + 'getEnabledModules' => [] + ] + ); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockResolver->make(), 'get' => null]) + ->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + + $resolver = ModuleResolver::getInstance(); + $property = new \ReflectionProperty(ModuleResolver::class, 'enabledModuleNameAndPaths'); + $property->setAccessible(true); + $property->setValue($resolver, $paths); + } +} diff --git a/dev/tests/unit/Util/TestDataArrayBuilder.php b/dev/tests/unit/Util/TestDataArrayBuilder.php index aeeeae850..eb192e040 100644 --- a/dev/tests/unit/Util/TestDataArrayBuilder.php +++ b/dev/tests/unit/Util/TestDataArrayBuilder.php @@ -225,7 +225,7 @@ public function withFileName($filename = null) */ public function withTestReference($reference = null) { - if ($reference != null) { + if ($reference !== null) { $this->testReference = $reference; } diff --git a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt index c98120d03..d7e31b5f2 100644 --- a/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt +++ b/dev/tests/verification/Resources/ActionGroupContainsStepKeyInArgText.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupContainsStepKeyInArgTextCest { diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt index c607cb7b2..127b9cc59 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertAfter.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupMergedViaInsertAfterCest { diff --git a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt index 08281a965..1486d042e 100644 --- a/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt +++ b/dev/tests/verification/Resources/ActionGroupMergedViaInsertBefore.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupMergedViaInsertBeforeCest { diff --git a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt b/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt index 64294b7f8..d11d5c238 100644 --- a/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt +++ b/dev/tests/verification/Resources/ActionGroupSkipReadiness.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupSkipReadinessCest { diff --git a/dev/tests/verification/Resources/ActionGroupToExtend.txt b/dev/tests/verification/Resources/ActionGroupToExtend.txt index b91c06036..d7e72260d 100644 --- a/dev/tests/verification/Resources/ActionGroupToExtend.txt +++ b/dev/tests/verification/Resources/ActionGroupToExtend.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupToExtendCest { diff --git a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt index 97369c6ea..96134fd86 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingCreateData.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupUsingCreateDataCest { diff --git a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt index 13b67b10f..b69ee6c9d 100644 --- a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupUsingNestedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt index f9984944a..c0498101d 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupWithDataOverrideTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt index 79bda5f1b..01a87104d 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupWithDataTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt index 68f95298d..ce8a4aed8 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Hardcoded Value in Param") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithDefaultArgumentAndStringSelectorParamCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt index 1c211ce4e..b7bef0135 100644 --- a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Multiple Argument Values in Param") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithMultipleParameterSelectorsFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt index 11da2ddf6..6b81a187f 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With No Argument") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithNoArgumentsCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt index b24362028..6125c7697 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupWithNoDefaultTestCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt index 7d32e2e68..163ebc6ba 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementWithHyphen.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithParameterizedElementWithHyphenCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt index ba62ba479..de7af8209 100644 --- a/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithParameterizedElementsWithStepKeyReferences.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithParameterizedElementsWithStepKeyReferencesCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt index 6b85f8b47..9720595e0 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Hardcoded Value in Param") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithPassedArgumentAndStringSelectorParamCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt index f774269ea..da0480379 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupWithPersistedDataCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt index 20a9c5137..a0b6d0074 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSectionAndDataAsArguments.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithSectionAndDataAsArgumentsCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt index 52cdeb83d..4d3da18a4 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Default Argument") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithSimpleDataUsageFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt index 9ad3dd560..d866c3992 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Simple Data Usage From Passed Argument") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithSimpleDataUsageFromPassedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt index 48fc52ff4..af7af43a4 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Default Argument Value and Argument Value in Param") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithSingleParameterSelectorFromDefaultArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt index 97c8bca5b..096623789 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With Passed Argument Value and Argument Value in Param") + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithSingleParameterSelectorFromPassedArgumentCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt index d83447d03..eddaaf784 100644 --- a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ActionGroupWithStepKeyReferencesCest { diff --git a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt index c579d165e..ef1a5636c 100644 --- a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ActionGroupWithTopLevelPersistedDataCest { diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 5b9f838a4..2da03b506 100644 --- a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt +++ b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class ArgumentWithSameNameAsElementCest { diff --git a/dev/tests/verification/Resources/AssertTest.txt b/dev/tests/verification/Resources/AssertTest.txt index 1f8eef56e..28c38bc0b 100644 --- a/dev/tests/verification/Resources/AssertTest.txt +++ b/dev/tests/verification/Resources/AssertTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/AssertTest.xml
") */ class AssertTestCest { diff --git a/dev/tests/verification/Resources/BasicActionGroupTest.txt b/dev/tests/verification/Resources/BasicActionGroupTest.txt index c6b3cec81..4bb9c1a95 100644 --- a/dev/tests/verification/Resources/BasicActionGroupTest.txt +++ b/dev/tests/verification/Resources/BasicActionGroupTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class BasicActionGroupTestCest { diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 58794ba20..f82c84119 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: A Functional Cest") * @group functional + * @Description("

Test files

verification/TestModule/Test/BasicFunctionalTest.xml
") */ class BasicFunctionalTestCest { diff --git a/dev/tests/verification/Resources/BasicMergeTest.txt b/dev/tests/verification/Resources/BasicMergeTest.txt index 8b311dac2..5421efe33 100644 --- a/dev/tests/verification/Resources/BasicMergeTest.txt +++ b/dev/tests/verification/Resources/BasicMergeTest.txt @@ -18,6 +18,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; * @Title("[NO TESTCASEID]: BasicMergeTest") * @group functional * @group mergeTest + * @Description("

Test files

verification/TestModule/Test/MergeFunctionalTest.xml
verification/TestModuleMerged/Test/MergeFunctionalTest.xml
") */ class BasicMergeTestCest { diff --git a/dev/tests/verification/Resources/CharacterReplacementTest.txt b/dev/tests/verification/Resources/CharacterReplacementTest.txt index 94459d3b1..db3852b17 100644 --- a/dev/tests/verification/Resources/CharacterReplacementTest.txt +++ b/dev/tests/verification/Resources/CharacterReplacementTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/CharacterReplacementTest.xml
") */ class CharacterReplacementTestCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt index 4c8ec1ea1..e03501707 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestAddHooks.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestAddHooks") * @group Parent + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestAddHooksCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt index a59d141b4..a99843b48 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestMerging.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestMerging.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestMerging") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestMergingCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt index 34106b5fa..1eec9e48c 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestNoParent.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestNoParent") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") * @group skip */ class ChildExtendedTestNoParentCest diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt index b1eb8340b..20aa62e28 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveAction.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveAction") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestRemoveActionCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt index cd0e6b049..561fd24fa 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestRemoveHookAction.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestRemoveHookAction") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestRemoveHookActionCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt index 6c699b7db..8d131a83f 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplace.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplace.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplace") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestReplaceCest { diff --git a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt index 82184a206..ae0b02c20 100644 --- a/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt +++ b/dev/tests/verification/Resources/ChildExtendedTestReplaceHook.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestReplaceHook") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ChildExtendedTestReplaceHookCest { diff --git a/dev/tests/verification/Resources/DataActionsTest.txt b/dev/tests/verification/Resources/DataActionsTest.txt index 728bef2e7..380db238d 100644 --- a/dev/tests/verification/Resources/DataActionsTest.txt +++ b/dev/tests/verification/Resources/DataActionsTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/DataActionsTest.xml
") */ class DataActionsTestCest { diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index 8e8eb70a6..731ae63f8 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/DataReplacementTest.xml
") */ class DataReplacementTestCest { diff --git a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt b/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt index 0fbee0550..6b709a0cb 100644 --- a/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt +++ b/dev/tests/verification/Resources/ExecuteInSeleniumTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ExecuteInSeleniumTest.xml
") */ class ExecuteInSeleniumTestCest { @@ -27,6 +28,6 @@ class ExecuteInSeleniumTestCest */ public function ExecuteInSeleniumTest(AcceptanceTester $I) { - $I->executeInSelenium(function ($webdriver) { return 'Hello, World!'}); // stepKey: executeInSeleniumStep + $I->executeInSelenium(function ($webdriver) { return "Hello, World!"}); // stepKey: executeInSeleniumStep } } diff --git a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt index 19140eaf9..f7618cbf9 100644 --- a/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt +++ b/dev/tests/verification/Resources/ExecuteJsEscapingTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ExecuteJsTest.xml
") */ class ExecuteJsEscapingTestCest { diff --git a/dev/tests/verification/Resources/ExtendParentDataTest.txt b/dev/tests/verification/Resources/ExtendParentDataTest.txt index a91c2f464..fb90599e3 100644 --- a/dev/tests/verification/Resources/ExtendParentDataTest.txt +++ b/dev/tests/verification/Resources/ExtendParentDataTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ExtendedDataTest.xml
") */ class ExtendParentDataTestCest { diff --git a/dev/tests/verification/Resources/ExtendedActionGroup.txt b/dev/tests/verification/Resources/ExtendedActionGroup.txt index 66ecdbd8a..7456d99ce 100644 --- a/dev/tests/verification/Resources/ExtendedActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedActionGroup.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ExtendedActionGroupCest { diff --git a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt index 2483617d1..f52a46c2f 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestInSuiteCest.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestInSuite") * @group ExtendedTestInSuite + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ExtendedChildTestInSuiteCest { diff --git a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt index cb5fc2e12..3dfeac8f3 100644 --- a/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt +++ b/dev/tests/verification/Resources/ExtendedChildTestNotInSuite.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ExtendedChildTestNotInSuite") + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ExtendedChildTestNotInSuiteCest { diff --git a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt index eed17340e..a9c971b96 100644 --- a/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt +++ b/dev/tests/verification/Resources/ExtendedRemoveActionGroup.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupTest.xml
") */ class ExtendedRemoveActionGroupCest { diff --git a/dev/tests/verification/Resources/ExtendingSkippedTest.txt b/dev/tests/verification/Resources/ExtendingSkippedTest.txt index b5632c030..681af0fef 100644 --- a/dev/tests/verification/Resources/ExtendingSkippedTest.txt +++ b/dev/tests/verification/Resources/ExtendingSkippedTest.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ChildExtendedTestSkippedParent") * @group Child + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ExtendingSkippedTestCest { diff --git a/dev/tests/verification/Resources/HookActionsTest.txt b/dev/tests/verification/Resources/HookActionsTest.txt index 3068e6ac1..231353bc9 100644 --- a/dev/tests/verification/Resources/HookActionsTest.txt +++ b/dev/tests/verification/Resources/HookActionsTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/HookActionsTest.xml
") */ class HookActionsTestCest { diff --git a/dev/tests/verification/Resources/LocatorFunctionTest.txt b/dev/tests/verification/Resources/LocatorFunctionTest.txt index ad9a1a9e7..00baeeac5 100644 --- a/dev/tests/verification/Resources/LocatorFunctionTest.txt +++ b/dev/tests/verification/Resources/LocatorFunctionTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/LocatorFunctionTest.xml
") */ class LocatorFunctionTestCest { diff --git a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt index 2588e679a..7d3bfec09 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertAfter.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/BasicFunctionalTest.xml
verification/TestModule/Test/MergeFunctionalTest.xml
") */ class MergeMassViaInsertAfterCest { diff --git a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt index 7f220c2b7..42a0fcf89 100644 --- a/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt +++ b/dev/tests/verification/Resources/MergeMassViaInsertBefore.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/BasicFunctionalTest.xml
verification/TestModule/Test/MergeFunctionalTest.xml
") */ class MergeMassViaInsertBeforeCest { diff --git a/dev/tests/verification/Resources/MergeSkip.txt b/dev/tests/verification/Resources/MergeSkip.txt index 878fe6497..fdfbdbfe4 100644 --- a/dev/tests/verification/Resources/MergeSkip.txt +++ b/dev/tests/verification/Resources/MergeSkip.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/MergeFunctionalTest.xml
verification/TestModuleMerged/Test/MergeFunctionalTest.xml
") */ class MergeSkipCest { diff --git a/dev/tests/verification/Resources/MergedActionGroupTest.txt b/dev/tests/verification/Resources/MergedActionGroupTest.txt index 9bd6c0ded..636c28627 100644 --- a/dev/tests/verification/Resources/MergedActionGroupTest.txt +++ b/dev/tests/verification/Resources/MergedActionGroupTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class MergedActionGroupTestCest { diff --git a/dev/tests/verification/Resources/MergedReferencesTest.txt b/dev/tests/verification/Resources/MergedReferencesTest.txt index 456f66cc1..794ba6370 100644 --- a/dev/tests/verification/Resources/MergedReferencesTest.txt +++ b/dev/tests/verification/Resources/MergedReferencesTest.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: MergedReferencesTest") * @group functional + * @Description("

Test files

verification/TestModule/Test/MergeFunctionalTest.xml
") */ class MergedReferencesTestCest { diff --git a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt index 4f12b2f07..186cd404c 100644 --- a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt +++ b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @group functional + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class MultipleActionGroupsTestCest { diff --git a/dev/tests/verification/Resources/PageReplacementTest.txt b/dev/tests/verification/Resources/PageReplacementTest.txt index c7bede23e..f6c6b1bac 100644 --- a/dev/tests/verification/Resources/PageReplacementTest.txt +++ b/dev/tests/verification/Resources/PageReplacementTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/PageReplacementTest.xml
") */ class PageReplacementTestCest { diff --git a/dev/tests/verification/Resources/ParameterArrayTest.txt b/dev/tests/verification/Resources/ParameterArrayTest.txt index 8e278b826..52332217b 100644 --- a/dev/tests/verification/Resources/ParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ParameterArrayTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ParameterArrayTest.xml
") */ class ParameterArrayTestCest { diff --git a/dev/tests/verification/Resources/ParentExtendedTest.txt b/dev/tests/verification/Resources/ParentExtendedTest.txt index 7c0375190..aeae567a2 100644 --- a/dev/tests/verification/Resources/ParentExtendedTest.txt +++ b/dev/tests/verification/Resources/ParentExtendedTest.txt @@ -17,6 +17,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: ParentExtendedTest") * @group Parent + * @Description("

Test files

verification/TestModule/Test/ExtendedFunctionalTest.xml
") */ class ParentExtendedTestCest { diff --git a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt index 7840afc76..c6c09c9c7 100644 --- a/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt +++ b/dev/tests/verification/Resources/PersistedAndXmlEntityArguments.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/ActionGroupFunctionalTest.xml
") */ class PersistedAndXmlEntityArgumentsCest { diff --git a/dev/tests/verification/Resources/PersistedReplacementTest.txt b/dev/tests/verification/Resources/PersistedReplacementTest.txt index c7ef3291a..eb3642ed1 100644 --- a/dev/tests/verification/Resources/PersistedReplacementTest.txt +++ b/dev/tests/verification/Resources/PersistedReplacementTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/PersistedReplacementTest.xml
") */ class PersistedReplacementTestCest { diff --git a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt index bc5e16294..95aab913f 100644 --- a/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt +++ b/dev/tests/verification/Resources/PersistenceActionGroupAppendingTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/PersistenceActionGroupAppendingTest.xml
") */ class PersistenceActionGroupAppendingTestCest { diff --git a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt index 4853619e1..38dc364e7 100644 --- a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt +++ b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/PersistenceCustomFieldsTest.xml
") */ class PersistenceCustomFieldsTestCest { diff --git a/dev/tests/verification/Resources/SectionReplacementTest.txt b/dev/tests/verification/Resources/SectionReplacementTest.txt index aab4a87a0..5089cfbfd 100644 --- a/dev/tests/verification/Resources/SectionReplacementTest.txt +++ b/dev/tests/verification/Resources/SectionReplacementTest.txt @@ -15,6 +15,7 @@ use Yandex\Allure\Adapter\Model\SeverityLevel; use Yandex\Allure\Adapter\Annotation\TestCaseId; /** + * @Description("

Test files

verification/TestModule/Test/SectionReplacementTest.xml
") */ class SectionReplacementTestCest { diff --git a/dev/tests/verification/Resources/SkippedTest.txt b/dev/tests/verification/Resources/SkippedTest.txt index 1b466d4e0..08ff4fd43 100644 --- a/dev/tests/verification/Resources/SkippedTest.txt +++ b/dev/tests/verification/Resources/SkippedTest.txt @@ -16,7 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTest") - * @Description("") + * @Description("

Test files

verification/TestModule/Test/SkippedTest.xml
") */ class SkippedTestCest { diff --git a/dev/tests/verification/Resources/SkippedTestNoIssues.txt b/dev/tests/verification/Resources/SkippedTestNoIssues.txt index b68823d39..168077458 100644 --- a/dev/tests/verification/Resources/SkippedTestNoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestNoIssues.txt @@ -16,7 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedNoIssuesTest") - * @Description("") + * @Description("

Test files

verification/TestModule/Test/SkippedTest.xml
") * @group skip */ class SkippedTestNoIssuesCest diff --git a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt index dbe3ea054..3de81e02f 100644 --- a/dev/tests/verification/Resources/SkippedTestTwoIssues.txt +++ b/dev/tests/verification/Resources/SkippedTestTwoIssues.txt @@ -16,7 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedMultipleIssuesTest") - * @Description("") + * @Description("

Test files

verification/TestModule/Test/SkippedTest.xml
") */ class SkippedTestTwoIssuesCest { diff --git a/dev/tests/verification/Resources/SkippedTestWithHooks.txt b/dev/tests/verification/Resources/SkippedTestWithHooks.txt index ab6d241e3..a86924b94 100644 --- a/dev/tests/verification/Resources/SkippedTestWithHooks.txt +++ b/dev/tests/verification/Resources/SkippedTestWithHooks.txt @@ -16,7 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: skippedTestWithHooks") - * @Description("") + * @Description("

Test files

verification/TestModule/Test/SkippedTest.xml
") */ class SkippedTestWithHooksCest { diff --git a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt index 4605cc350..7e43433a1 100644 --- a/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedActionGroupTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Action Group With comment block in arguments and action group body") + * @Description("

Test files

verification/TestModule/Test/XmlCommentedActionGroupTest.xml
") */ class XmlCommentedActionGroupTestCest { diff --git a/dev/tests/verification/Resources/XmlCommentedTest.txt b/dev/tests/verification/Resources/XmlCommentedTest.txt index 1d8bca5aa..f77aa5fe4 100644 --- a/dev/tests/verification/Resources/XmlCommentedTest.txt +++ b/dev/tests/verification/Resources/XmlCommentedTest.txt @@ -16,6 +16,7 @@ use Yandex\Allure\Adapter\Annotation\TestCaseId; /** * @Title("[NO TESTCASEID]: Test With comment blocks in root element 'tests', in annotations and in test body.") + * @Description("

Test files

verification/TestModule/Test/XmlCommentedTest.xml
") */ class XmlCommentedTestCest { diff --git a/dev/tests/verification/TestModule/Page/SamplePage.xml b/dev/tests/verification/TestModule/Page/SamplePage.xml index bf8f99615..59efded6e 100644 --- a/dev/tests/verification/TestModule/Page/SamplePage.xml +++ b/dev/tests/verification/TestModule/Page/SamplePage.xml @@ -8,25 +8,25 @@ - +
- +
- +
- +
- +
- +
- +
diff --git a/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml b/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml index 0a72189b4..c25164220 100644 --- a/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml +++ b/dev/tests/verification/TestModule/Test/ExecuteInSeleniumTest.xml @@ -9,6 +9,6 @@ - + diff --git a/docs/getting-started.md b/docs/getting-started.md index 626cca31b..8f024b90d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -18,6 +18,7 @@ Make sure that you have the following software installed and configured on your
[PhpStorm] supports [Codeception test execution][], which is helpful when debugging.
+ ## Install Magento {#install-magento} Use instructions below to install Magento. @@ -67,11 +68,17 @@ To disable the WYSIWYG and enable the web driver to process these fields as simp 3. In the WYSIWYG Options section set the **Enable WYSIWYG Editor** option to **Disabled Completely**. 4. Click **Save Config**. +or via command line: + +```bash +bin/magento config:set cms/wysiwyg/enabled disabled +``` +
When you want to test the WYSIWYG functionality, re-enable WYSIWYG in your test suite.
-### Security settings {#security-settings} +### Security settings {#security-settings} To enable the **Admin Account Sharing** setting, to avoid unpredictable logout during a testing session, and disable the **Add Secret Key in URLs** setting, to open pages using direct URLs: @@ -80,6 +87,16 @@ To enable the **Admin Account Sharing** setting, to avoid unpredictable logout d 3. Set **Add Secret Key to URLs** to **No**. 4. Click **Save Config**. +or via command line: + +```bash +bin/magento config:set admin/security/admin_account_sharing 1 +``` + +```bash +bin/magento config:set admin/security/use_form_key 0 +``` + ### Nginx settings {#nginx-settings} If Nginx Web server is used on your development environment then **Use Web Server Rewrites** setting in **Stores** > Settings > **Configuration** > **Web** > **Search Engine Optimization** must be set to **Yes**. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d546c4e15..d2a7dcabe 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -2,60 +2,64 @@ Having a little trouble with the MFTF? See some common errors and fixes below. -## WebDriver issues - -Troubleshoot your WebDriver issues on various browsers. +## AcceptanceTester class issues -### PhantomJS - -You are unable to upload file input using the MFTF actions and are seeing the following exception: +If you see the following error: ```terminal -[Facebook\WebDriver\Exception\NoSuchDriverException] -No active session with ID e56f9260-b366-11e7-966b-db3e6f35d8e1 +AcceptanceTester class doesn't exist in suite folder. +Run the 'build' command to generate it ``` -#### Reason +### Reason -Use of PhantomJS is not actually supported by the MFTF. +Something went wrong during the `mftf build:project` command that prevented the creation of the AcceptanceTester class. -#### Solution +### Solution -For headless browsing, the [Headless Chrome][]{:target="\_blank"} has better compatibility with the MFTF. +This issue is fixed in the MFTF 2.5.0. -### Chrome +In versions of the MFTF lower than 2.5.0 you should: -You are seeing an "unhandled inspector error" exception: +1. Open the functional.suite.yml file at: -```terminal -[Facebook\WebDriver\Exception\UnknownServerException] -unknown error: undhandled inspector error: {"code":-32601, "message": -"'Network.deleteCookie' wasn't found"} .... -``` + ```terminal + /dev/tests/acceptance/tests/functional.suite.yml + ``` +1. Add quotation marks (`"`) around these values: -![Screenshot with the exception](./img/trouble-chrome232.png) + 1. `%SELENIUM_HOST%` + 1. `%SELENIUM_PORT%` + 1. `%SELENIUM_PROTOCOL%` + 1. `%SELENIUM_PATH%` + +1. Run the `vendor/bin/mftf build:project` command again. +1. You should see the AcceptanceTester class is created at: -#### Reason + ```terminal + /vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/AcceptanceTester.php + ``` -Chrome v62 is in the process of being rolled out, and it causes an error with ChromeDriver v2.32+. +## WebDriver issues -#### Solution +Troubleshoot your WebDriver issues on various browsers. -Use [ChromeDriver 74.0.3729.6+][]{:target="\_blank"} and [Selenium Server Standalone v3.9+][]{:target="\_blank"} in order to execute tests in Google Chrome v62+. +### PhantomJS -### Firefox +You are unable to upload file input using the MFTF actions and are seeing the following exception: -Tests that use the `moveMouseOver` action cause an error when run locally. +```terminal +[Facebook\WebDriver\Exception\NoSuchDriverException] +No active session with ID e56f9260-b366-11e7-966b-db3e6f35d8e1 +``` #### Reason -There's a compatibility issue with Codeception's `moveMouseOver` function and GeckoDriver with Firefox. +Use of PhantomJS is not supported by the MFTF. #### Solution -None yet. Solving this problem is dependent on a GeckoDriver fix. +For headless browsing, the [Headless Chrome][]{:target="\_blank"} has better compatibility with the MFTF. [Headless Chrome]: https://developers.google.com/web/updates/2017/04/headless-chrome -[ChromeDriver 74.0.3729.6+]: https://chromedriver.storage.googleapis.com/index.html?path=2.33/ -[Selenium Server Standalone v3.9+]: http://www.seleniumhq.org/download/ diff --git a/etc/config/.env.example b/etc/config/.env.example index f4aa90e51..f25cb3de7 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -43,7 +43,7 @@ BROWSER=chrome #DEFAULT_TIMEZONE=America/Los_Angeles #*** These properties impact the modules loaded into MFTF, you can point to your own full path, or a custom set of modules located with the core set -MODULE_WHITELIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +MODULE_WHITELIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProductCatalogSearch #CUSTOM_MODULE_PATHS= #*** Bool property which allows the user to toggle debug output during test execution diff --git a/etc/config/command.php b/etc/config/command.php index 047af324a..7b45a2595 100644 --- a/etc/config/command.php +++ b/etc/config/command.php @@ -11,9 +11,9 @@ $magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); $tokenModel = $magentoObjectManager->get(\Magento\Integration\Model\Oauth\Token::class); - $tokenPassedIn = urldecode($_POST['token']); - $command = urldecode($_POST['command']); - $arguments = urldecode($_POST['arguments']); + $tokenPassedIn = urldecode($_POST['token'] ?? ''); + $command = urldecode($_POST['command'] ?? ''); + $arguments = urldecode($_POST['arguments'] ?? ''); // Token returned will be null if the token we passed in is invalid $tokenFromMagento = $tokenModel->loadByToken($tokenPassedIn)->getToken(); @@ -22,7 +22,8 @@ $magentoBinary = $php . ' -f ../../../../bin/magento'; $valid = validateCommand($magentoBinary, $command); if ($valid) { - $process = new Symfony\Component\Process\Process($magentoBinary . " $command" . " $arguments"); + $fullCommand = escapeshellcmd($magentoBinary . " $command" . " $arguments"); + $process = new Symfony\Component\Process\Process($fullCommand); $process->setIdleTimeout(60); $process->setTimeout(0); $idleTimeout = false; @@ -40,6 +41,11 @@ $output = "CLI command timed out, no output available."; $idleTimeout = true; } + + if (checkForFilePath($output)) { + $output = "CLI output suppressed, filepath detected in output."; + } + $exitCode = $process->getExitCode(); if ($exitCode == 0 || $idleTimeout) { @@ -103,3 +109,13 @@ function trimAfterWhitespace($string) { return strtok($string, ' '); } + +/** + * Detects file path in string. + * @param string $string + * @return boolean + */ +function checkForFilePath($string) +{ + return preg_match('/\/[\S]+\//', $string); +} diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index 3bcbf0b26..4bce08d9f 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -219,7 +219,6 @@ public function testError(FailEvent $failEvent) * Override of parent method, polls stepStorage for testcase and formats it according to actionGroup nesting. * * @return void - * @SuppressWarnings(PHPMD) */ public function testEnd() { @@ -245,11 +244,7 @@ public function testEnd() $step->setName(str_replace(ActionGroupObject::ACTION_GROUP_CONTEXT_START, '', $step->getName())); $actionGroupStepContainer = $step; - - preg_match(TestGenerator::ACTION_GROUP_STEP_KEY_REGEX, $step->getName(), $matches); - if (!empty($matches['actionGroupStepKey'])) { - $actionGroupStepKey = ucfirst($matches['actionGroupStepKey']); - } + $actionGroupStepKey = $this->retrieveActionGroupStepKey($step); continue; } @@ -289,6 +284,25 @@ function () use ($rootStep, $formattedSteps) { $this->getLifecycle()->fire(new TestCaseFinishedEvent()); } + /** + * Reads action group stepKey from step. + * + * @param Step $step + * @return string|null + */ + private function retrieveActionGroupStepKey($step) + { + $actionGroupStepKey = null; + + preg_match(TestGenerator::ACTION_GROUP_STEP_KEY_REGEX, $step->getName(), $matches); + + if (!empty($matches['actionGroupStepKey'])) { + $actionGroupStepKey = ucfirst($matches['actionGroupStepKey']); + } + + return $actionGroupStepKey; + } + /** * Reading stepKey from file. * diff --git a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php index 6e61189bc..0ba1a3b2f 100644 --- a/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php +++ b/src/Magento/FunctionalTestingFramework/Codeception/Subscriber/Console.php @@ -38,7 +38,7 @@ class Console extends \Codeception\Subscriber\Console * @param array $extensionOptions * @param array $options * - * @SuppressWarnings(PHPMD) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct($extensionOptions = [], $options = []) { diff --git a/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php new file mode 100644 index 000000000..98f72d0c1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/AbstractComposer.php @@ -0,0 +1,86 @@ +[^,\s]+_[^,\s]+)/'; + + /**#@+ + * Composer package array keys + */ + const PACKAGE_NAME = 'name'; + const PACKAGE_TYPE = 'type'; + const PACKAGE_VERSION = 'version'; + const PACKAGE_DESCRIPTION = 'description'; + const PACKAGE_INSTALLEDPATH = 'installedPath'; + const PACKAGE_REQUIRES = 'requires'; + const PACKAGE_DEVREQUIRES = 'devRequires'; + const PACKAGE_SUGGESTS = 'suggests'; + const PACKAGE_SUGGESTED_MAGENTO_MODULES = 'suggestedMagentoModules'; + /**#@-*/ + + /** + * @var \Composer\Composer + */ + protected $composer; + + /** + * @param string $composerFile + */ + public function __construct($composerFile) + { + $this->composer = \Composer\Factory::create(new BufferIO(), $composerFile); + } + + /** + * Get composer + * + * @return \Composer\Composer + */ + protected function getComposer() + { + return $this->composer; + } + + /** + * Parse input array and return all suggested magento module names, i.e. an example "suggest" in composer.json + * + * "suggest": { + * "magento/module-backend": "type: magento2-module, name: Magento_Backend, version: ~100.0.0", + * "magento/module-store": "type: magento2-module, name: Magento_Store, version: ~100.0.0" + * } + * + * @param array $suggests + * @return array + */ + protected function parseSuggestsForMagentoModuleNames($suggests) + { + $magentoModuleNames = []; + foreach ($suggests as $suggest) { + // Expecting pattern - type: magento2-module, name: Magento_Store, version: ~100.0.0 + preg_match(self::MODULE_NAME_IN_SUGGEST_REGEX, $suggest, $match); + if (isset($match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX])) { + $magentoModuleNames[] = $match[self::MODULE_NAME_IN_SUGGEST_REGEX_INDEX]; + } + } + + return array_unique($magentoModuleNames); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php new file mode 100644 index 000000000..f10c0b418 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerInstall.php @@ -0,0 +1,102 @@ +isInstalledPackageOfType($packageName, self::TEST_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if package is a magento package + * + * @param string $packageName + * @return boolean + */ + public function isMagentoPackage($packageName) + { + return $this->isInstalledPackageOfType($packageName, self::MAGENTO_MODULE_PACKAGE_TYPE); + } + + /** + * Determines if an installed package is of a certain type + * + * @param string $packageName + * @param string $packageType + * @return boolean + */ + public function isInstalledPackageOfType($packageName, $packageType) + { + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if (($package->getName() == $packageName) && ($package->getType() == $packageType)) { + return true; + } + } + return false; + } + + /** + * Collect all installed mftf test packages from composer lock + * + * @return array + */ + public function getInstalledTestPackages() + { + $packages = []; + /** @var CompletePackageInterface $package */ + foreach ($this->getLocker()->getLockedRepository()->getPackages() as $package) { + if ($package->getType() == self::TEST_MODULE_PACKAGE_TYPE) { + $packages[$package->getName()] = [ + self::PACKAGE_NAME => $package->getName(), + self::PACKAGE_TYPE => $package->getType(), + self::PACKAGE_VERSION => $package->getPrettyVersion(), + self::PACKAGE_DESCRIPTION => $package->getDescription(), + self::PACKAGE_SUGGESTS => $package->getSuggests(), + self::PACKAGE_REQUIRES => $package->getRequires(), + self::PACKAGE_DEVREQUIRES => $package->getDevRequires(), + self::PACKAGE_SUGGESTED_MAGENTO_MODULES => $this->parseSuggestsForMagentoModuleNames( + $package->getSuggests() + ), + self::PACKAGE_INSTALLEDPATH => $this->getComposer()->getInstallationManager() + ->getInstallPath($package) + ]; + } + } + return $packages; + } + + /** + * Load locker + * + * @return \Composer\Package\Locker + */ + private function getLocker() + { + if (!$this->locker) { + $this->locker = $this->getComposer()->getLocker(); + } + return $this->locker; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php new file mode 100644 index 000000000..0a6bfdca2 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Composer/ComposerPackage.php @@ -0,0 +1,161 @@ +getRootPackage(); + return $package->getPrettyName(); + } + + /** + * Retrieve package type from composer json + * + * @return string + */ + public function getType() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getType(); + } + + /** + * Retrieve package version from composer json + * + * @return string + */ + public function getVersion() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getPrettyVersion(); + } + + /** + * Retrieve package description from composer json + * + * @return string + */ + public function getDescription() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDescription(); + } + + /** + * Retrieve package require from composer json + * + * @return array + */ + public function getRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getRequires(); + } + + /** + * Retrieve package dev require from composer json + * + * @return array + */ + public function getDevRequires() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getDevRequires(); + } + + /** + * Retrieve package suggest from composer json + * + * @return array + */ + public function getSuggests() + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getRootPackage(); + return $package->getSuggests(); + } + + /** + * Retrieve magento module names in package's suggest + * + * @return array + */ + public function getSuggestedMagentoModules() + { + return $this->parseSuggestsForMagentoModuleNames($this->getSuggests()); + } + + /** + * Determines if package is a mftf test package + * + * @return boolean + */ + public function isMftfTestPackage() + { + return ($this->getType() == self::TEST_MODULE_PACKAGE_TYPE) ? true : false; + } + + /** + * Retrieve packages require for given package name and version + * + * @param string $name + * @param string $version + * @return array + */ + public function getRequiresForPackage($name, $version) + { + /** @var \Composer\Package\CompletePackage $package */ + $package = $this->getComposer()->getRepositoryManager()->findPackage($name, $version); + return $package->getRequires(); + } + + /** + * Check if a package is required in composer json + * + * @param string $packageName + * @return boolean + */ + public function isPackageRequiredInComposerJson($packageName) + { + return (in_array($packageName, array_keys($this->getRequires())) + || in_array($packageName, array_keys($this->getDevRequires())) + ); + } + + /** + * Get root package + * + * @return \Composer\Package\RootPackageInterface + */ + public function getRootPackage() + { + if (!$this->rootPackage) { + $this->rootPackage = $this->getComposer()->getPackage(); + } + return $this->rootPackage; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter.php b/src/Magento/FunctionalTestingFramework/Config/Converter.php index dcc67e5cf..cf61805dc 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter.php @@ -83,6 +83,7 @@ public function convert($source) * @param \DOMNodeList|array $elements * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @TODO ported magento code - to be refactored later */ protected function convertXml($elements) { diff --git a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php index 4350633e7..001a811d9 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Config/Converter/Dom/Flat.php @@ -70,6 +70,7 @@ protected function getNodeAttributes(\DOMNode $node) * @return string|array * @throws \UnexpectedValueException * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ public function convert(\DOMNode $source, $basePath = '') { diff --git a/src/Magento/FunctionalTestingFramework/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Config/Dom.php index 566fbd5b3..6d10811ca 100644 --- a/src/Magento/FunctionalTestingFramework/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Config/Dom.php @@ -145,6 +145,7 @@ protected function mergeNode(\DOMElement $node, $parentPath) * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO Ported magento code - to be refactored later */ protected function mergeMatchingNode(\DomElement $node, $parentPath, $matchedNode, $path) { diff --git a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php index 6b421a332..5102a3fdf 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\Console; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; @@ -94,14 +95,18 @@ protected function execute(InputInterface $input, OutputInterface $output) $process->setWorkingDirectory(TESTS_BP); $process->setIdleTimeout(600); $process->setTimeout(0); - $process->run( + $codeceptReturnCode = $process->run( function ($type, $buffer) use ($output) { - if ($output->isVerbose()) { - $output->write($buffer); - } + $output->write($buffer); } ); + if ($codeceptReturnCode !== 0) { + throw new TestFrameworkException( + "The codecept build command failed unexpectedly. Please see the above output for more details." + ); + } + if ($input->getOption('upgrade')) { $upgradeCommand = new UpgradeTestsCommand(); $upgradeOptions = new ArrayInput(['path' => TESTS_MODULE_PATH]); @@ -133,9 +138,7 @@ private function generateConfigFiles(OutputInterface $output) $output->writeln("codeception.yml configuration successfully applied."); } - if ($output->isVerbose()) { - $output->writeln("codeception.yml applied to " . TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml'); - } + $output->writeln("codeception.yml applied to " . TESTS_BP . DIRECTORY_SEPARATOR . 'codeception.yml'); // copy the functional suite yml, will only copy if there are differences between the template the destination $fileSystem->copy( @@ -144,10 +147,8 @@ private function generateConfigFiles(OutputInterface $output) ); $output->writeln('functional.suite.yml configuration successfully applied.'); - if ($output->isVerbose()) { - $output->writeln("functional.suite.yml applied to " . - TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml'); - } + $output->writeln("functional.suite.yml applied to " . + TESTS_BP . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional.suite.yml'); $fileSystem->copy( FW_BP . '/etc/config/.credentials.example', diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 34edeee1d..47a966f84 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -9,6 +9,7 @@ use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; use Magento\FunctionalTestingFramework\DataGenerator\Util\GenerationDataReferenceResolver; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; /** @@ -161,7 +162,6 @@ public function getAllData() * @param integer $uniquenessFormat * @return string|null * @throws TestFrameworkException - * @SuppressWarnings(PHPMD) */ public function getDataByName($name, $uniquenessFormat) { @@ -177,12 +177,24 @@ public function getDataByName($name, $uniquenessFormat) throw new TestFrameworkException($exceptionMessage); } - $name_lower = strtolower($name); - if ($this->data === null) { return null; } + return $this->resolveDataReferences($name, $uniquenessFormat); + } + /** + * Resolves data references in entities while generating static test files. + * + * @param string $name + * @param integer $uniquenessFormat + * @return string|null + * @throws TestFrameworkException + * @throws TestReferenceException + */ + private function resolveDataReferences($name, $uniquenessFormat) + { + $name_lower = strtolower($name); $dataReferenceResolver = new GenerationDataReferenceResolver(); if (array_key_exists($name_lower, $this->data)) { if (is_array($this->data[$name_lower])) { diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php index 6d1ebc3fc..b2b4fb0e4 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/OperationDataArrayResolver.php @@ -13,6 +13,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Util\OperationElementExtractor; use Magento\FunctionalTestingFramework\DataGenerator\Util\RuntimeDataReferenceResolver; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; class OperationDataArrayResolver { @@ -66,7 +67,6 @@ public function __construct($dependentEntities = null) * @param boolean $fromArray * @return array * @throws \Exception - * @SuppressWarnings(PHPMD) */ public function resolveOperationDataArray($entityObject, $operationMetadata, $operation, $fromArray = false) { @@ -120,7 +120,19 @@ public function resolveOperationDataArray($entityObject, $operationMetadata, $op ); } } + return $this->resolveRunTimeDataReferences($operationDataArray, $entityObject); + } + /** + * Resolve data references at run time. + * @param array $operationDataArray + * @param EntityDataObject $entityObject + * @return array + * @throws TestFrameworkException + * @throws TestReferenceException + */ + private function resolveRunTimeDataReferences($operationDataArray, $entityObject) + { $dataReferenceResolver = new RuntimeDataReferenceResolver(); foreach ($operationDataArray as $key => $operationDataValue) { if (is_array($operationDataValue)) { diff --git a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php b/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php index 8d49ad86c..2dfcbec1a 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php @@ -106,8 +106,6 @@ public function beforeTest(TestEvent $e) * @param StepEvent $e * @return void * @throws \Exception - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function beforeStep(StepEvent $e) { @@ -117,7 +115,26 @@ public function beforeStep(StepEvent $e) return; } - // Check if page has changed and reset metric tracking if so + $this->resetMetricTracker($step); + + $metrics = $this->readinessMetrics; + + $this->waitForReadiness($metrics); + + /** @var AbstractMetricCheck $metric */ + foreach ($metrics as $metric) { + $metric->finalizeForStep($step); + } + } + + /** + * Check if page has changed, if so reset metric tracking + * + * @param Step $step + * @return void + */ + private function resetMetricTracker($step) + { if ($this->pageChanged($step)) { $this->logDebug( 'Page URI changed; resetting readiness metric failure tracking', @@ -131,7 +148,17 @@ public function beforeStep(StepEvent $e) $metric->resetTracker(); } } + } + /** + * Wait for page readiness. + * @param array $metrics + * @return void + * @throws \Codeception\Exception\ModuleRequireException + * @throws \Facebook\WebDriver\Exception\NoSuchElementException + */ + private function waitForReadiness($metrics) + { // todo: Implement step parameter to override global timeout configuration if (isset($this->config['timeout'])) { $timeout = intval($this->config['timeout']); @@ -139,8 +166,6 @@ public function beforeStep(StepEvent $e) $timeout = $this->getDriver()->_getConfig()['pageload_timeout']; } - $metrics = $this->readinessMetrics; - try { $this->getDriver()->webDriver->wait($timeout)->until( function () use ($metrics) { @@ -160,11 +185,6 @@ function () use ($metrics) { ); } catch (TimeoutException $exception) { } - - /** @var AbstractMetricCheck $metric */ - foreach ($metrics as $metric) { - $metric->finalizeForStep($step); - } } /** diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php index c8e6c8292..3b2c827d5 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Config.php @@ -153,6 +153,7 @@ public function getPreference($type) * @param string $type * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function collectConfiguration($type) { @@ -194,7 +195,6 @@ protected function collectConfiguration($type) * * @param array $configuration * @return void - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function mergeConfiguration(array $configuration) { @@ -207,28 +207,39 @@ protected function mergeConfiguration(array $configuration) break; default: - $key = ltrim($key, '\\'); - if (isset($curConfig['type'])) { - $this->virtualTypes[$key] = ltrim($curConfig['type'], '\\'); - } - if (isset($curConfig['arguments'])) { - if (!empty($this->mergedArguments)) { - $this->mergedArguments = []; - } - if (isset($this->arguments[$key])) { - $this->arguments[$key] = array_replace($this->arguments[$key], $curConfig['arguments']); - } else { - $this->arguments[$key] = $curConfig['arguments']; - } - } - if (isset($curConfig['shared'])) { - if (!$curConfig['shared']) { - $this->nonShared[$key] = 1; - } else { - unset($this->nonShared[$key]); - } - } - break; + $this->setConfiguration($key, $curConfig); + } + } + } + + /** + * Set configuration + * + * @param string $key + * @param array $config + * @return void + */ + private function setConfiguration($key, $config) + { + $key = ltrim($key, '\\'); + if (isset($config['type'])) { + $this->virtualTypes[$key] = ltrim($config['type'], '\\'); + } + if (isset($config['arguments'])) { + if (!empty($this->mergedArguments)) { + $this->mergedArguments = []; + } + if (isset($this->arguments[$key])) { + $this->arguments[$key] = array_replace($this->arguments[$key], $config['arguments']); + } else { + $this->arguments[$key] = $config['arguments']; + } + } + if (isset($config['shared'])) { + if (!$config['shared']) { + $this->nonShared[$key] = 1; + } else { + unset($this->nonShared[$key]); } } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php index bf11e0003..ba7bc64cd 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Config/Mapper/Dom.php @@ -48,9 +48,6 @@ public function __construct( * @return array * @throws \Exception * @todo this method has high cyclomatic complexity in order to avoid performance issues - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function convert($config) { @@ -83,34 +80,7 @@ public function convert($config) $typeData['type'] = $attributeType->nodeValue; } } - $typeArguments = []; - /** @var \DOMNode $typeChildNode */ - foreach ($node->childNodes as $typeChildNode) { - if ($typeChildNode->nodeType != XML_ELEMENT_NODE) { - continue; - } - switch ($typeChildNode->nodeName) { - case 'arguments': - /** @var \DOMNode $argumentNode */ - foreach ($typeChildNode->childNodes as $argumentNode) { - if ($argumentNode->nodeType != XML_ELEMENT_NODE) { - continue; - } - $argumentName = $argumentNode->attributes->getNamedItem('name')->nodeValue; - $argumentData = $this->argumentParser->parse($argumentNode); - $typeArguments[$argumentName] = $this->argumentInterpreter->evaluate( - $argumentData - ); - } - break; - default: - throw new \Exception( - "Invalid application config. Unknown node: {$typeChildNode->nodeName}." - ); - } - } - - $typeData['arguments'] = $typeArguments; + $typeData['arguments'] = $this->setTypeArguments($node); $output[$typeNodeAttributes->getNamedItem('name')->nodeValue] = $typeData; break; default: @@ -120,4 +90,42 @@ public function convert($config) return $output; } + + /** Read typeChildNodes and set typeArguments + * @param DOMNode $node + * @return mixed + * @throws \Exception + */ + private function setTypeArguments($node) + { + $typeArguments = []; + + foreach ($node->childNodes as $typeChildNode) { + /** @var \DOMNode $typeChildNode */ + if ($typeChildNode->nodeType != XML_ELEMENT_NODE) { + continue; + } + switch ($typeChildNode->nodeName) { + case 'arguments': + /** @var \DOMNode $argumentNode */ + foreach ($typeChildNode->childNodes as $argumentNode) { + if ($argumentNode->nodeType != XML_ELEMENT_NODE) { + continue; + } + $argumentName = $argumentNode->attributes->getNamedItem('name')->nodeValue; + $argumentData = $this->argumentParser->parse($argumentNode); + $typeArguments[$argumentName] = $this->argumentInterpreter->evaluate( + $argumentData + ); + } + break; + + default: + throw new \Exception( + "Invalid application config. Unknown node: {$typeChildNode->nodeName}." + ); + } + } + return $typeArguments; + } } diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php index 9b795d58f..c5882d035 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory.php @@ -103,6 +103,7 @@ public function prepareArguments($object, $method, array $arguments = []) * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function resolveArguments($requestedType, array $parameters, array $arguments = []) { @@ -179,8 +180,10 @@ protected function resolveArguments($requestedType, array $parameters, array $ar * * @param array $array * @return void + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function parseArray(&$array) { @@ -219,8 +222,6 @@ protected function parseArray(&$array) * @param array $arguments * @return object * @throws \Exception - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function create($requestedType, array $arguments = []) { diff --git a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php index dbe6aa497..937be7da6 100644 --- a/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php +++ b/src/Magento/FunctionalTestingFramework/ObjectManager/Factory/Dynamic/Developer.php @@ -87,6 +87,7 @@ public function setObjectManager(\Magento\FunctionalTestingFramework\ObjectManag * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ protected function resolveArguments($requestedType, array $parameters, array $arguments = []) { @@ -171,7 +172,6 @@ protected function parseArray(&$array) * @return object * @throws \Exception * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPCPD) */ public function create($requestedType, array $arguments = []) diff --git a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php index bb6d08f58..989209461 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Page/Config/Dom.php @@ -87,9 +87,9 @@ public function initDom($xml, $filename = null) ); $pageNodes = $dom->getElementsByTagName('page'); $currentModule = - $this->modulePathExtractor->extractModuleName($filename) . - '_' . - $this->modulePathExtractor->getExtensionPath($filename); + $this->modulePathExtractor->getExtensionPath($filename) + . '_' + . $this->modulePathExtractor->extractModuleName($filename); foreach ($pageNodes as $pageNode) { $pageModule = $pageNode->getAttribute("module"); $pageName = $pageNode->getAttribute("name"); diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php index 3abbd40c2..6cfa4e9a8 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -24,7 +24,7 @@ /** * Class TestDependencyCheck * @package Magento\FunctionalTestingFramework\StaticCheck - * @SuppressWarnings(PHPMD) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TestDependencyCheck implements StaticCheckInterface { @@ -74,13 +74,18 @@ class TestDependencyCheck implements StaticCheckInterface */ private $output; + /** + * Array containing all entities after resolving references. + * @var array + */ + private $allEntities = []; + /** * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module * * @param InputInterface $input * @return string * @throws Exception; - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function execute(InputInterface $input) { @@ -158,7 +163,6 @@ private function findErrorsInFileSet($files) } $contents = file_get_contents($filePath); - $allEntities = []; preg_match_all(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, $contents, $braceReferences); preg_match_all(self::ACTIONGROUP_REGEX_PATTERN, $contents, $actionGroupReferences); preg_match_all(self::EXTENDS_REGEX_PATTERN, $contents, $extendReferences); @@ -169,10 +173,52 @@ private function findErrorsInFileSet($files) $braceReferences[1] = array_unique($braceReferences[1]); $braceReferences[2] = array_filter(array_unique($braceReferences[2])); - // Check `data` entities in {{data.field}} or {{data.field('param')}} - foreach ($braceReferences[0] as $reference) { - // trim `{{data.field}}` to `data` - preg_match('/{{([^.]+)/', $reference, $entityName); + // resolve data entity references + $this->resolveDataEntityReferences($braceReferences[0], $contents); + + //resolve entity references + $this->resolveParametrizedReferences($braceReferences[2], $contents); + + // Check actionGroup references + $this->resolveEntityReferences($actionGroupReferences[1]); + + // Check extended objects + $this->resolveEntityReferences($extendReferences[1]); + + // Find violating references and set error output + $violatingReferences = $this->findViolatingReferences($moduleFullName); + $testErrors = $this->setErrorOutput($violatingReferences, $filePath); + } + return $testErrors; + } + + /** + * Drill down into params in {{ref.params('string', $data.key$, entity.reference)}} + * and resolve references. + * + * @param array $braceReferences + * @param string $contents + * @return void + * @throws XmlException + */ + private function resolveParametrizedReferences($braceReferences, $contents) + { + foreach ($braceReferences as $parameterizedReference) { + preg_match( + ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, + $parameterizedReference, + $arguments + ); + $splitArguments = explode(',', ltrim(rtrim($arguments[0], ")"), "(")); + foreach ($splitArguments as $argument) { + // Do nothing for 'string' or $persisted.data$ + if (preg_match(ActionObject::STRING_PARAMETER_REGEX, $argument)) { + continue; + } elseif (preg_match(TestGenerator::PERSISTED_OBJECT_NOTATION_REGEX, $argument)) { + continue; + } + // trim `data.field` to `data` + preg_match('/([^.]+)/', $argument, $entityName); // Double check that {{data.field}} isn't an argument for an ActionGroup $entity = $this->findEntity($entityName[1]); preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); @@ -180,81 +226,105 @@ private function findErrorsInFileSet($files) continue; } if ($entity !== null) { - $allEntities[$entity->getName()] = $entity; + $this->allEntities[$entity->getName()] = $entity; } } + } + } - // Drill down into params in {{ref.params('string', $data.key$, entity.reference)}} - foreach ($braceReferences[2] as $parameterizedReference) { - preg_match( - ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, - $parameterizedReference, - $arguments - ); - $splitArguments = explode(',', ltrim(rtrim($arguments[0], ")"), "(")); - foreach ($splitArguments as $argument) { - // Do nothing for 'string' or $persisted.data$ - if (preg_match(ActionObject::STRING_PARAMETER_REGEX, $argument)) { - continue; - } elseif (preg_match(TestGenerator::PERSISTED_OBJECT_NOTATION_REGEX, $argument)) { - continue; - } - // trim `data.field` to `data` - preg_match('/([^.]+)/', $argument, $entityName); - // Double check that {{data.field}} isn't an argument for an ActionGroup - $entity = $this->findEntity($entityName[1]); - preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); - if (array_search($entityName[1], $possibleArgument[1]) !== false) { - continue; - } - if ($entity !== null) { - $allEntities[$entity->getName()] = $entity; - } - } + /** + * Check `data` entities in {{data.field}} or {{data.field('param')}} and resolve references + * + * @param array $braceReferences + * @param string $contents + * @return void + * @throws XmlException + + */ + private function resolveDataEntityReferences($braceReferences, $contents) + { + foreach ($braceReferences as $reference) { + // trim `{{data.field}}` to `data` + preg_match('/{{([^.]+)/', $reference, $entityName); + // Double check that {{data.field}} isn't an argument for an ActionGroup + $entity = $this->findEntity($entityName[1]); + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); + if (array_search($entityName[1], $possibleArgument[1]) !== false) { + continue; } - // Check actionGroup references - foreach ($actionGroupReferences[1] as $reference) { - $entity = $this->findEntity($reference); - if ($entity !== null) { - $allEntities[$entity->getName()] = $entity; - } + if ($entity !== null) { + $this->allEntities[$entity->getName()] = $entity; } - // Check extended objects - foreach ($extendReferences[1] as $reference) { - $entity = $this->findEntity($reference); - if ($entity !== null) { - $allEntities[$entity->getName()] = $entity; - } + } + } + + /** + * Resolve entity references + * + * @param array $references + * @return void + * @throws XmlException + */ + private function resolveEntityReferences($references) + { + foreach ($references as $reference) { + $entity = $this->findEntity($reference); + if ($entity !== null) { + $this->allEntities[$entity->getName()] = $entity; } + } + } - $currentModule = $this->moduleNameToComposerName[$moduleFullName]; - $modulesReferencedInTest = $this->getModuleDependenciesFromReferences($allEntities, $currentModule); - $moduleDependencies = $this->flattenedDependencies[$moduleFullName]; - // Find Violations - $violatingReferences = []; - foreach ($modulesReferencedInTest as $entityName => $files) { - $valid = false; - foreach ($files as $module) { - if (array_key_exists($module, $moduleDependencies) || $module == $currentModule) { - $valid = true; - break; - } - } - if (!$valid) { - $violatingReferences[$entityName] = $files; + /** + * Find violating references + * + * @param string $moduleName + * @return array + */ + private function findViolatingReferences($moduleName) + { + // Find Violations + $violatingReferences = []; + $currentModule = $this->moduleNameToComposerName[$moduleName]; + $modulesReferencedInTest = $this->getModuleDependenciesFromReferences($this->allEntities); + $moduleDependencies = $this->flattenedDependencies[$moduleName]; + foreach ($modulesReferencedInTest as $entityName => $files) { + $valid = false; + foreach ($files as $module) { + if (array_key_exists($module, $moduleDependencies) || $module == $currentModule) { + $valid = true; + break; } } + if (!$valid) { + $violatingReferences[$entityName] = $files; + } + } - if (!empty($violatingReferences)) { - // Build error output - $errorOutput = "\nFile \"{$filePath->getRealPath()}\""; - $errorOutput .= "\ncontains entity references that violate dependency constraints:\n\t\t"; - foreach ($violatingReferences as $entityName => $files) { - $errorOutput .= "\n\t {$entityName} from module(s): " . implode(", ", $files); - } - $testErrors[$filePath->getRealPath()][] = $errorOutput; + return $violatingReferences; + } + + /** + * Builds and returns error output for violating references + * + * @param array $violatingReferences + * @param string $path + * @return mixed + */ + private function setErrorOutput($violatingReferences, $path) + { + $testErrors = []; + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$path->getRealPath()}\""; + $errorOutput .= "\ncontains entity references that violate dependency constraints:\n\t\t"; + foreach ($violatingReferences as $entityName => $files) { + $errorOutput .= "\n\t {$entityName} from module(s): " . implode(", ", $files); } + $testErrors[$path->getRealPath()][] = $errorOutput; } + return $testErrors; } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 642671246..7ef3d5e8c 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -26,12 +26,19 @@ class SuiteObjectExtractor extends BaseObjectExtractor const TEST_TAG_NAME = 'test'; const GROUP_TAG_NAME = 'group'; + /** + * TestHookObjectExtractor initialized in constructor. + * + * @var TestHookObjectExtractor + */ + private $testHookObjectExtractor; + /** * SuiteObjectExtractor constructor */ public function __construct() { - // empty constructor + $this->testHookObjectExtractor = new TestHookObjectExtractor(); } /** @@ -40,14 +47,13 @@ public function __construct() * @param array $parsedSuiteData * @return array * @throws XmlException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) * @throws \Exception + * + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function parseSuiteDataIntoObjects($parsedSuiteData) { $suiteObjects = []; - $testHookObjectExtractor = new TestHookObjectExtractor(); // make sure there are suites defined before trying to parse as objects. if (!array_key_exists(self::SUITE_ROOT_TAG, $parsedSuiteData)) { @@ -60,70 +66,26 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) continue; } - // validate the name used isn't using special char or the "default" reserved name - NameValidationUtil::validateName($parsedSuite[self::NAME], 'Suite'); - if ($parsedSuite[self::NAME] == 'default') { - throw new XmlException("A Suite can not have the name \"default\""); - } - - $suiteHooks = []; - - //Check for collisions between suite name and existing group name - $suiteName = $parsedSuite[self::NAME]; - $testGroupConflicts = TestObjectHandler::getInstance()->getTestsByGroup($suiteName); - if (!empty($testGroupConflicts)) { - $testGroupConflictsFileNames = ""; - foreach ($testGroupConflicts as $test) { - $testGroupConflictsFileNames .= $test->getFilename() . "\n"; - } - $exceptionmessage = "\"Suite names and Group names can not have the same value. \t\n" . - "Suite: \"{$suiteName}\" also exists as a group annotation in: \n{$testGroupConflictsFileNames}"; - throw new XmlException($exceptionmessage); - } + $this->validateSuiteName($parsedSuite); //extract include and exclude references $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; - // resolve references as test objects + //resolve references as test objects $includeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToInclude); $excludeTests = $this->extractTestObjectsFromSuiteRef($groupTestsToExclude); // parse any object hooks - if (array_key_exists(TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_BEFORE_HOOK] = $testHookObjectExtractor->extractHook( - $parsedSuite[self::NAME], - TestObjectExtractor::TEST_BEFORE_HOOK, - $parsedSuite[TestObjectExtractor::TEST_BEFORE_HOOK] - ); - } - if (array_key_exists(TestObjectExtractor::TEST_AFTER_HOOK, $parsedSuite)) { - $suiteHooks[TestObjectExtractor::TEST_AFTER_HOOK] = $testHookObjectExtractor->extractHook( - $parsedSuite[self::NAME], - TestObjectExtractor::TEST_AFTER_HOOK, - $parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK] - ); - } + $suiteHooks = $this->parseObjectHooks($parsedSuite); - if (count($suiteHooks) == 1) { - throw new XmlException(sprintf( - "Suites that contain hooks must contain both a 'before' and an 'after' hook. Suite: \"%s\"", - $parsedSuite[self::NAME] - )); - } - // check if suite hooks are empty/not included and there are no included tests/groups/modules - $noHooks = count($suiteHooks) == 0 || - ( - empty($suiteHooks['before']->getActions()) && - empty($suiteHooks['after']->getActions()) - ); - // if suite body is empty throw error - if ($noHooks && empty($includeTests) && empty($excludeTests)) { + //throw an exception if suite is empty + if ($this->isSuiteEmpty($suiteHooks, $includeTests, $excludeTests)) { throw new XmlException(sprintf( "Suites must not be empty. Suite: \"%s\"", $parsedSuite[self::NAME] )); - } + }; // add all test if include tests is completely empty if (empty($includeTests)) { @@ -142,6 +104,95 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) return $suiteObjects; } + /** + * Throws exception for suite names meeting the below conditions: + * 1. the name used is using special char or the "default" reserved name + * 2. collisions between suite name and existing group name + * + * @param array $parsedSuite + * @return void + * @throws XmlException + */ + private function validateSuiteName($parsedSuite) + { + //check if name used is using special char or the "default" reserved name + NameValidationUtil::validateName($parsedSuite[self::NAME], 'Suite'); + if ($parsedSuite[self::NAME] == 'default') { + throw new XmlException("A Suite can not have the name \"default\""); + } + + $suiteName = $parsedSuite[self::NAME]; + //check for collisions between suite and existing group names + $testGroupConflicts = TestObjectHandler::getInstance()->getTestsByGroup($suiteName); + if (!empty($testGroupConflicts)) { + $testGroupConflictsFileNames = ""; + foreach ($testGroupConflicts as $test) { + $testGroupConflictsFileNames .= $test->getFilename() . "\n"; + } + $exceptionmessage = "\"Suite names and Group names can not have the same value. \t\n" . + "Suite: \"{$suiteName}\" also exists as a group annotation in: \n{$testGroupConflictsFileNames}"; + throw new XmlException($exceptionmessage); + } + } + + /** + * Parse object hooks + * + * @param array $parsedSuite + * @return array + * @throws XmlException + */ + private function parseObjectHooks($parsedSuite) + { + $suiteHooks = []; + + if (array_key_exists(TestObjectExtractor::TEST_BEFORE_HOOK, $parsedSuite)) { + $suiteHooks[TestObjectExtractor::TEST_BEFORE_HOOK] = $this->testHookObjectExtractor->extractHook( + $parsedSuite[self::NAME], + TestObjectExtractor::TEST_BEFORE_HOOK, + $parsedSuite[TestObjectExtractor::TEST_BEFORE_HOOK] + ); + } + if (array_key_exists(TestObjectExtractor::TEST_AFTER_HOOK, $parsedSuite)) { + $suiteHooks[TestObjectExtractor::TEST_AFTER_HOOK] = $this->testHookObjectExtractor->extractHook( + $parsedSuite[self::NAME], + TestObjectExtractor::TEST_AFTER_HOOK, + $parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK] + ); + } + + if (count($suiteHooks) == 1) { + throw new XmlException(sprintf( + "Suites that contain hooks must contain both a 'before' and an 'after' hook. Suite: \"%s\"", + $parsedSuite[self::NAME] + )); + } + return $suiteHooks; + } + + /** + * Check if suite hooks are empty/not included and there are no included tests/groups/modules + * + * @param array $suiteHooks + * @param array $includeTests + * @param array $excludeTests + * @return boolean + */ + private function isSuiteEmpty($suiteHooks, $includeTests, $excludeTests) + { + + $noHooks = count($suiteHooks) == 0 || + ( + empty($suiteHooks['before']->getActions()) && + empty($suiteHooks['after']->getActions()) + ); + + if ($noHooks && empty($includeTests) && empty($excludeTests)) { + return true; + } + return false; + } + /** * Wrapper method for resolving suite reference data, checks type of suite reference and calls corresponding * resolver for each suite reference. diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php index f888518aa..0769f5ebf 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Converter/Dom/Flat.php @@ -70,6 +70,7 @@ public function convert($source) * @return string|array * @throws \UnexpectedValueException * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * Revisited to reduce cyclomatic complexity, left unrefactored for readability */ public function convertXml(\DOMNode $source, $basePath = '') { diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index acad18572..d5420e355 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -118,6 +118,17 @@ public function extractTestData($testData) //Override features with module name if present, populates it otherwise $testAnnotations["features"] = [$module]; + // Always try to append test file names in description annotation, i.e. displaying test files title only + // when $fileNames is not available + if (!isset($testAnnotations["description"])) { + $testAnnotations["description"] = []; + } + $description = $testAnnotations["description"][0] ?? ''; + $testAnnotations["description"][0] = $this->appendFileNamesInDescriptionAnnotation( + $description, + $fileNames + ); + // extract before if (array_key_exists(self::TEST_BEFORE_HOOK, $testData) && is_array($testData[self::TEST_BEFORE_HOOK])) { $testHooks[self::TEST_BEFORE_HOOK] = $this->testHookObjectExtractor->extractHook( @@ -155,4 +166,32 @@ public function extractTestData($testData) throw new XmlException($exception->getMessage() . ' in Test ' . $filename); } } + + /** + * Append names of test files, including merge files, in description annotation + * + * @param string $description + * @param array $fileNames + * + * @return string + */ + private function appendFileNamesInDescriptionAnnotation($description, $fileNames) + { + $description .= '

Test files

'; + + foreach ($fileNames as $fileName) { + if (!empty($fileName) && realpath($fileName) !== false) { + $fileName = realpath($fileName); + $relativeFileName = ltrim( + str_replace(MAGENTO_BP, '', $fileName), + DIRECTORY_SEPARATOR + ); + if (!empty($relativeFileName)) { + $description .= $relativeFileName . '
'; + } + } + } + + return $description; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php new file mode 100644 index 000000000..210c10fdd --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/ComposerModuleResolver.php @@ -0,0 +1,179 @@ +installedTestModules) { + return $this->installedTestModules; + } + + if (!file_exists($rootComposerFile) || basename($rootComposerFile, '.json') != 'composer') { + throw new TestFrameworkException("Invalid root composer json file: {$rootComposerFile}"); + } + + $this->installedTestModules = []; + $composer = new ComposerInstall($rootComposerFile); + + foreach ($composer->getInstalledTestPackages() as $packageName => $packageData) { + $suggestedModuleNames = $packageData[ComposerInstall::PACKAGE_SUGGESTED_MAGENTO_MODULES]; + $path = $packageData[ComposerInstall::PACKAGE_INSTALLEDPATH]; + $this->installedTestModules[$path] = $suggestedModuleNames; + } + return $this->installedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directories + * + * @param array $directories + * @return array + * @throws TestFrameworkException + */ + public function getTestModulesFromPaths($directories) + { + if (null !== $this->searchedTestModules) { + return $this->searchedTestModules; + } + + $this->searchedTestModules = []; + foreach ($directories as $directory) { + $this->searchedTestModules = array_merge_recursive( + $this->searchedTestModules, + $this->getTestModules($directory) + ); + } + return $this->searchedTestModules; + } + + /** + * Get code paths by searching test module composer json file from input directory + * + * @param string $directory + * @return array + * @throws TestFrameworkException + */ + private function getTestModules($directory) + { + $normalizedDir = realpath($directory); + if (!is_dir($normalizedDir)) { + throw new TestFrameworkException("Invalid directory: {$directory}"); + } + + // Find all composer json files under directory + $modules = []; + $fileList = $this->findComposerJsonFilesAtDepth($normalizedDir, 2); + foreach ($fileList as $file) { + // Parse composer json for test module name and path information + $composerInfo = new ComposerPackage($file); + if ($composerInfo->isMftfTestPackage()) { + $modulePath = str_replace( + DIRECTORY_SEPARATOR . 'composer.json', + '', + $file + ); + $suggestedMagentoModuleNames = $composerInfo->getSuggestedMagentoModules(); + if (array_key_exists($modulePath, $modules)) { + $modules[$modulePath] = array_merge($modules[$modulePath], $suggestedMagentoModuleNames); + } else { + $modules[$modulePath] = $suggestedMagentoModuleNames; + } + } + } + return $modules; + } + + /** + * Find absolute paths of all composer json files in a given directory + * + * @param string $directory + * @return array + */ + private function findAllComposerJsonFiles($directory) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = []; + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive($jsonFileList, self::findAllComposerJsonFiles($dir)); + } + + $curJsonFiles = glob($directory . $jsonPattern); + if ($curJsonFiles !== false && !empty($curJsonFiles)) { + $jsonFileList = array_merge_recursive($jsonFileList, $curJsonFiles); + } + return $jsonFileList; + } + + /** + * Find absolute paths of all composer json files in a given directory at certain depths + * + * @param string $directory + * @param integer $depth + * @return array + */ + private function findComposerJsonFilesAtDepth($directory, $depth) + { + $directory = realpath($directory); + $jsonPattern = DIRECTORY_SEPARATOR . "composer.json"; + $subDirectoryPattern = DIRECTORY_SEPARATOR . "*"; + + $jsonFileList = []; + if ($depth > 0) { + foreach (glob($directory . $subDirectoryPattern, GLOB_ONLYDIR) as $dir) { + $jsonFileList = array_merge_recursive( + $jsonFileList, + self::findComposerJsonFilesAtDepth($dir, $depth-1) + ); + } + } elseif ($depth == 0) { + $jsonFileList = glob($directory . $jsonPattern); + if ($jsonFileList === false) { + $jsonFileList = []; + } + } + return $jsonFileList; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php b/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php index 2642a715f..7a28f8969 100644 --- a/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/DocGenerator.php @@ -15,7 +15,6 @@ /** * Class TestGenerator - * @SuppressWarnings(PHPMD) */ class DocGenerator { diff --git a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php index 33e559ea8..80b556f9a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModulePathExtractor.php @@ -11,41 +11,89 @@ */ class ModulePathExtractor { - const MAGENTO = 'Magento'; + const SPLIT_DELIMITER = '_'; + + /** + * Test module paths + * + * @var array + */ + private $testModulePaths = []; + + /** + * ModulePathExtractor constructor + */ + public function __construct() + { + $verbosePath = true; + if (empty($this->testModulePaths)) { + $this->testModulePaths = ModuleResolver::getInstance()->getModulesPath($verbosePath); + } + } /** * Extracts module name from the path given + * * @param string $path * @return string */ public function extractModuleName($path) { - if (empty($path)) { - return "NO MODULE DETECTED"; - } - $paths = explode(DIRECTORY_SEPARATOR, $path); - if (count($paths) < 3) { + $key = $this->extractKeyByPath($path); + if (empty($key)) { return "NO MODULE DETECTED"; - } elseif ($paths[count($paths)-3] == "Mftf") { - // app/code/Magento/[Analytics]/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-5]; } - // dev/tests/acceptance/tests/functional/Magento/FunctionalTest/[Analytics]/Test/SomeText.xml - return $paths[count($paths)-3]; + $parts = $this->splitKeyForParts($key); + return isset($parts[1]) ? $parts[1] : "NO MODULE DETECTED"; } /** - * Extracts the extension form the path, Magento for dev/tests/acceptance, [name] before module otherwise + * Extracts vendor name for module from the path given + * * @param string $path * @return string */ public function getExtensionPath($path) { - $paths = explode(DIRECTORY_SEPARATOR, $path); - if ($paths[count($paths)-3] == "Mftf") { - // app/code/[Magento]/Analytics/Test/Mftf/Test/SomeText.xml - return $paths[count($paths)-6]; + $key = $this->extractKeyByPath($path); + if (empty($key)) { + return "NO VENDOR DETECTED"; + } + $parts = $this->splitKeyForParts($key); + return isset($parts[0]) ? $parts[0] : "NO VENDOR DETECTED"; + } + + /** + * Split key by SPLIT_DELIMITER and return parts array + * + * @param string $key + * @return array + */ + private function splitKeyForParts($key) + { + $parts = explode(self::SPLIT_DELIMITER, $key); + return count($parts) == 2 ? $parts : []; + } + + /** + * Extract module name key by path + * + * @param string $path + * @return string + */ + private function extractKeyByPath($path) + { + $shortenedPath = dirname(dirname($path)); + // Ignore this path if we cannot go to parent directory two levels up + if (empty($shortenedPath) || $shortenedPath === '.') { + return ''; + } + + foreach ($this->testModulePaths as $key => $value) { + if ($value == $shortenedPath) { + return $key; + } } - return self::MAGENTO; + return ''; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 16e6037fa..d776603d1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -15,6 +15,8 @@ * Class ModuleResolver, resolve module path based on enabled modules of target Magento instance. * * @api + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class ModuleResolver { @@ -38,6 +40,25 @@ class ModuleResolver */ const REGISTRAR_CLASS = "\Magento\Framework\Component\ComponentRegistrar"; + const TEST_MFTF_PATTERN = 'Test' . DIRECTORY_SEPARATOR . 'Mftf'; + const VENDOR = 'vendor'; + const APP_CODE = 'app' . DIRECTORY_SEPARATOR . "code"; + const DEV_TESTS = 'dev' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'acceptance' + . DIRECTORY_SEPARATOR + . 'tests' + . DIRECTORY_SEPARATOR + . 'functional'; + const DEPRECATED_DEV_TESTS = DIRECTORY_SEPARATOR + . self:: DEV_TESTS + . DIRECTORY_SEPARATOR + . "Magento" + . DIRECTORY_SEPARATOR + . "FunctionalTest"; + /** * Enabled modules. * @@ -52,6 +73,13 @@ class ModuleResolver */ protected $enabledModulePaths = null; + /** + * Name and path for enabled modules + * + * @var array|null + */ + protected $enabledModuleNameAndPaths = null; + /** * Configuration instance. * @@ -110,6 +138,27 @@ class ModuleResolver 'SampleTests', 'SampleTemplates' ]; + /** + * Registered module list in magento system under test + * + * @var array + */ + private $registeredModuleList = []; + + /** + * Composer json based test module paths + * + * @var array + */ + private $composerJsonModulePaths = null; + + /** + * Composer installed test module paths + * + * @var array + */ + private $composerInstalledModulePaths = null; + /** * Get ModuleResolver instance. * @@ -138,6 +187,7 @@ private function __construct() * Return an array of enabled modules of target Magento instance. * * @return array + * @throws TestFrameworkException */ public function getEnabledModules() { @@ -179,50 +229,84 @@ public function getEnabledModules() return $this->enabledModules; } - /** - * Return an array of module whitelist that not exist in target Magento instance. - * - * @return array - */ - protected function getModuleWhitelist() - { - $moduleWhitelist = getenv(self::MODULE_WHITELIST); - - if (empty($moduleWhitelist)) { - return []; - } - return array_map('trim', explode(',', $moduleWhitelist)); - } - /** * Return the modules path based on which modules are enabled in the target Magento instance. * + * @param boolean $verbosePath * @return array */ - public function getModulesPath() + public function getModulesPath($verbosePath = false) { - if (isset($this->enabledModulePaths)) { + if (isset($this->enabledModulePaths) && !$verbosePath) { return $this->enabledModulePaths; } + if (isset($this->enabledModuleNameAndPaths) && $verbosePath) { + return $this->enabledModuleNameAndPaths; + } + + // Find test modules paths by searching patterns (Test/Mftf, etc) $allModulePaths = $this->aggregateTestModulePaths(); + // Find test modules paths by searching test composer.json files + $composerBasedModulePaths = $this->aggregateTestModulePathsFromComposerJson(); + + // Find test modules paths by querying composer installed packages + $composerBasedModulePaths = array_merge( + $composerBasedModulePaths, + $this->aggregateTestModulePathsFromComposerInstaller() + ); + + // Merge test module paths altogether + $allModulePaths = $this->mergeModulePaths($allModulePaths, $composerBasedModulePaths); + + // Normalize module names if we get registered module names from Magento system + $allModulePaths = $this->normalizeModuleNames($allModulePaths); + if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { + $allModulePaths = $this->flipAndSortModulePathsArray($allModulePaths, true); $this->enabledModulePaths = $this->applyCustomModuleMethods($allModulePaths); return $this->enabledModulePaths; } $enabledModules = array_merge($this->getEnabledModules(), $this->getModuleWhitelist()); - $enabledDirectoryPaths = $this->getEnabledDirectoryPaths($enabledModules, $allModulePaths); - + $enabledDirectoryPaths = $this->flipAndFilterModulePathsArray($allModulePaths, $enabledModules); $this->enabledModulePaths = $this->applyCustomModuleMethods($enabledDirectoryPaths); + return $this->enabledModulePaths; } + /** + * Sort files according module sequence. + * + * @param array $files + * @return array + */ + public function sortFilesByModuleSequence(array $files) + { + return $this->sequenceSorter->sort($files); + } + + /** + * Return an array of module whitelist that not exist in target Magento instance. + * + * @return array + */ + protected function getModuleWhitelist() + { + $moduleWhitelist = getenv(self::MODULE_WHITELIST); + + if (empty($moduleWhitelist)) { + return []; + } + return array_map('trim', explode(',', $moduleWhitelist)); + } + /** * Retrieves all module directories which might contain pertinent test code. * * @return array + * @throws TestFrameworkException */ private function aggregateTestModulePaths() { @@ -235,13 +319,14 @@ private function aggregateTestModulePaths() $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); - $vendorCodePath = DIRECTORY_SEPARATOR . "vendor"; - $appCodePath = DIRECTORY_SEPARATOR . "app" . DIRECTORY_SEPARATOR . "code"; + $vendorCodePath = DIRECTORY_SEPARATOR . self::VENDOR; + $appCodePath = DIRECTORY_SEPARATOR . self::APP_CODE; $codePathsToPattern = [ $modulePath => '', - $magentoBaseCodePath . $vendorCodePath => 'Test' . DIRECTORY_SEPARATOR . 'Mftf', - $magentoBaseCodePath . $appCodePath => 'Test' . DIRECTORY_SEPARATOR . 'Mftf' + $magentoBaseCodePath . $vendorCodePath => self::TEST_MFTF_PATTERN, + $magentoBaseCodePath . $appCodePath => self::TEST_MFTF_PATTERN, + $magentoBaseCodePath . self::DEPRECATED_DEV_TESTS => '' ]; foreach ($codePathsToPattern as $codePath => $pattern) { @@ -269,8 +354,6 @@ private function globRelevantPaths($testPath, $pattern) $relevantPaths = $this->globRelevantWrapper($testPath, $pattern); } - $allComponents = $this->getRegisteredModuleList(); - foreach ($relevantPaths as $codePath) { // Reduce magento/app/code/Magento/AdminGws/ to magento/app/code/Magento/AdminGws to read symlink // Symlinks must be resolved otherwise they will not match Magento's filepath to the module @@ -278,9 +361,8 @@ private function globRelevantPaths($testPath, $pattern) if (is_link($potentialSymlink)) { $codePath = realpath($potentialSymlink) . DIRECTORY_SEPARATOR . $pattern; } - - $mainModName = array_search($codePath, $allComponents) ?: basename(str_replace($pattern, '', $codePath)); - $modulePaths[$mainModName][] = $codePath; + $mainModName = basename(str_replace($pattern, '', $codePath)); + $modulePaths[$codePath] = [$mainModName]; if (MftfApplicationConfig::getConfig()->verboseEnabled()) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->debug( @@ -290,6 +372,22 @@ private function globRelevantPaths($testPath, $pattern) } } + /* TODO uncomment this to show deprecation warning when we ready to fully deliver test packaging feature + if (strpos($testPath, self::DEPRECATED_DEV_TESTS) !== false && !empty($modulePaths)) { + $deprecatedPath = ltrim(self::DEPRECATED_DEV_TESTS, DIRECTORY_SEPARATOR); + $suggestedPath = self::DEV_TESTS . DIRECTORY_SEPARATOR . 'Magento'; + $message = "DEPRECATION: Found MFTF test modules in the deprecated path: $deprecatedPath." + . " Move these test modules to $suggestedPath."; + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warning($message); + } + // Suppress print during unit testing + if (MftfApplicationConfig::getConfig()->getPhase() !== MftfApplicationConfig::UNIT_TEST_PHASE) { + print ("\n$message\n\n"); + } + } + */ return $modulePaths; } @@ -314,46 +412,259 @@ private static function globRelevantWrapper($testPath, $pattern) } /** - * Takes a multidimensional array of module paths and flattens to return a one dimensional array of test paths + * Aggregate all code paths with test module composer json files * - * @param array $modulePaths * @return array */ - private function flattenAllModulePaths($modulePaths) + private function aggregateTestModulePathsFromComposerJson() { - $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($modulePaths)); - $resultArray = []; + // Define the module paths + $magentoBaseCodePath = MAGENTO_BP; - foreach ($it as $value) { - $resultArray[] = $value; + // Define the module paths from default TESTS_MODULE_PATH + $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; + $modulePath = rtrim($modulePath, DIRECTORY_SEPARATOR); + + $searchCodePaths = [ + $magentoBaseCodePath . DIRECTORY_SEPARATOR . self::DEV_TESTS, + ]; + + // Add TESTS_MODULE_PATH if it's not included + if (array_search($modulePath, $searchCodePaths) === false) { + $searchCodePaths[] = $modulePath; } - return $resultArray; + return $this->getComposerJsonTestModulePaths($searchCodePaths); } /** - * Runs through enabled modules and maps them known module paths by name. - * @param array $enabledModules - * @param array $allModulePaths + * Retrieve all module code paths that have test module composer json files + * + * @param array $codePaths * @return array */ - private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) + private function getComposerJsonTestModulePaths($codePaths) { - $enabledDirectoryPaths = []; - foreach ($enabledModules as $magentoModuleName) { - if (!isset($this->knownDirectories[$magentoModuleName]) && !isset($allModulePaths[$magentoModuleName])) { - continue; - } elseif (isset($this->knownDirectories[$magentoModuleName]) - && !isset($allModulePaths[$magentoModuleName])) { - LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warn( - "Known directory could not match to an existing path.", - ['knownDirectory' => $magentoModuleName] - ); + if (null !== $this->composerJsonModulePaths) { + return $this->composerJsonModulePaths; + } + try { + $this->composerJsonModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerJsonModulePaths = $resolver->getTestModulesFromPaths($codePaths); + } catch (TestFrameworkException $e) { + } + + return $this->composerJsonModulePaths; + } + + /** + * Aggregate all code paths with composer installed test modules + * + * @return array + */ + private function aggregateTestModulePathsFromComposerInstaller() + { + // Define the module paths + $magentoBaseCodePath = MAGENTO_BP; + $composerFile = $magentoBaseCodePath . DIRECTORY_SEPARATOR . 'composer.json'; + + return $this->getComposerInstalledTestModulePaths($composerFile); + } + + /** + * Retrieve composer installed test module code paths + * + * @params string $composerFile + * @return array + */ + private function getComposerInstalledTestModulePaths($composerFile) + { + if (null !== $this->composerInstalledModulePaths) { + return $this->composerInstalledModulePaths; + } + try { + $this->composerInstalledModulePaths = []; + $resolver = new ComposerModuleResolver(); + $this->composerInstalledModulePaths = $resolver->getComposerInstalledTestModules($composerFile); + } catch (TestFrameworkException $e) { + } + + return $this->composerInstalledModulePaths; + } + + /** + * Flip and filter module code paths + * + * @param array $objectArray + * @param array $filterArray + * @return array + */ + private function flipAndFilterModulePathsArray($objectArray, $filterArray) + { + $oneToOneArray = []; + $oneToManyArray = []; + // Filter array by enabled modules + foreach ($objectArray as $path => $modules) { + if (!array_diff($modules, $filterArray) + || (count($modules) == 1 && isset($this->knownDirectories[$modules[0]]))) { + if (count($modules) == 1) { + $oneToOneArray[$path] = $modules[0]; + } else { + $oneToManyArray[$path] = $modules; + } + } + } + + $flippedArray = []; + // Set flipped array for "one path => one module" case first to maintain module sequencing + foreach ($filterArray as $moduleName) { + $path = array_search($moduleName, $oneToOneArray); + if ($path !== false) { + if (strpos($moduleName, '_') === false) { + $moduleName = $this->findVendorNameFromPath($path) . '_' . $moduleName; + } + $flippedArray = $this->setArrayValueWithLogging($flippedArray, $moduleName, $path); + unset($oneToOneArray[$path]); + } + } + + // Set flipped array for everything else + return $this->flipAndSortModulePathsArray( + array_merge($oneToOneArray, $oneToManyArray), + false, + $flippedArray + ); + } + + /** + * Flip module code paths and optionally sort in alphabetical order + * + * @param array $objectArray + * @param boolean $sort + * @param array $inFlippedArray + * @return array + */ + private function flipAndSortModulePathsArray($objectArray, $sort, $inFlippedArray = []) + { + $flippedArray = $inFlippedArray; + + // Set flipped array from object array + foreach ($objectArray as $path => $modules) { + if (is_array($modules) && count($modules) > 1) { + // The "one path => many module names" case is designed to be strictly used when it's + // impossible to write tests in dedicated modules. Due to performance consideration and there + // is no real usage of this currently, we will use the first module name for the path. + // TODO: consider saving all module names if this information is needed in the future. + $module = $modules[0]; + } elseif (is_array($modules)) { + if (strpos($modules[0], '_') === false) { + $module = $this->findVendorNameFromPath($path) . '_' . $modules[0]; + } else { + $module = $modules[0]; + } + } else { + if (strpos($modules, '_') === false) { + $module = $this->findVendorNameFromPath($path) . '_' . $modules; + } else { + $module = $modules; + } + } + $flippedArray = $this->setArrayValueWithLogging($flippedArray, $module, $path); + } + + // Sort array in alphabetical order + if ($sort) { + ksort($flippedArray); + } + + return $flippedArray; + } + + /** + * Set array value at index only if array value at index is not yet set, skip otherwise and log warning message + * + * @param array $inArray + * @param string $index + * @param string $value + * + * @return array + */ + private function setArrayValueWithLogging($inArray, $index, $value) + { + $outArray = $inArray; + if (!isset($inArray[$index])) { + $outArray[$index] = $value; + } else { + $warnMsg = 'Path: ' . $value . ' is ignored by ModuleResolver. ' . PHP_EOL . 'Path: '; + $warnMsg .= $inArray[$index] . ' is set for Module: ' . $index . PHP_EOL; + LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->warn($warnMsg); + } + return $outArray; + } + + /** + * Merge code paths + * + * @param array $oneToOneArray + * @param array $oneToManyArray + * @return array + */ + private function mergeModulePaths($oneToOneArray, $oneToManyArray) + { + $mergedArray = $oneToOneArray; + foreach ($oneToManyArray as $path => $modules) { + // Do nothing when array_key_exists + if (!array_key_exists($path, $oneToOneArray)) { + $mergedArray[$path] = $modules; + } + } + return $mergedArray; + } + + /** + * Normalize module name if registered module list is available + * + * @param array $codePaths + * + * @return array + */ + private function normalizeModuleNames($codePaths) + { + $allComponents = $this->getRegisteredModuleList(); + if (empty($allComponents)) { + return $codePaths; + } + + $normalizedCodePaths = []; + foreach ($codePaths as $path => $moduleNames) { + $mainModName = array_search($path, $allComponents); + if ($mainModName) { + $normalizedCodePaths[$path] = [$mainModName]; } else { - $enabledDirectoryPaths[$magentoModuleName] = $allModulePaths[$magentoModuleName]; + $normalizedCodePaths[$path] = $moduleNames; } } - return $enabledDirectoryPaths; + + return $normalizedCodePaths; + } + + /** + * Takes a multidimensional array of module paths and flattens to return a one dimensional array of test paths + * + * @param array $modulePaths + * @return array + */ + private function flattenAllModulePaths($modulePaths) + { + $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($modulePaths)); + $resultArray = []; + + foreach ($it as $value) { + $resultArray[] = $value; + } + + return $resultArray; } /** @@ -447,17 +758,6 @@ protected function getAdminToken() return json_decode($response); } - /** - * Sort files according module sequence. - * - * @param array $files - * @return array - */ - public function sortFilesByModuleSequence(array $files) - { - return $this->sequenceSorter->sort($files); - } - /** * A wrapping method for any custom logic which needs to be applied to the module list * @@ -469,13 +769,16 @@ protected function applyCustomModuleMethods($modulesPath) $modulePathsResult = $this->removeBlacklistModules($modulesPath); $customModulePaths = $this->getCustomModulePaths(); - array_map(function ($value) { + array_map(function ($key, $value) { LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( "including custom module", - ['module' => $value] + [$key => $value] ); - }, $customModulePaths); + }, array_keys($customModulePaths), $customModulePaths); + if (!isset($this->enabledModuleNameAndPaths)) { + $this->enabledModuleNameAndPaths = array_merge($modulePathsResult, $customModulePaths); + } return $this->flattenAllModulePaths(array_merge($modulePathsResult, $customModulePaths)); } @@ -489,6 +792,7 @@ private function removeBlacklistModules($modulePaths) { $modulePathsResult = $modulePaths; foreach ($modulePathsResult as $moduleName => $modulePath) { + // Remove module if it is in blacklist if (in_array($moduleName, $this->getModuleBlacklist())) { unset($modulePathsResult[$moduleName]); LoggingUtil::getInstance()->getLogger(ModuleResolver::class)->info( @@ -508,13 +812,18 @@ private function removeBlacklistModules($modulePaths) */ private function getCustomModulePaths() { - $customModulePaths = getenv(self::CUSTOM_MODULE_PATHS); + $customModulePaths = []; + $paths = getenv(self::CUSTOM_MODULE_PATHS); - if (!$customModulePaths) { - return []; + if (!$paths) { + return $customModulePaths; + } + + foreach (explode(',', $paths) as $path) { + $customModulePaths = [$this->findVendorAndModuleNameFromPath(trim($path)) => $path]; } - return array_map('trim', explode(',', $customModulePaths)); + return $customModulePaths; } /** @@ -534,6 +843,10 @@ private function getModuleBlacklist() */ private function getRegisteredModuleList() { + if (!empty($this->registeredModuleList)) { + return $this->registeredModuleList; + } + if (array_key_exists('MAGENTO_BP', $_ENV)) { $autoloadPath = realpath(MAGENTO_BP . "/app/autoload.php"); if ($autoloadPath) { @@ -556,7 +869,7 @@ private function getRegisteredModuleList() array_walk($allComponents, function (&$value) { // Magento stores component paths with unix DIRECTORY_SEPARATOR, need to stay uniform and convert $value = realpath($value); - $value .= '/Test/Mftf'; + $value .= DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN; }); return $allComponents; } catch (TestFrameworkException $e) { @@ -575,4 +888,43 @@ private function getBackendUrl() { return getenv('MAGENTO_BACKEND_BASE_URL') ?: getenv('MAGENTO_BASE_URL'); } + + /** + * Find vendor and module name from path + * + * @param string $path + * @return string + */ + private function findVendorAndModuleNameFromPath($path) + { + $path = str_replace(DIRECTORY_SEPARATOR . self::TEST_MFTF_PATTERN, '', $path); + return $this->findVendorNameFromPath($path) . '_' . basename($path); + } + + /** + * Find vendor name from path + * + * @param string $path + * @return string + */ + private function findVendorNameFromPath($path) + { + $possibleVendorName = 'UnknownVendor'; + $dirPaths = [ + self::VENDOR, + self::APP_CODE, + self::DEV_TESTS + ]; + + foreach ($dirPaths as $dirPath) { + $regex = "~.+\\/" . $dirPath . "\/(?<" . self::VENDOR . ">[^\/]+)\/.+~"; + $match = []; + preg_match($regex, $path, $match); + if (isset($match[self::VENDOR])) { + $possibleVendorName = ucfirst($match[self::VENDOR]); + return $possibleVendorName; + } + } + return $possibleVendorName; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 1facfe746..ce45af963 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -654,7 +654,10 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato } if (isset($customActionAttributes['function'])) { - $function = $this->addUniquenessFunctionCall($customActionAttributes['function']); + $function = $this->addUniquenessFunctionCall( + $customActionAttributes['function'], + $actionObject->getType() !== "executeInSelenium" + ); if (in_array($actionObject->getType(), ActionObject::FUNCTION_CLOSURE_ACTIONS)) { // Argument must be a closure function, not a string. $function = trim($function, '"'); @@ -1736,12 +1739,17 @@ private function processPressKey($input) /** * Add uniqueness function call to input string based on regex pattern. * - * @param string $input + * @param string $input + * @param boolean $wrapWithDoubleQuotes * @return string */ - private function addUniquenessFunctionCall($input) + private function addUniquenessFunctionCall($input, $wrapWithDoubleQuotes = true) { - $output = $this->wrapWithDoubleQuotes($input); + if ($wrapWithDoubleQuotes) { + $output = $this->wrapWithDoubleQuotes($input); + } else { + $output = $input; + } //Match on msq(\"entityName\") preg_match_all('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\(\\\\"[\w]+\\\\"\)/', $output, $matches);