diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a24b5f29..fd555ee88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ Magento Functional Testing Framework Changelog ================================================ +3.1.0 +________ + +### Enhancements + +* Customizability + * Introduced the new `return` action that allows action groups to return a value. See the [actions page](./docs/test/actions.md#return) for details. + * Introduced new MFTF command that invokes `vendor/bin/codecept run`. See the [mftf page](./docs/commands/mftf.md#codeceptrun) for details. + +* Usability + * Introduced new action `pause`, to invoke codeception interactive pause for debugging during test execution. See the [Interactive Pause](./docs/interactive-pause.md) page for details. + * Introduced a new `.env` configuration option `ENABLE_PAUSE`, to enable the new pause feature. + +* Maintainability + * Added a new static check that checks for the usage of the `pause` action. See the [command page](./docs/commands/mftf.md#static-checks) for details. + +* Modularity + * MFTF now supports the use of actions from multiple modules within suites. + +* Traceability + * A deprecation notice is now added at test execution time for deprecated metadata usage. + +### Fixes + +* Fixed issue with suite precondition failure for `createData` with required entity. + +### GitHub Issues/Pull requests: + + * [#547](https://github.com/magento/magento2-functional-testing-framework/pull/547) -- Fix invalid behavior of MAGENTO_BACKEND_BASE_URL + * [#742](https://github.com/magento/magento2-functional-testing-framework/pull/742) -- Fix Waits In MagentoPwaWebDriver + * [#750](https://github.com/magento/magento2-functional-testing-framework/pull/750) -- Docs: Outlining the difference between Allure severity levels + * [#683](https://github.com/magento/magento2-functional-testing-framework/pull/683) -- Docs: Renamed sample test name with the correct one + * [#691](https://github.com/magento/magento2-functional-testing-framework/pull/691) -- Docs: Branch name updates + * [#678](https://github.com/magento/magento2-functional-testing-framework/pull/678) -- Docs: Command added to modify the web server rewrites configuration + * [#745](https://github.com/magento/magento2-functional-testing-framework/pull/745) -- Docs: Remove invalid sample test name + 3.0.0 --------- diff --git a/composer.json b/composer.json index e1a04296f..813ab382b 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": "3.0.0", + "version": "3.1.0", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": { @@ -24,6 +24,7 @@ "composer/composer": "^1.9", "csharpru/vault-php": "~3.5.3", "csharpru/vault-php-guzzle6-transport": "^2.0", + "hoa/console": "~3.0", "monolog/monolog": "^1.17", "mustache/mustache": "~2.5", "php-webdriver/webdriver": "^1.8.0", diff --git a/composer.lock b/composer.lock index dff4f1006..7f7648096 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": "a0516f6072ced659bf4ed7a486756bb2", + "content-hash": "278e33e2c7d183d0b7689b5a76127d29", "packages": [ { "name": "allure-framework/allure-codeception", @@ -1662,6 +1662,555 @@ ], "time": "2019-07-01T23:21:34+00:00" }, + { + "name": "hoa/consistency", + "version": "1.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Consistency.git", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f", + "shasum": "" + }, + "require": { + "hoa/exception": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "hoa/stream": "~1.0", + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Consistency\\": "." + }, + "files": [ + "Prelude.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Consistency library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autoloader", + "callable", + "consistency", + "entity", + "flex", + "keyword", + "library" + ], + "time": "2017-05-02T12:18:12+00:00" + }, + { + "name": "hoa/console", + "version": "3.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Console.git", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Console/zipball/e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/file": "~1.0", + "hoa/protocol": "~1.0", + "hoa/stream": "~1.0", + "hoa/ustring": "~4.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-pcntl": "To enable hoa://Event/Console/Window:resize.", + "hoa/dispatcher": "To use the console kit.", + "hoa/router": "To use the console kit." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Console\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Console library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autocompletion", + "chrome", + "cli", + "console", + "cursor", + "getoption", + "library", + "option", + "parser", + "processus", + "readline", + "terminfo", + "tput", + "window" + ], + "time": "2017-05-02T12:26:19+00:00" + }, + { + "name": "hoa/event", + "version": "1.17.01.13", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Event.git", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Event\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Event library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "event", + "library", + "listener", + "observer" + ], + "time": "2017-01-13T15:30:50+00:00" + }, + { + "name": "hoa/exception", + "version": "1.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Exception.git", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Exception\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Exception library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "exception", + "library" + ], + "time": "2017-01-16T07:53:27+00:00" + }, + { + "name": "hoa/file", + "version": "1.17.07.11", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/File.git", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/iterator": "~2.0", + "hoa/stream": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\File\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\File library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Socket", + "directory", + "file", + "finder", + "library", + "link", + "temporary" + ], + "time": "2017-07-11T07:42:15+00:00" + }, + { + "name": "hoa/iterator", + "version": "2.17.01.10", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Iterator.git", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Iterator\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Iterator library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "iterator", + "library" + ], + "time": "2017-01-10T10:34:47+00:00" + }, + { + "name": "hoa/protocol", + "version": "1.17.01.14", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Protocol.git", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Protocol\\": "." + }, + "files": [ + "Wrapper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Protocol library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "protocol", + "resource", + "stream", + "wrapper" + ], + "time": "2017-01-14T12:26:10+00:00" + }, + { + "name": "hoa/stream", + "version": "1.17.02.21", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Stream.git", + "reference": "3293cfffca2de10525df51436adf88a559151d82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82", + "reference": "3293cfffca2de10525df51436adf88a559151d82", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/protocol": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Stream\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Stream library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Context", + "bucket", + "composite", + "filter", + "in", + "library", + "out", + "protocol", + "stream", + "wrapper" + ], + "time": "2017-02-21T16:01:06+00:00" + }, + { + "name": "hoa/ustring", + "version": "4.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Ustring.git", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/e6326e2739178799b1fe3fdd92029f9517fa17a0", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().", + "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Ustring\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Ustring library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "search", + "string", + "unicode" + ], + "time": "2017-01-16T07:08:25+00:00" + }, { "name": "jms/metadata", "version": "1.7.0", @@ -3115,6 +3664,7 @@ "keywords": [ "tokenizer" ], + "abandoned": true, "time": "2020-02-07T06:19:00+00:00" }, { @@ -6920,5 +7470,6 @@ "ext-json": "*", "ext-openssl": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "1.1.0" } diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index 059441721..9d3750ec6 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -9,9 +9,11 @@ $vendorAutoloadPath = realpath(PROJECT_ROOT . '/vendor/autoload.php'); $mftfTestCasePath = realpath(PROJECT_ROOT . '/dev/tests/util/MftfTestCase.php'); +$mftfStaticTestCasePath = realpath(PROJECT_ROOT . '/dev/tests/util/MftfStaticTestCase.php'); require_once $vendorAutoloadPath; require_once $mftfTestCasePath; +require_once $mftfStaticTestCasePath; // Set up AspectMock $kernel = \AspectMock\Kernel::getInstance(); diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php index c9ec92ba8..745789501 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/DataObjectHandlerTest.php @@ -13,12 +13,22 @@ use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; +use tests\unit\Util\TestLoggingUtil; /** * Class DataObjectHandlerTest */ class DataObjectHandlerTest extends MagentoTestCase { + /** + * Setup method + */ + public function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + // All tests share this array, feel free to add but be careful modifying or removing const PARSER_OUTPUT = [ 'entity' => [ @@ -44,6 +54,22 @@ class DataObjectHandlerTest extends MagentoTestCase ] ]; + const PARSER_OUTPUT_DEPRECATED = [ + 'entity' => [ + 'EntityOne' => [ + 'type' => 'testType', + 'data' => [ + 0 => [ + 'key' => 'testKey', + 'value' => 'testValue' + ] + ], + 'deprecated' => "deprecation message", + 'filename' => "filename.xml" + ], + ] + ]; + const PARSER_OUTPUT_WITH_EXTEND = [ 'entity' => [ 'EntityOne' => [ @@ -123,7 +149,7 @@ class DataObjectHandlerTest extends MagentoTestCase */ public function testGetAllObjects() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT); // Call the method under test $actual = DataObjectHandler::getInstance()->getAllObjects(); @@ -134,12 +160,30 @@ public function testGetAllObjects() $this->assertEquals($expected, $actual['EntityOne']); } + /** + * test deprecated data object + */ + public function testDeprecatedDataObject() + { + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT_DEPRECATED); + + // Call the method under test + $actual = DataObjectHandler::getInstance()->getAllObjects(); + + //validate deprecation warning + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + "DEPRECATION: The data entity 'EntityOne' is deprecated.", + ["fileName" => "filename.xml", "deprecatedMessage" => "deprecation message"] + ); + } + /** * getObject should return the expected data object if it exists */ public function testGetObject() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT); // Call the method under test $actual = DataObjectHandler::getInstance()->getObject('EntityOne'); @@ -154,7 +198,7 @@ public function testGetObject() */ public function testGetObjectNull() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT); $actual = DataObjectHandler::getInstance()->getObject('h953u789h0g73t521'); // doesnt exist $this->assertNull($actual); @@ -165,7 +209,7 @@ public function testGetObjectNull() */ public function testGetAllObjectsWithDataExtends() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); // Call the method under test $actual = DataObjectHandler::getInstance()->getAllObjects(); @@ -189,7 +233,7 @@ public function testGetAllObjectsWithDataExtends() */ public function testGetObjectWithDataExtends() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND); // Call the method under test $actual = DataObjectHandler::getInstance()->getObject('EntityTwo'); @@ -212,7 +256,7 @@ public function testGetObjectWithDataExtends() */ public function testGetAllObjectsWithDataExtendsItself() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); $this->expectExceptionMessage( @@ -229,7 +273,7 @@ public function testGetAllObjectsWithDataExtendsItself() */ public function testGetObjectWithDataExtendsItself() { - $this->setUpMockDataObjectHander(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); + ObjectHandlerUtil::mockDataObjectHandlerWithData(self::PARSER_OUTPUT_WITH_EXTEND_INVALID); $this->expectException(\Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException::class); $this->expectExceptionMessage( @@ -244,29 +288,10 @@ public function testGetObjectWithDataExtendsItself() } /** - * Set up everything required to mock DataObjectHander::getInstance() - * The first call to getInstance() uses these mocks to emulate the parser, initializing internal state - * according to the PARSER_OUTPUT value - * - * @param array $entityDataArray + * clean up function runs after all tests */ - private function setUpMockDataObjectHander($entityDataArray) + public static function tearDownAfterClass(): void { - // Clear DataObjectHandler singleton if already set - $property = new \ReflectionProperty(DataObjectHandler::class, "INSTANCE"); - $property->setAccessible(true); - $property->setValue(null); - - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => $entityDataArray - ])->make(); - - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); - - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php index 9ca10c7f2..35b06a24a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/OperationDefinitionObjectHandlerTest.php @@ -14,12 +14,22 @@ use Magento\FunctionalTestingFramework\DataGenerator\Handlers\OperationDefinitionObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Parsers\OperationDefinitionParser; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; +use tests\unit\Util\TestLoggingUtil; /** * Class OperationDefinitionObjectHandlerTest */ class OperationDefinitionObjectHandlerTest extends MagentoTestCase { + /** + * Setup method + */ + public function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + public function testGetMultipleObjects() { // Data Variables for Assertions @@ -63,7 +73,7 @@ public function testGetMultipleObjects() ], ] ]]]; - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); //Perform Assertions $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -72,6 +82,56 @@ public function testGetMultipleObjects() $this->assertArrayHasKey($operationType2 . $dataType1, $operations); } + public function testDeprecatedOperation() + { + // Data Variables for Assertions + $dataType1 = "type1"; + $operationType1 = "create"; + + /** + * Parser Output. Just one metadata with 1 field + * operationName + * createType1 + * has field + * key=id, value=integer + */ + $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + "testOperationName" => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1", + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "POST", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => "id", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => "integer" + ], + ], + OperationDefinitionObjectHandler::OBJ_DEPRECATED => 'deprecation message' + ]]]; + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); + + //Perform Assertions + $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); + $operations = $operationDefinitionManager->getAllObjects(); + + $this->assertArrayHasKey($operationType1 . $dataType1, $operations); + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + "NOTICE: 1 metadata operation name violations detected. See mftf.log for details.", + [] + ); + // test run time deprecation notice + $operation = $operationDefinitionManager->getOperationDefinition($operationType1, $dataType1); + $operation->logDeprecated(); + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + "DEPRECATION: The operation testOperationName is deprecated.", + ['operationType' => 'create', 'deprecatedMessage' => 'deprecation message'] + ); + } + public function testObjectCreation() { // Data Variables for Assertions @@ -180,7 +240,7 @@ public function testObjectCreation() ); // Set up mocked data output - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); // Get Operation $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -278,7 +338,7 @@ public function testObjectArrayCreation() ); // Set up mocked data output - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); // Get Operation $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -346,7 +406,7 @@ public function testLooseJsonCreation() ); // Set up mocked data output - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); // get Operations $operationDefinitionManager = OperationDefinitionObjectHandler::getInstance(); @@ -358,25 +418,10 @@ public function testLooseJsonCreation() } /** - * Function used to set mock for parser return and force init method to run between tests. - * - * @param array $data + * clean up function runs after all tests */ - private function setMockParserOutput($data) + public static function tearDownAfterClass(): void { - // clear Operation object handler value to inject parsed content - $property = new \ReflectionProperty( - OperationDefinitionObjectHandler::class, - 'INSTANCE' - ); - $property->setAccessible(true); - $property->setValue(null); - - $mockOperationParser = AspectMock::double( - OperationDefinitionParser::class, - ["readOperationMetadata" => $data] - )->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockOperationParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php index 870e75dcd..7e39fe216 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/DataGenerator/Handlers/PersistedObjectHandlerTest.php @@ -16,6 +16,7 @@ use Magento\FunctionalTestingFramework\ObjectManager; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; use tests\unit\Util\TestLoggingUtil; /** @@ -84,7 +85,7 @@ public function testCreateSimpleEntity() "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutput); $this->mockCurlHandler($jsonResponse); $handler = PersistedObjectHandler::getInstance(); @@ -127,7 +128,7 @@ public function testDeleteSimpleEntity() "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutput); $this->mockCurlHandler($jsonResponse); $handler = PersistedObjectHandler::getInstance(); @@ -175,7 +176,7 @@ public function testGetSimpleEntity() "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutput); $this->mockCurlHandler($jsonResponse); $handler = PersistedObjectHandler::getInstance(); @@ -235,7 +236,7 @@ public function testUpdateSimpleEntity() "; // Mock Classes - $this->mockDataHandlerWithOutput($parserOutput); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutput); $this->mockCurlHandler($jsonResponse); $handler = PersistedObjectHandler::getInstance(); $handler->createEntity( @@ -322,7 +323,7 @@ public function testRetrieveEntityAcrossScopes() // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - $this->mockDataHandlerWithOutput($parserOutputOne); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutputOne); $this->mockCurlHandler($jsonReponseOne); $handler->createEntity( $entityStepKeyOne, @@ -399,7 +400,7 @@ public function testRetrieveEntityValidField($name, $key, $value, $type, $scope, // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - $this->mockDataHandlerWithOutput($parserOutputOne); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutputOne); $this->mockCurlHandler($jsonReponseOne); $handler->createEntity($stepKey, $scope, $name); @@ -447,8 +448,7 @@ public function testRetrieveEntityInValidField($name, $key, $value, $type, $scop // Mock Classes and Create Entities $handler = PersistedObjectHandler::getInstance(); - - $this->mockDataHandlerWithOutput($parserOutputOne); + ObjectHandlerUtil::mockDataObjectHandlerWithData($parserOutputOne); $this->mockCurlHandler($jsonReponseOne); $handler->createEntity($stepKey, $scope, $name); @@ -475,31 +475,6 @@ public static function entityDataProvider() ]; } - /** - * Mocks DataObjectHandler to use given output to create - * @param $parserOutput - * @throws \Exception - */ - public function mockDataHandlerWithOutput($parserOutput) - { - // Clear DataObjectHandler singleton if already set - $property = new \ReflectionProperty(DataObjectHandler::class, "INSTANCE"); - $property->setAccessible(true); - $property->setValue(null); - - $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ - 'readDataProfiles' => $parserOutput - ])->make(); - - $mockObjectManager = AspectMock::double(ObjectManager::class, [ - 'create' => $mockDataProfileSchemaParser - ])->make(); - - AspectMock::double(ObjectManagerFactory::class, [ - 'getObjectManager' => $mockObjectManager - ]); - } - public function mockCurlHandler($response) { AspectMock::double(CurlHandler::class, [ diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php index 1a0ba5fc2..eb305bc4d 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php @@ -12,9 +12,19 @@ use Magento\FunctionalTestingFramework\Page\Handlers\PageObjectHandler; use Magento\FunctionalTestingFramework\XmlParser\PageParser; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; +use tests\unit\Util\TestLoggingUtil; class PageObjectHandlerTest extends MagentoTestCase { + /** + * Setup method + */ + public function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + public function testGetPageObject() { $mockData = [ @@ -36,7 +46,7 @@ public function testGetPageObject() ], "area" => "test" ]]; - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockPageObjectHandlerWithData($mockData); // get pages $pageHandler = PageObjectHandler::getInstance(); @@ -61,7 +71,7 @@ public function testGetEmptyPage() ], "area" => "test" ]]; - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockPageObjectHandlerWithData($mockData); // get pages $page = PageObjectHandler::getInstance()->getObject('testPage1'); @@ -70,20 +80,35 @@ public function testGetEmptyPage() $this->addToAssertionCount(1); } + public function testDeprecatedPage() + { + $mockData = [ + "testPage1" => [ + "url" => "testURL1", + "module" => "testModule1", + "section" => [ + ], + "area" => "test", + "deprecated" => "deprecation message", + "filename" => "filename.xml" + ]]; + ObjectHandlerUtil::mockPageObjectHandlerWithData($mockData); + + // get pages + $page = PageObjectHandler::getInstance()->getObject('testPage1'); + + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + "NOTICE: 1 Page name violations detected. See mftf.log for details.", + [] + ); + } + /** - * Function used to set mock for parser return and force init method to run between tests. - * - * @param array $data + * clean up function runs after all tests */ - private function setMockParserOutput($data) + public static function tearDownAfterClass(): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); - $property->setAccessible(true); - $property->setValue(null); - - $mockSectionParser = AspectMock::double(PageParser::class, ["getData" => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['get' => $mockSectionParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php index b8bcf3dfb..69088944a 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/SectionObjectHandlerTest.php @@ -12,9 +12,19 @@ use Magento\FunctionalTestingFramework\Page\Handlers\SectionObjectHandler; use Magento\FunctionalTestingFramework\XmlParser\SectionParser; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; +use tests\unit\Util\TestLoggingUtil; class SectionObjectHandlerTest extends MagentoTestCase { + /** + * Setup method + */ + public function setUp(): void + { + TestLoggingUtil::getInstance()->setMockLoggingUtil(); + } + public function testGetSectionObject() { $mockData = [ @@ -37,7 +47,7 @@ public function testGetSectionObject() ] ]; - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockSectionObjectHandlerWithData($mockData); // get sections $sectionHandler = SectionObjectHandler::getInstance(); @@ -52,20 +62,41 @@ public function testGetSectionObject() $this->assertNull($invalidSection); } + public function testDeprecatedSection() + { + $mockData = [ + "testSection1" => [ + "element" => [ + "testElement" => [ + "type" => "input", + "selector" => "#element", + "deprecated" => "element deprecation message" + ] + ], + "filename" => "filename.xml", + "deprecated" => "section deprecation message" + ] + ]; + + ObjectHandlerUtil::mockSectionObjectHandlerWithData($mockData); + + // get sections + $sectionHandler = SectionObjectHandler::getInstance(); + $section = $sectionHandler->getObject("testSection1"); + + //validate deprecation warning + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'notice', + "NOTICE: 1 Section name violations detected. See mftf.log for details.", + [] + ); + } + /** - * Set the mock parser return value - * - * @param array $data + * clean up function runs after all tests */ - private function setMockParserOutput($data) + public static function tearDownAfterClass(): void { - // clear section object handler value to inject parsed content - $property = new \ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); - $property->setAccessible(true); - $property->setValue(null); - - $mockSectionParser = AspectMock::double(SectionParser::class, ["getData" => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ["get" => $mockSectionParser])->make(); - AspectMock::double(ObjectManagerFactory::class, ["getObjectManager" => $instance]); + TestLoggingUtil::getInstance()->clearMockLoggingUtil(); } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php new file mode 100644 index 000000000..dc339c8a3 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/StaticCheck/DeprecatedEntityUsageCheckTest.php @@ -0,0 +1,251 @@ +staticCheck = new DeprecatedEntityUsageCheck(); + $this->staticCheckClass = new \ReflectionClass($this->staticCheck); + } + + public function tearDown(): void + { + AspectMock::clean(); + } + + public function testInvalidPathOption() + { + $input = $this->getMockBuilder(InputInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $input->method('getOption') + ->with('path') + ->willReturn('/invalidPath'); + + $loadAllXmlFiles = $this->staticCheckClass->getMethod('loadAllXMLFiles'); + $loadAllXmlFiles->setAccessible(true); + + $this->expectException(InvalidArgumentException::class); + $loadAllXmlFiles->invoke($this->staticCheck, $input); + } + + public function testViolatingElementReferences() + { + //variables for assertions + $elementName = 'elementOne'; + $sectionName = 'SectionOne'; + $fileName = 'section.xml'; + + $element = new ElementObject($elementName, 'type', '#selector1', null, '41', false, 'deprecated'); + $section = new SectionObject($sectionName, [$element], $fileName); + $elementRef = $sectionName . '.' . $elementName; + $references = [$elementRef => $element, $sectionName => $section]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Element(s)' => [ + 0 => [ + 'name' => $elementRef, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + public function testViolatingPageReferences() + { + //Page variables for assertions + $pageName = 'Page'; + $fileName = 'page.xml'; + + $page = new PageObject($pageName, '/url.html', 'Test', [], false, "test", $fileName, 'deprecated'); + $references = ['Page' => $page]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Page(s)' => [ + 0 => [ + 'name' => $pageName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + public function testViolatingDataReferences() + { + //Data entity variables for assertions + $entityName = 'EntityOne'; + $fileName = 'entity.xml'; + + $entity = new EntityDataObject( + $entityName, + 'testType', + ['testkey' => 'testValue'], + [], + null, + [], + null, + $fileName, + 'deprecated' + ); + $references = [$entityName => $entity]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Data(s)' => [ + 0 => [ + 'name' => $entityName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + public function testViolatingTestReferences() + { + // test variables for assertions + $testName = 'Test1'; + $fileName = 'test.xml'; + + $test = new TestObject($testName, [], [], [], $fileName, null, 'deprecated'); + $references = ['Test1' => $test]; + $actual = $this->callViolatingReferences($references); + $expected = [ + 'Deprecated Test(s)' => [ + 0 => [ + 'name' => $testName, + 'file' => $fileName + ] + ] + ]; + $this->assertEquals($actual, $expected); + } + + public function testViolatingMetaDataReferences() + { + // Data Variables for Assertions + $dataType1 = "type1"; + $operationType1 = "create"; + $operationType2 = "update"; + + /** + * Parser Output. + * operationName + * createType1 + * has field + * key=id, value=integer + * updateType1 + * has field + * key=id, value=integer + */ + $mockData = [OperationDefinitionObjectHandler::ENTITY_OPERATION_ROOT_TAG => [ + "testOperationName" => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1", + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "POST", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => "id", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => "integer" + ], + ], + OperationDefinitionObjectHandler::OBJ_DEPRECATED => 'deprecated' + ],[ + OperationDefinitionObjectHandler::ENTITY_OPERATION_DATA_TYPE => $dataType1, + OperationDefinitionObjectHandler::ENTITY_OPERATION_TYPE => $operationType2, + OperationDefinitionObjectHandler::ENTITY_OPERATION_AUTH => "auth", + OperationDefinitionObjectHandler::ENTITY_OPERATION_URL => "V1/Type1/{id}", + OperationDefinitionObjectHandler::ENTITY_OPERATION_METHOD => "PUT", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY => [ + 0 => [ + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_KEY => "id", + OperationDefinitionObjectHandler::ENTITY_OPERATION_ENTRY_VALUE => "integer" + ], + ] + ]]]; + + ObjectHandlerUtil::mockOperationHandlerWithData($mockData); + $dataName = 'dataName1'; + $references = [ + $dataName => [ + $dataType1 => [ + $operationType1, + $operationType2 + ] + ] + ]; + + $expected = [ + '"'.$dataName.'" references deprecated' => [ + 0 => [ + 'name' => $dataType1, + 'file' => 'metadata xml file' + ] + ] + ]; + $property = $this->staticCheckClass->getMethod('findViolatingMetadataReferences'); + $property->setAccessible(true); + $actual = $property->invoke($this->staticCheck, $references); + $this->assertEquals($actual, $expected); + } + + public function testIsDeprecated() + { + // Test Data + $contents = ' + + + + + + '; + + $property = $this->staticCheckClass->getMethod('isDeprecated'); + $property->setAccessible(true); + $output = $property->invoke($this->staticCheck, $contents); + $this->assertTrue($output); + } + + /** + * Invoke findViolatingReferences + * @param $references + * @return mixed + * @throws \ReflectionException + */ + public function callViolatingReferences($references) + { + $property = $this->staticCheckClass->getMethod('findViolatingReferences'); + $property->setAccessible(true); + return $property->invoke($this->staticCheck, $references); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php index deb08d783..71c1833ba 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/ActionGroupObjectHandlerTest.php @@ -15,6 +15,7 @@ use tests\unit\Util\MagentoTestCase; use tests\unit\Util\ActionGroupArrayBuilder; use Magento\FunctionalTestingFramework\Test\Parsers\ActionGroupDataParser; +use tests\unit\Util\ObjectHandlerUtil; class ActionGroupObjectHandlerTest extends MagentoTestCase { @@ -34,7 +35,7 @@ public function testGetTestObjectWithInvalidExtends() ->withFilename() ->withActionObjects() ->build(); - $this->setMockParserOutput(['actionGroups' => $actionGroupOne]); + ObjectHandlerUtil::mockActionGroupObjectHandlerWithData(['actionGroups' => $actionGroupOne]); $handler = ActionGroupObjectHandler::getInstance(); @@ -68,7 +69,14 @@ public function testGetAllTestObjectsWithInvalidExtends() ->withActionObjects() ->build(); - $this->setMockParserOutput(['actionGroups' => array_merge($actionGroupOne, $actionGroupTwo)]); + ObjectHandlerUtil::mockActionGroupObjectHandlerWithData( + [ + 'actionGroups' => array_merge( + $actionGroupOne, + $actionGroupTwo + ) + ] + ); $handler = ActionGroupObjectHandler::getInstance(); @@ -76,23 +84,4 @@ public function testGetAllTestObjectsWithInvalidExtends() $this->expectExceptionMessage("Mftf Action Group can not extend from itself: " . $nameOne); $handler->getAllObjects(); } - - /** - * Function used to set mock for parser return and force init method to run between tests. - * - * @param array $data - * @throws \Exception - */ - private function setMockParserOutput($data) - { - // Clear action group object handler value to inject parsed content - $property = new \ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); - $property->setAccessible(true); - $property->setValue(null); - - $mockDataParser = AspectMock::double(ActionGroupDataParser::class, ['readActionGroupData' => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) - ->make(); // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); - } } diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php index 4db2f21a4..939dd496e 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Handlers/TestObjectHandlerTest.php @@ -17,6 +17,7 @@ use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use tests\unit\Util\MagentoTestCase; +use tests\unit\Util\ObjectHandlerUtil; use tests\unit\Util\TestDataArrayBuilder; use tests\unit\Util\MockModuleResolverBuilder; @@ -41,7 +42,7 @@ public function testGetTestObject() $resolverMock = new MockModuleResolverBuilder(); $resolverMock->setup(); - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockTestObjectHandlerWitData($mockData); // run object handler method $toh = TestObjectHandler::getInstance(); @@ -78,7 +79,7 @@ public function testGetTestObject() $expectedFailedHookObject = new TestHookObject( TestObjectExtractor::TEST_FAILED_HOOK, $testDataArrayBuilder->testName, - [$expectedFailedActionObject] + ["saveScreenshot" => $expectedFailedActionObject] ); $expectedTestActionObject = new ActionObject( @@ -135,7 +136,7 @@ public function testGetTestsByGroup() $resolverMock = new MockModuleResolverBuilder(); $resolverMock->setup(); - $this->setMockParserOutput(array_merge($includeTest, $excludeTest)); + ObjectHandlerUtil::mockTestObjectHandlerWitData(array_merge($includeTest, $excludeTest)); // execute test method $toh = TestObjectHandler::getInstance(); @@ -184,7 +185,7 @@ public function testGetTestWithModuleName() $resolverMock = new MockModuleResolverBuilder(); $resolverMock->setup(['Vendor_' . $moduleExpected => $filepath]); - $this->setMockParserOutput($mockData); + ObjectHandlerUtil::mockTestObjectHandlerWitData($mockData); // Execute Test Method $toh = TestObjectHandler::getInstance(); $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); @@ -212,7 +213,7 @@ public function testGetTestObjectWithInvalidExtends() ->build(); $resolverMock = new MockModuleResolverBuilder(); $resolverMock->setup(); - $this->setMockParserOutput($testOne); + ObjectHandlerUtil::mockTestObjectHandlerWitData($testOne); $toh = TestObjectHandler::getInstance(); @@ -250,7 +251,7 @@ public function testGetAllTestObjectsWithInvalidExtends() $resolverMock = new MockModuleResolverBuilder(); $resolverMock->setup(); - $this->setMockParserOutput(array_merge($testOne, $testTwo)); + ObjectHandlerUtil::mockTestObjectHandlerWitData(array_merge($testOne, $testTwo)); $toh = TestObjectHandler::getInstance(); @@ -260,22 +261,96 @@ public function testGetAllTestObjectsWithInvalidExtends() } /** - * Function used to set mock for parser return and force init method to run between tests. + * Validate test object when ENABLE_PAUSE is set to true * - * @param array $data * @throws \Exception */ - private function setMockParserOutput($data) + public function testGetTestObjectWhenEnablePause() { - // clear test object handler value to inject parsed content - $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); - $property->setAccessible(true); - $property->setValue(null); - - $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $data])->make(); - $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) - ->make(); // bypass the private constructor - AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + // set up mock data + putenv('ENABLE_PAUSE=true'); + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockData = $testDataArrayBuilder + ->withAnnotations() + ->withFailedHook() + ->withAfterHook() + ->withBeforeHook() + ->withTestActions() + ->build(); + + $resolverMock = new MockModuleResolverBuilder(); + $resolverMock->setup(); + ObjectHandlerUtil::mockTestObjectHandlerWitData($mockData); + + // run object handler method + $toh = TestObjectHandler::getInstance(); + $mockConfig = AspectMock::double(TestObjectHandler::class, ['initTestData' => false]); + $actualTestObject = $toh->getObject($testDataArrayBuilder->testName); + + // perform asserts + $expectedBeforeActionObject = new ActionObject( + $testDataArrayBuilder->testActionBeforeName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedAfterActionObject = new ActionObject( + $testDataArrayBuilder->testActionAfterName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedFailedActionObject1 = new ActionObject( + 'saveScreenshot', + 'saveScreenshot', + [] + ); + $expectedFailedActionObject2 = new ActionObject( + 'pauseWhenFailed', + 'pause', + [ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE => true] + ); + + $expectedBeforeHookObject = new TestHookObject( + TestObjectExtractor::TEST_BEFORE_HOOK, + $testDataArrayBuilder->testName, + ["testActionBefore" => $expectedBeforeActionObject] + ); + $expectedAfterHookObject = new TestHookObject( + TestObjectExtractor::TEST_AFTER_HOOK, + $testDataArrayBuilder->testName, + ["testActionAfter" => $expectedAfterActionObject] + ); + $expectedFailedHookObject = new TestHookObject( + TestObjectExtractor::TEST_FAILED_HOOK, + $testDataArrayBuilder->testName, + [ + "saveScreenshot" => $expectedFailedActionObject1, + "pauseWhenFailed" => $expectedFailedActionObject2, + ] + ); + + $expectedTestActionObject = new ActionObject( + $testDataArrayBuilder->testTestActionName, + $testDataArrayBuilder->testActionType, + [] + ); + $expectedTestObject = new TestObject( + $testDataArrayBuilder->testName, + ["testActionInTest" => $expectedTestActionObject], + [ + 'features' => ['NO MODULE DETECTED'], + 'group' => ['test'], + 'description' => ['test_files' => '

Test files

', 'deprecated' => []] + ], + [ + TestObjectExtractor::TEST_BEFORE_HOOK => $expectedBeforeHookObject, + TestObjectExtractor::TEST_AFTER_HOOK => $expectedAfterHookObject, + TestObjectExtractor::TEST_FAILED_HOOK => $expectedFailedHookObject + ], + null + ); + + $this->assertEquals($expectedTestObject, $actualTestObject); + putenv('ENABLE_PAUSE'); } /** diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php index 2c9e171d5..1fea7e848 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionGroupObjectExtractorTest.php @@ -34,23 +34,47 @@ public function testEmptyStepKey() $this->testActionGroupObjectExtractor->extractActionGroup($this->createBasicActionObjectArray("")); } + /** + * Tests deprecation message for an action group + */ + public function testDeprecationMessage() + { + $this->testActionGroupObjectExtractor->extractActionGroup( + $this->createBasicActionObjectArray( + "testDeprecatedAction1", + "actionGroup", + "filename1.xml", + "message" + ) + ); + + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'warning', + "DEPRECATION: The action group 'actionGroup' is deprecated.", + ["fileName" => "filename1.xml", "deprecatedMessage" => "message"] + ); + } + /** * Utility function to return mock parser output for testing extraction into ActionObjects. * * @param string $stepKey * @param string $actionGroup * @param string $filename + * @param string $deprecated * @return array */ private function createBasicActionObjectArray( $stepKey = 'testAction1', $actionGroup = "actionGroup", - $filename = "filename.xml" + $filename = "filename.xml", + $deprecated = null ) { $baseArray = [ 'nodeName' => 'actionGroup', 'name' => $actionGroup, 'filename' => $filename, + 'deprecated' => $deprecated, $stepKey => [ "nodeName" => "sampleAction", "stepKey" => $stepKey, diff --git a/dev/tests/unit/Util/ObjectHandlerUtil.php b/dev/tests/unit/Util/ObjectHandlerUtil.php new file mode 100644 index 000000000..0e4543f2b --- /dev/null +++ b/dev/tests/unit/Util/ObjectHandlerUtil.php @@ -0,0 +1,145 @@ +setAccessible(true); + $property->setValue(null); + + $mockOperationParser = AspectMock::double( + OperationDefinitionParser::class, + ["readOperationMetadata" => $data] + )->make(); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockOperationParser])->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + } + + /** + * Set up everything required to mock DataObjectHandler::getInstance() with $data value + * + * @param array $data + */ + public static function mockDataObjectHandlerWithData($data) + { + // Clear DataObjectHandler singleton if already set + $property = new \ReflectionProperty(DataObjectHandler::class, "INSTANCE"); + $property->setAccessible(true); + $property->setValue(null); + + $mockDataProfileSchemaParser = AspectMock::double(DataProfileSchemaParser::class, [ + 'readDataProfiles' => $data + ])->make(); + + $mockObjectManager = AspectMock::double(ObjectManager::class, [ + 'create' => $mockDataProfileSchemaParser + ])->make(); + + AspectMock::double(ObjectManagerFactory::class, [ + 'getObjectManager' => $mockObjectManager + ]); + } + + /** + * Set up everything required to mock PageObjectHandler::getInstance() with $data value + * + * @param array $data + */ + public static function mockPageObjectHandlerWithData($data) + { + // clear section object handler value to inject parsed content + $property = new \ReflectionProperty(PageObjectHandler::class, 'INSTANCE'); + $property->setAccessible(true); + $property->setValue(null); + + $mockSectionParser = AspectMock::double(PageParser::class, ["getData" => $data])->make(); + $instance = AspectMock::double(ObjectManager::class, ['get' => $mockSectionParser])->make(); + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + } + + /** + * Set up everything required to mock SectionObjectHandler::getInstance() with $data value + * + * @param array $data + */ + public static function mockSectionObjectHandlerWithData($data) + { + // clear section object handler value to inject parsed content + $property = new \ReflectionProperty(SectionObjectHandler::class, "INSTANCE"); + $property->setAccessible(true); + $property->setValue(null); + + $mockSectionParser = AspectMock::double(SectionParser::class, ["getData" => $data])->make(); + $instance = AspectMock::double(ObjectManager::class, ["get" => $mockSectionParser])->make(); + AspectMock::double(ObjectManagerFactory::class, ["getObjectManager" => $instance]); + } + + /** + * Set up everything required to mock TestObjectHandler::getInstance() with $data value + * + * @param array $data + * @throws \Exception + */ + public static function mockTestObjectHandlerWitData($data) + { + // clear test object handler value to inject parsed content + $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null); + + $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $data])->make(); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) + ->make(); // bypass the private constructor + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + } + + /** + * Set up everything required to mock ActionGroupObjectHandler::getInstance() with $data value + * + * @param array $data + * @throws \Exception + */ + public static function mockActionGroupObjectHandlerWithData($data) + { + // Clear action group object handler value to inject parsed content + $property = new \ReflectionProperty(ActionGroupObjectHandler::class, 'instance'); + $property->setAccessible(true); + $property->setValue(null); + + $mockDataParser = AspectMock::double(ActionGroupDataParser::class, ['readActionGroupData' => $data])->make(); + $instance = AspectMock::double(ObjectManager::class, ['create' => $mockDataParser]) + ->make(); // bypass the private constructor + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + } +} diff --git a/dev/tests/util/MftfStaticTestCase.php b/dev/tests/util/MftfStaticTestCase.php new file mode 100644 index 000000000..708561031 --- /dev/null +++ b/dev/tests/util/MftfStaticTestCase.php @@ -0,0 +1,49 @@ +getMockBuilder(InputInterface::class) + ->disableOriginalConstructor() + ->getMock(); + if ($path) { + $input->method('getOption') + ->with('path') + ->willReturn($path); + } + return $input; + } + + public function tearDown(): void + { + DirSetupUtil::rmdirRecursive(self::STATIC_RESULTS_DIR); + } +} diff --git a/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml new file mode 100644 index 000000000..c83dab211 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml new file mode 100644 index 000000000..e8412a29e --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml b/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml new file mode 100644 index 000000000..75a6b6678 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Data/DeprecationCheckData.xml @@ -0,0 +1,14 @@ + + + + + + value + + diff --git a/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml b/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml new file mode 100644 index 000000000..757c6284b --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Metadata/DeprecationCheckMeta.xml @@ -0,0 +1,18 @@ + + + + + + application/json + + value1 + value2 + + + \ No newline at end of file diff --git a/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml b/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml new file mode 100644 index 000000000..dd3f187dc --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml b/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml new file mode 100644 index 000000000..1837e1ef5 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml @@ -0,0 +1,16 @@ + + + + +
+ + + +
+
diff --git a/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml b/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml new file mode 100644 index 000000000..b89ad99f6 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml new file mode 100644 index 000000000..1b3ace053 --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml new file mode 100644 index 000000000..1c4fc3dba --- /dev/null +++ b/dev/tests/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml new file mode 100644 index 000000000..5e9299631 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml new file mode 100644 index 000000000..8acae47e8 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithNoPauseActionGroup.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml new file mode 100644 index 000000000..aaef1befa --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml b/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml new file mode 100644 index 000000000..47ff1088b --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + dataHere + + + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml b/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml new file mode 100644 index 000000000..71a3b5769 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + dataHere + + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml b/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml new file mode 100644 index 000000000..fa47e976c --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml b/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml new file mode 100644 index 000000000..70d0903b8 --- /dev/null +++ b/dev/tests/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + diff --git a/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt new file mode 100644 index 000000000..08e0b44f8 --- /dev/null +++ b/dev/tests/verification/Resources/ActionGroupReturningValueTest.txt @@ -0,0 +1,75 @@ +Test filesverification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml
") + */ +class ActionGroupReturningValueTestCest +{ + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _before(AcceptanceTester $I) + { + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam + $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup + $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup + $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Stories({"MQE-433"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithReturnValue1] FunctionalActionGroupWithReturnValueActionGroup"); + $grabTextFrom1ActionGroupWithReturnValue1 = $I->grabTextFrom("#foo"); // stepKey: grabTextFrom1ActionGroupWithReturnValue1 + $actionGroupWithReturnValue1 = $I->return($grabTextFrom1ActionGroupWithReturnValue1); // stepKey: returnActionGroupWithReturnValue1 + $I->comment("Exiting Action Group [actionGroupWithReturnValue1] FunctionalActionGroupWithReturnValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupWithReturnValue1, "#element .{$actionGroupWithReturnValue1}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } +} diff --git a/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt b/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt new file mode 100644 index 000000000..d4e1b6eb3 --- /dev/null +++ b/dev/tests/verification/Resources/ActionsInDifferentModulesSuite.txt @@ -0,0 +1,218 @@ +webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); + // increment test count per execution + $this->currentTestRun++; + $this->executePreConditions(); + + if ($this->preconditionFailure != null) { + //if our preconditions fail, we need to mark all the tests as incomplete. + $e->getTest()->getMetadata()->setIncomplete("SUITE PRECONDITION FAILED:" . PHP_EOL . $this->preconditionFailure); + } + } + + private function executePreConditions() + { + if ($this->currentTestRun == 1) { + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); + + try { + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); + } + $cli = $this->getModuleForAction("magentoCLISecret")->magentoCLISecret($this->getModuleForAction("getSecret")->getSecret("magento/some/secret"), 60); // stepKey: cli + print($cli); // stepKey: cli + $create1Fields['someKey'] = "dataHere"; + PersistedObjectHandler::getInstance()->createEntity( + "create1", + "suite", + "SecretData", + [], + $create1Fields + ); + PersistedObjectHandler::getInstance()->createEntity( + "create2", + "suite", + "SecretData", + [] + ); + PersistedObjectHandler::getInstance()->createEntity( + "create3", + "suite", + "SecretData", + ["create1", "create2"] + ); + $this->getModuleForAction("fillSecretField")->fillSecretField("#fill", $this->getModuleForAction("getSecret")->getSecret("magento/some/secret") . "+" . $this->getModuleForAction("getSecret")->getSecret("magento/some/secret")); // stepKey: fillBefore + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create2', 'key2', 'suite')); // stepKey: click + print("Entering Action Group [return1] ActionGroupReturningValueActionGroup"); + $grabProducts1Return1 = $this->getModuleForAction("grabMultiple")->grabMultiple("selector"); // stepKey: grabProducts1Return1 + $this->getModuleForAction("assertCount")->assertCount(1, $grabProducts1Return1); // stepKey: assertCountReturn1 + $return1 = $this->getModuleForAction("return")->return($grabProducts1Return1); // stepKey: returnProducts1Return1 + print("Exiting Action Group [return1] ActionGroupReturningValueActionGroup"); + } catch (\Exception $exception) { + $this->preconditionFailure = $exception->getMessage(); + } + + // reset configuration and close session + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; + + print sprintf(self::$HOOK_EXECUTION_END, "before"); + } + } + + public function _after(\Codeception\Event\TestEvent $e) + { + $this->executePostConditions($e); + } + + private function executePostConditions(\Codeception\Event\TestEvent $e) + { + if ($this->currentTestRun == $this->testCount) { + print sprintf(self::$HOOK_EXECUTION_INIT, "after"); + + try { + // Find out if Test in Suite failed, will cause potential failures in suite after + $cest = $e->getTest(); + + //Access private TestResultObject to find stack and if there are any errors (as opposed to failures) + $testResultObject = call_user_func(\Closure::bind( + function () use ($cest) { + return $cest->getTestResultObject(); + }, + $cest + )); + $errors = $testResultObject->errors(); + + if (!empty($errors)) { + foreach ($errors as $error) { + if ($error->failedTest()->getTestMethod() == $cest->getName()) { + // Do not attempt to run _after if failure was in the _after block + // Try to run _after but catch exceptions to prevent them from overwriting original failure. + print("LAST TEST IN SUITE FAILED, TEST AFTER MAY NOT BE SUCCESSFUL\n"); + } + } + } + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); + } else { + $this->webDriver->_initializeSession(); + } + print("Entering Action Group [return2] ExtendedActionGroupReturningValueActionGroup"); + $grabProducts1Return2 = $this->getModuleForAction("grabMultiple")->grabMultiple("selector"); // stepKey: grabProducts1Return2 + $this->getModuleForAction("assertCount")->assertCount(1, $grabProducts1Return2); // stepKey: assertCountReturn2 + $return2 = $this->getModuleForAction("return")->return($grabProducts1Return2); // stepKey: returnProducts1Return2 + $grabProducts2Return2 = $this->getModuleForAction("grabMultiple")->grabMultiple("otherSelector"); // stepKey: grabProducts2Return2 + $this->getModuleForAction("assertCount")->assertCount(2, $grabProducts2Return2); // stepKey: assertSecondCountReturn2 + $return2 = $this->getModuleForAction("return")->return($grabProducts2Return2); // stepKey: returnProducts2Return2 + print("Exiting Action Group [return2] ExtendedActionGroupReturningValueActionGroup"); + PersistedObjectHandler::getInstance()->deleteEntity( + "create1", + "suite" + ); + PersistedObjectHandler::getInstance()->deleteEntity( + "create2", + "suite" + ); + PersistedObjectHandler::getInstance()->deleteEntity( + "create3", + "suite" + ); + $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: deleteThis + $this->getModuleForAction("fillSecretField")->fillSecretField("#fill", $this->getModuleForAction("getSecret")->getSecret("magento/some/secret")); // stepKey: fillAfter + $cli2 = $this->getModuleForAction("magentoCLISecret")->magentoCLISecret($this->getModuleForAction("getSecret")->getSecret("magento/some/secret") . "-some/data-" . $this->getModuleForAction("getSecret")->getSecret("magento/some/secret"), 60); // stepKey: cli2 + print($cli2); // stepKey: cli2 + } catch (\Exception $exception) { + print $exception->getMessage(); + } + + PersistedObjectHandler::getInstance()->clearSuiteObjects(); + + $this->closeSession($this->webDriver); + + print sprintf(self::$HOOK_EXECUTION_END, "after"); + } + } + + /** + * Close session method closes current session. + * If config 'close_all_sessions' is set to 'true' all sessions will be closed. + * + * return void + */ + private function closeSession(): void + { + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); + if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { + $wdHost = sprintf( + '%s://%s:%s%s', + $webDriverConfig['protocol'], + $webDriverConfig['host'], + $webDriverConfig['port'], + $webDriverConfig['path'] + ); + $availableSessions = RemoteWebDriver::getAllSessions($wdHost); + foreach ($availableSessions as $session) { + $remoteWebDriver = RemoteWebDriver::createBySessionID($session['id'], $wdHost); + $remoteWebDriver->quit(); + } + } + } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } +} diff --git a/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt new file mode 100644 index 000000000..2f26a6463 --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedActionGroupReturningValueTest.txt @@ -0,0 +1,40 @@ +Test filesverification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml
") + */ +class ExtendedActionGroupReturningValueTestCest +{ + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [actionGroupReturningValue] ActionGroupReturningValueActionGroup"); + $grabProducts1ActionGroupReturningValue = $I->grabMultiple("selector"); // stepKey: grabProducts1ActionGroupReturningValue + $I->assertCount(99, $grabProducts1ActionGroupReturningValue); // stepKey: assertCountActionGroupReturningValue + $actionGroupReturningValue = $I->return($grabProducts1ActionGroupReturningValue); // stepKey: returnProducts1ActionGroupReturningValue + $I->comment("Exiting Action Group [actionGroupReturningValue] ActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupReturningValue, "#element .{$actionGroupReturningValue}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } +} diff --git a/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt new file mode 100644 index 000000000..d043a5c04 --- /dev/null +++ b/dev/tests/verification/Resources/ExtendedChildActionGroupReturningValueTest.txt @@ -0,0 +1,43 @@ +Test filesverification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml
") + */ +class ExtendedChildActionGroupReturningValueTestCest +{ + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ExtendedChildActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->comment("Entering Action Group [extendedActionGroupReturningValue] ExtendedActionGroupReturningValueActionGroup"); + $grabProducts1ExtendedActionGroupReturningValue = $I->grabMultiple("selector"); // stepKey: grabProducts1ExtendedActionGroupReturningValue + $I->assertCount(99, $grabProducts1ExtendedActionGroupReturningValue); // stepKey: assertCountExtendedActionGroupReturningValue + $extendedActionGroupReturningValue = $I->return($grabProducts1ExtendedActionGroupReturningValue); // stepKey: returnProducts1ExtendedActionGroupReturningValue + $grabProducts2ExtendedActionGroupReturningValue = $I->grabMultiple("otherSelector"); // stepKey: grabProducts2ExtendedActionGroupReturningValue + $I->assertCount(8000, $grabProducts2ExtendedActionGroupReturningValue); // stepKey: assertSecondCountExtendedActionGroupReturningValue + $extendedActionGroupReturningValue = $I->return($grabProducts2ExtendedActionGroupReturningValue); // stepKey: returnProducts2ExtendedActionGroupReturningValue + $I->comment("Exiting Action Group [extendedActionGroupReturningValue] ExtendedActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($extendedActionGroupReturningValue, "#element .{$extendedActionGroupReturningValue}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } +} diff --git a/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt b/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt new file mode 100644 index 000000000..a4689779b --- /dev/null +++ b/dev/tests/verification/Resources/MergedActionGroupReturningValueTest.txt @@ -0,0 +1,77 @@ +Test filesverification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml
") + */ +class MergedActionGroupReturningValueTestCest +{ + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _before(AcceptanceTester $I) + { + $I->createEntity("createPersonParam", "hook", "ReplacementPerson", [], []); // stepKey: createPersonParam + $I->comment("Entering Action Group [beforeGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1BeforeGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2BeforeGroup + $I->comment("Exiting Action Group [beforeGroup] FunctionalActionGroup"); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _after(AcceptanceTester $I) + { + $I->comment("Entering Action Group [afterGroup] FunctionalActionGroup"); + $I->fillField("#foo", "myData1"); // stepKey: fillField1AfterGroup + $I->fillField("#bar", "myData2"); // stepKey: fillField2AfterGroup + $I->comment("Exiting Action Group [afterGroup] FunctionalActionGroup"); + } + + /** + * @param AcceptanceTester $I + * @throws \Exception + */ + public function _failed(AcceptanceTester $I) + { + $I->saveScreenshot(); // stepKey: saveScreenshot + } + + /** + * @Severity(level = SeverityLevel::CRITICAL) + * @Features({"TestModule"}) + * @Stories({"MQE-433"}) + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function MergedActionGroupReturningValueTest(AcceptanceTester $I) + { + $I->amOnPage("/someUrl"); // stepKey: step1 + $I->comment("Entering Action Group [actionGroupWithReturnValue1] MergeActionGroupReturningValueActionGroup"); + $I->amOnPage("/Jane/Dane.html"); // stepKey: amOnPage1ActionGroupWithReturnValue1 + $I->click(".merge .Jane"); // stepKey: myMergedClickActionGroupWithReturnValue1 + $grabMultiple1ActionGroupWithReturnValue1 = $I->grabMultiple("#foo"); // stepKey: grabMultiple1ActionGroupWithReturnValue1 + $actionGroupWithReturnValue1 = $I->return($grabMultiple1ActionGroupWithReturnValue1); // stepKey: returnValueActionGroupWithReturnValue1 + $I->comment("Exiting Action Group [actionGroupWithReturnValue1] MergeActionGroupReturningValueActionGroup"); + $I->comment("Entering Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + $I->see($actionGroupWithReturnValue1, "#element .{$actionGroupWithReturnValue1}"); // stepKey: see1ActionGroupWithStringUsage1 + $I->comment("Exiting Action Group [actionGroupWithStringUsage1] actionGroupWithStringUsage"); + } +} diff --git a/dev/tests/verification/Resources/PageReplacementTest.txt b/dev/tests/verification/Resources/PageReplacementTest.txt index cda119999..6f75ee096 100644 --- a/dev/tests/verification/Resources/PageReplacementTest.txt +++ b/dev/tests/verification/Resources/PageReplacementTest.txt @@ -35,8 +35,8 @@ class PageReplacementTestCest $I->amOnPage("/John/StringLiteral2.html"); // stepKey: twoParamPageStringData $I->amOnPage("/John/" . $I->retrieveEntityField('datakey', 'firstname', 'test') . ".html"); // stepKey: twoParamPageDataPersist $I->amOnPage("/" . $I->retrieveEntityField('datakey', 'firstname', 'test') . "/StringLiteral2.html"); // stepKey: twoParamPagePersistString - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/backend"); // stepKey: onAdminPage - $I->amOnPage("/" . getenv("MAGENTO_BACKEND_NAME") . "/StringLiteral/page.html"); // stepKey: oneParamAdminPageString + $I->amOnPage((getenv("MAGENTO_BACKEND_BASE_URL") ? rtrim(getenv("MAGENTO_BACKEND_BASE_URL"), "/") : "") . "/" . getenv("MAGENTO_BACKEND_NAME") . "/backend"); // stepKey: onAdminPage + $I->amOnPage((getenv("MAGENTO_BACKEND_BASE_URL") ? rtrim(getenv("MAGENTO_BACKEND_BASE_URL"), "/") : "") . "/" . getenv("MAGENTO_BACKEND_NAME") . "/StringLiteral/page.html"); // stepKey: oneParamAdminPageString $I->amOnUrl("http://myFullUrl.com/"); // stepKey: onExternalPage } } diff --git a/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt b/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt new file mode 100644 index 000000000..63e025470 --- /dev/null +++ b/dev/tests/verification/Resources/StaticChecks/mftf-deprecated-entity-usage-checks.txt @@ -0,0 +1,23 @@ + +File "/verification/DeprecationCheckModule/Test/DeprecationCheckTest.xml" contains: + - Deprecated Page(s): + "DeprecationCheckPage" in /verification/DeprecationCheckModule/Page/DeprecationCheckPage.xml + - Deprecated ActionGroup(s): + "DeprecationCheckDeprecatedActionGroup" in /verification/DeprecationCheckModule/ActionGroup/DeprecationCheckDeprecatedActionGroup.xml + - Deprecated Data(s): + "DeprecationCheckData" in /verification/DeprecationCheckModule/Data/DeprecationCheckData.xml + - "DeprecationCheckData" references deprecated: + "type1" in metadata xml file + + +File "/verification/DeprecationCheckModule/ActionGroup/DeprecationCheckActionGroup.xml" contains: + - Deprecated Section(s): + "DeprecationCheckSection" in /verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml + - Deprecated Element(s): + "DeprecationCheckSection.deprecationCheckElement" in /verification/DeprecationCheckModule/Section/DeprecationCheckSection.xml + + +File "/verification/DeprecationCheckModule/Suite/deprecationCheckSuite.xml" contains: + - Deprecated Test(s): + "DeprecationCheckDeprecatedTest" in /verification/DeprecationCheckModule/Test/DeprecationCheckDeprecatedTest.xml + diff --git a/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt b/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt new file mode 100644 index 000000000..fd042a24c --- /dev/null +++ b/dev/tests/verification/Resources/StaticChecks/mftf-pause-action-usage-checks.txt @@ -0,0 +1,30 @@ + +File "/verification/PauseCheckModule/ActionGroup/ActionGroupWithMultiplePausesActionGroup.xml" +contains pause action(s): + + ActionGroupWithMultiplePausesActionGroup has pause action at stepKey(s): pauseAfterFillField1, pauseAfterFillField2, pauseAfterFillField3 + +File "/verification/PauseCheckModule/ActionGroup/ActionGroupWithPauseActionGroup.xml" +contains pause action(s): + + ActionGroupWithPauseActionGroup has pause action at stepKey(s): pauseAfterFillField2 + +File "/verification/PauseCheckModule/Test/TestWithMultiplePauseActionsTest.xml" +contains pause action(s): + + TestWithMultiplePauseActionsTest has pause action at stepKey(s): pauseBeforeAmOnPageKey, pauseAfterStep2, pauseAfterAmOnPageKey + +File "/verification/PauseCheckModule/Test/TestWithPauseActionTest.xml" +contains pause action(s): + + TestWithPauseActionTest has pause action at stepKey(s): pauseAfterStep3 + +File "/verification/PauseCheckModule/Suite/suiteWithMultiplePauseActionsSuite.xml" +contains pause action(s): + + suiteWithMultiplePauseActionsSuite has pause action at stepKey(s): pauseCreate1, pauseFillAfter + +File "/verification/PauseCheckModule/Suite/suiteWithPauseActionSuite.xml" +contains pause action(s): + + suiteWithPauseActionSuite has pause action at stepKey(s): pauseSuite diff --git a/dev/tests/verification/Resources/functionalSuiteHooks.txt b/dev/tests/verification/Resources/functionalSuiteHooks.txt index 3b5f6bae0..3156a59dd 100644 --- a/dev/tests/verification/Resources/functionalSuiteHooks.txt +++ b/dev/tests/verification/Resources/functionalSuiteHooks.txt @@ -6,6 +6,11 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -24,9 +29,15 @@ class functionalSuiteHooks extends \Codeception\GroupObject private $currentTestRun = 0; private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of functionalSuiteHooks suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of functionalSuiteHooks suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); // increment test count per execution $this->currentTestRun++; $this->executePreConditions(); @@ -37,41 +48,56 @@ class functionalSuiteHooks extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { print sprintf(self::$HOOK_EXECUTION_INIT, "before"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { - if ($webDriver->webDriver != null) { - $webDriver->_restart(); + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); } else { - $webDriver->_initializeSession(); + $this->webDriver->_initializeSession(); } - $webDriver->amOnPage("some.url"); // stepKey: before - $createFields['someKey'] = "dataHere"; + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before + $createOneFields['someKey'] = "dataHere"; + PersistedObjectHandler::getInstance()->createEntity( + "createOne", + "suite", + "createEntityOne", + [], + $createOneFields + ); + PersistedObjectHandler::getInstance()->createEntity( + "createTwo", + "suite", + "createEntityTwo", + ["createEntityOne"] + ); + PersistedObjectHandler::getInstance()->createEntity( + "createThree", + "suite", + "createEntityThree", + [] + ); PersistedObjectHandler::getInstance()->createEntity( - "create", + "createFour", "suite", - "createThis", - $createFields + "createEntityFour", + ["createEntityTwo", "createEntityThree"] ); - $webDriver->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('createTwo', 'data', 'suite')); // stepKey: clickWithData print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); } catch (\Exception $exception) { $this->preconditionFailure = $exception->getMessage(); } // reset configuration and close session - $webDriver->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; print sprintf(self::$HOOK_EXECUTION_END, "before"); } @@ -82,15 +108,11 @@ class functionalSuiteHooks extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { print sprintf(self::$HOOK_EXECUTION_INIT, "after"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { // Find out if Test in Suite failed, will cause potential failures in suite after $cest = $e->getTest(); @@ -113,15 +135,15 @@ class functionalSuiteHooks extends \Codeception\GroupObject } } } - if ($webDriver->webDriver != null) { - $webDriver->_restart(); + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); } else { - $webDriver->_initializeSession(); + $this->webDriver->_initializeSession(); } - $webDriver->amOnPage("some.url"); // stepKey: after - $webDriver->deleteEntityByUrl("deleteThis"); // stepKey: delete + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: after + $this->getModuleForAction("deleteEntityByUrl")->deleteEntityByUrl("deleteThis"); // stepKey: delete print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); } catch (\Exception $exception) { print $exception->getMessage(); @@ -129,7 +151,7 @@ class functionalSuiteHooks extends \Codeception\GroupObject PersistedObjectHandler::getInstance()->clearSuiteObjects(); - $this->closeSession($webDriver); + $this->closeSession($this->webDriver); print sprintf(self::$HOOK_EXECUTION_END, "after"); } @@ -139,13 +161,12 @@ class functionalSuiteHooks extends \Codeception\GroupObject * Close session method closes current session. * If config 'close_all_sessions' is set to 'true' all sessions will be closed. * - * @param MagentoWebDriver $webDriver * return void */ - private function closeSession(MagentoWebDriver $webDriver): void + private function closeSession(): void { - $webDriverConfig = $webDriver->_getConfig(); - $webDriver->_closeSession(); + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { $wdHost = sprintf( '%s://%s:%s%s', @@ -161,4 +182,20 @@ class functionalSuiteHooks extends \Codeception\GroupObject } } } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } } diff --git a/dev/tests/verification/Resources/functionalSuiteWithComments.txt b/dev/tests/verification/Resources/functionalSuiteWithComments.txt index c8523d0c1..02b50f092 100644 --- a/dev/tests/verification/Resources/functionalSuiteWithComments.txt +++ b/dev/tests/verification/Resources/functionalSuiteWithComments.txt @@ -6,6 +6,11 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -24,9 +29,15 @@ class functionalSuiteWithComments extends \Codeception\GroupObject private $currentTestRun = 0; private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of functionalSuiteWithComments suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of functionalSuiteWithComments suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); // increment test count per execution $this->currentTestRun++; $this->executePreConditions(); @@ -37,43 +48,40 @@ class functionalSuiteWithComments extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { print sprintf(self::$HOOK_EXECUTION_INIT, "before"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { - if ($webDriver->webDriver != null) { - $webDriver->_restart(); + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); } else { - $webDriver->_initializeSession(); + $this->webDriver->_initializeSession(); } print("Comment in Before"); - $webDriver->amOnPage("some.url"); // stepKey: before + $this->getModuleForAction("amOnPage")->amOnPage("some.url"); // stepKey: before $createFields['someKey'] = "dataHere"; PersistedObjectHandler::getInstance()->createEntity( "create", "suite", "createThis", - $createFields + [], + $createFields ); print(""); - $webDriver->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData + $this->getModuleForAction("click")->click(PersistedObjectHandler::getInstance()->retrieveEntityField('create', 'data', 'suite')); // stepKey: clickWithData print("Entering Action Group [AC] actionGroupWithTwoArguments"); - $webDriver->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC + $this->getModuleForAction("see")->see("John", msq("uniqueData") . "John"); // stepKey: seeFirstNameAC print("Exiting Action Group [AC] actionGroupWithTwoArguments"); } catch (\Exception $exception) { $this->preconditionFailure = $exception->getMessage(); } // reset configuration and close session - $webDriver->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; print sprintf(self::$HOOK_EXECUTION_END, "before"); } @@ -84,15 +92,11 @@ class functionalSuiteWithComments extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { print sprintf(self::$HOOK_EXECUTION_INIT, "after"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { // Find out if Test in Suite failed, will cause potential failures in suite after $cest = $e->getTest(); @@ -115,10 +119,10 @@ class functionalSuiteWithComments extends \Codeception\GroupObject } } } - if ($webDriver->webDriver != null) { - $webDriver->_restart(); + if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); } else { - $webDriver->_initializeSession(); + $this->webDriver->_initializeSession(); } print("afterBlock"); } catch (\Exception $exception) { @@ -127,7 +131,7 @@ class functionalSuiteWithComments extends \Codeception\GroupObject PersistedObjectHandler::getInstance()->clearSuiteObjects(); - $this->closeSession($webDriver); + $this->closeSession($this->webDriver); print sprintf(self::$HOOK_EXECUTION_END, "after"); } @@ -137,13 +141,12 @@ class functionalSuiteWithComments extends \Codeception\GroupObject * Close session method closes current session. * If config 'close_all_sessions' is set to 'true' all sessions will be closed. * - * @param MagentoWebDriver $webDriver * return void */ - private function closeSession(MagentoWebDriver $webDriver): void + private function closeSession(): void { - $webDriverConfig = $webDriver->_getConfig(); - $webDriver->_closeSession(); + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { $wdHost = sprintf( '%s://%s:%s%s', @@ -159,4 +162,20 @@ class functionalSuiteWithComments extends \Codeception\GroupObject } } } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } } diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..ddb93c0c1 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + + + + {{count}} + grabProducts1 + + + + diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..18933a2f2 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup/ExtendedActionGroupReturningValueActionGroup.xml @@ -0,0 +1,21 @@ + + + + + + + + + + {{otherCount}} + grabProducts2 + + + + diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml new file mode 100644 index 000000000..77900efd3 --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/FunctionalActionGroupWithReturnValueActionGroup.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..83afbce0c --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/FunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml new file mode 100644 index 000000000..d952fdddf --- /dev/null +++ b/dev/tests/verification/TestModule/ActionGroup/MergeFunctionalActionGroup/MergeActionGroupReturningValueActionGroup.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml b/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml index 514b5eec6..7213fa6b9 100644 --- a/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/PersistedReplacementData.xml @@ -22,4 +22,8 @@ Dane unmerged + + some/data + {{_CREDS.magento/some/secret}} + diff --git a/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml new file mode 100644 index 000000000..e3553308d --- /dev/null +++ b/dev/tests/verification/TestModule/Suite/ActionsInDifferentModulesSuite.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + dataHere + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml index 4fc459e7d..b4dca7f8d 100644 --- a/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml +++ b/dev/tests/verification/TestModule/Suite/functionalSuiteHooks.xml @@ -13,10 +13,18 @@ - + dataHere - + + + + + + + + + diff --git a/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml new file mode 100644 index 000000000..ade1f51db --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupFunctionalTest/ActionGroupReturningValueTest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml new file mode 100644 index 000000000..68042344c --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedActionGroupReturningValueTest.xml @@ -0,0 +1,22 @@ + + + + + + + + </annotations> + <actionGroup ref="ActionGroupReturningValueActionGroup" stepKey="actionGroupReturningValue"> + <argument name="count" value="99"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$actionGroupReturningValue}"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml new file mode 100644 index 000000000..ecd46c3c4 --- /dev/null +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest/ExtendedChildActionGroupReturningValueTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ExtendedChildActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <title value="Extended Child ActionGroup Returning Value Test"/> + </annotations> + <actionGroup ref="ExtendedActionGroupReturningValueActionGroup" stepKey="extendedActionGroupReturningValue"> + <argument name="count" value="99"/> + <argument name="otherCount" value="8000"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$extendedActionGroupReturningValue}"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml new file mode 100644 index 000000000..baf7c0ded --- /dev/null +++ b/dev/tests/verification/TestModule/Test/MergeFunctionalTest/MergedActionGroupReturningValueTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="MergedActionGroupReturningValueTest"> + <annotations> + <severity value="CRITICAL"/> + <group value="functional"/> + <features value="Action Group Functional Cest"/> + <stories value="MQE-433"/> + </annotations> + <before> + <createData entity="ReplacementPerson" stepKey="createPersonParam"/> + <actionGroup ref="FunctionalActionGroup" stepKey="beforeGroup"/> + </before> + <amOnPage url="/someUrl" stepKey="step1"/> + <actionGroup ref="MergeActionGroupReturningValueActionGroup" stepKey="actionGroupWithReturnValue1"> + <argument name="myArg" value="DefaultPerson"/> + </actionGroup> + <actionGroup ref="actionGroupWithStringUsage" stepKey="actionGroupWithStringUsage1"> + <argument name="someArgument" value="{$actionGroupWithReturnValue1}"/> + </actionGroup> + <after> + <actionGroup ref="FunctionalActionGroup" stepKey="afterGroup"/> + </after> + </test> +</tests> diff --git a/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml new file mode 100644 index 000000000..1be662bbe --- /dev/null +++ b/dev/tests/verification/TestModule/Test/SampleSuiteTest/IncludeActionsInDifferentModulesTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="IncludeActionsInDifferentModulesTest"> + <createData entity="SecretData" stepKey="create"> + <field key="someKey">dataHere</field> + </createData> + <fillField selector="#fill" userInput="{{SecretData.key2}}+$create.key2$" stepKey="fill"/> + <magentoCLI command="create.key2$" stepKey="cli"/> + <actionGroup ref="ActionGroupReturningValueActionGroup" stepKey="return"> + <argument name="count" value="3"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php new file mode 100644 index 000000000..e369a797d --- /dev/null +++ b/dev/tests/verification/Tests/ActionGroupWithReturnGenerationTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use tests\util\MftfTestCase; + +class ActionGroupWithReturnGenerationTest extends MftfTestCase +{ + /** + * Test generation of a test referencing an action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testActionGroupReturningValue() + { + $this->generateAndCompareTest('ActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing a merged action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testMergedActionGroupReturningValue() + { + $this->generateAndCompareTest('MergedActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing an extended action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testExtendedActionGroupReturningValue() + { + $this->generateAndCompareTest('ExtendedActionGroupReturningValueTest'); + } + /** + * Test generation of a test referencing an extending child action group that returns a value. + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testExtendedChildActionGroupReturningValue() + { + $this->generateAndCompareTest('ExtendedChildActionGroupReturningValueTest'); + } +} diff --git a/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php b/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php new file mode 100644 index 000000000..f6aef65f6 --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/DeprecationStaticCheckTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\StaticCheck\DeprecatedEntityUsageCheck; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +use Symfony\Component\Console\Input\InputInterface; +use tests\util\MftfStaticTestCase; + +class DeprecationStaticCheckTest extends MftfStaticTestCase +{ + const LOG_FILE = self::STATIC_RESULTS_DIR . + DIRECTORY_SEPARATOR . + DeprecatedEntityUsageCheck::ERROR_LOG_FILENAME . + '.txt'; + + const TEST_MODULE_PATH = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + 'DeprecationCheckModule'. + DIRECTORY_SEPARATOR; + + /** + * test static-check DeprecatedEntityUsageCheck. + * + * @throws \Exception + */ + public function testDeprecatedEntityUsageCheck() + { + $staticCheck = new DeprecatedEntityUsageCheck(); + + $input = $this->mockInputInterface(self::TEST_MODULE_PATH); + AspectMock::double(StaticChecksList::class, ['getErrorFilesPath' => self::STATIC_RESULTS_DIR]); + + /** @var InputInterface $input */ + $staticCheck->execute($input); + + $this->assertTrue(file_exists(self::LOG_FILE)); + $this->assertFileEquals( + self::RESOURCES_PATH. + DIRECTORY_SEPARATOR . + DeprecatedEntityUsageCheck::ERROR_LOG_FILENAME . + ".txt", + self::LOG_FILE + ); + } +} diff --git a/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php b/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php new file mode 100644 index 000000000..7c8f77c2f --- /dev/null +++ b/dev/tests/verification/Tests/StaticCheck/PauseActionStaticCheckTest.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\verification\Tests; + +use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\StaticCheck\PauseActionUsageCheck; +use Magento\FunctionalTestingFramework\StaticCheck\StaticChecksList; +use Symfony\Component\Console\Input\InputInterface; + +use tests\util\MftfStaticTestCase; + +class PauseActionStaticCheckTest extends MftfStaticTestCase +{ + const LOG_FILE = self::STATIC_RESULTS_DIR . + DIRECTORY_SEPARATOR . + PauseActionUsageCheck::ERROR_LOG_FILENAME . + '.txt'; + + const TEST_MODULE_PATH = TESTS_MODULE_PATH . + DIRECTORY_SEPARATOR . + 'PauseCheckModule'. + DIRECTORY_SEPARATOR; + + /** + * test static-check PauseActionUsageCheck. + * + * @throws \Exception + */ + public function testPauseActionUsageCheck() + { + $staticCheck = new PauseActionUsageCheck(); + + $input = $this->mockInputInterface(self::TEST_MODULE_PATH); + AspectMock::double(StaticChecksList::class, ['getErrorFilesPath' => self::STATIC_RESULTS_DIR]); + + /** @var InputInterface $input */ + $staticCheck->execute($input); + + $this->assertTrue(file_exists(self::LOG_FILE)); + $this->assertFileEquals( + self::RESOURCES_PATH. + DIRECTORY_SEPARATOR . + PauseActionUsageCheck::ERROR_LOG_FILENAME . + ".txt", + self::LOG_FILE + ); + } +} diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index 4a8371692..f37283871 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -388,6 +388,65 @@ public function testSuiteCommentsGeneration() ); } + /** + * Test suite generation with actions from different modules + */ + public function testSuiteGenerationActionsInDifferentModules() + { + $groupName = 'ActionsInDifferentModulesSuite'; + + $expectedContents = [ + 'IncludeActionsInDifferentModulesTestCest.php' + ]; + + // Generate the Suite + SuiteGenerator::getInstance()->generateSuite($groupName); + + // Validate log message and add group name for later deletion + TestLoggingUtil::getInstance()->validateMockLogStatement( + 'info', + "suite generated", + ['suite' => $groupName, 'relative_path' => "_generated" . DIRECTORY_SEPARATOR . $groupName] + ); + self::$TEST_GROUPS[] = $groupName; + + // Validate Yaml file updated + $yml = Yaml::parse(file_get_contents(self::CONFIG_YML_FILE)); + $this->assertArrayHasKey($groupName, $yml['groups']); + + $suiteResultBaseDir = self::GENERATE_RESULT_DIR . + DIRECTORY_SEPARATOR . + $groupName . + DIRECTORY_SEPARATOR; + + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + + foreach ($expectedContents as $expectedFile) { + $this->assertTrue(in_array($expectedFile, $dirContents)); + } + + //assert group file created and contains correct contents + $groupFile = PROJECT_ROOT . + DIRECTORY_SEPARATOR . + "src" . + DIRECTORY_SEPARATOR . + "Magento" . + DIRECTORY_SEPARATOR . + "FunctionalTestingFramework" . + DIRECTORY_SEPARATOR . + "Group" . + DIRECTORY_SEPARATOR . + $groupName . + ".php"; + + $this->assertTrue(file_exists($groupFile)); + $this->assertFileEquals( + self::RESOURCES_PATH . DIRECTORY_SEPARATOR . $groupName . ".txt", + $groupFile + ); + } + /** * revert any changes made to config.yml * remove _generated directory diff --git a/docs/commands/mftf.md b/docs/commands/mftf.md index b7981808c..aabc8dadb 100644 --- a/docs/commands/mftf.md +++ b/docs/commands/mftf.md @@ -449,7 +449,7 @@ vendor/bin/mftf static-checks [<names>]... | Option | Description | |-----------------------|-----------------------------------------------------------------------------------------------------------| -| `-p, --path` | Path to a MFTF test module to run "deprecatedEntityUsage" static check script. Option is ignored by other static check scripts. +| `-p, --path` | Path to a MFTF test module to run "deprecatedEntityUsage" and "pauseActionUsage" static check scripts. Option is ignored by other static check scripts. #### Examples @@ -479,6 +479,10 @@ vendor/bin/mftf static-checks actionGroupArguments vendor/bin/mftf static-checks deprecatedEntityUsage ``` +```bash +vendor/bin/mftf static-checks pauseActionUsage +``` + ```bash vendor/bin/mftf static-checks annotations ``` @@ -487,6 +491,10 @@ vendor/bin/mftf static-checks annotations vendor/bin/mftf static-checks deprecatedEntityUsage -p path/to/mftf/test/module ``` +```bash +vendor/bin/mftf static-checks pauseActionUsage -p path/to/mftf/test/module +``` + ```bash vendor/bin/mftf static-checks testDependencies actionGroupArguments ``` @@ -499,6 +507,7 @@ vendor/bin/mftf static-checks testDependencies actionGroupArguments |`actionGroupArguments` | Checks that action groups do not have unused arguments.| |`deprecatedEntityUsage`| Checks that deprecated test entities are not being referenced.| |`annotations`| Checks various details of test annotations, such as missing annotations or duplicate annotations.| +|`pauseUsage`| Checks that pause action is not used in action groups, tests or suites.| #### Defining ruleset @@ -559,6 +568,47 @@ To upgrade all test components inside the `Catalog` module: vendor/bin/mftf upgrade:tests /Users/user/magento2/app/code/Magento/Catalog/Test/Mftf/ ``` +### `codecept:run` + +A MFTF wrapper command that invokes `vendor/bin/codecept run`. This command runs tests in functional suite. Tests must be generated before using this command. + +#### Usage + +See the [Run Command](https://codeception.com/docs/reference/Commands#Run). + +```bash +vendor/bin/mftf codecept:run [<suite|test>] --[<option(s)>] +``` + +#### Examples + +```bash +# Run all tests in functional suite +vendor/bin/mftf codecept:run functional +``` + +```bash +# Run all tests in functional suite with options +vendor/bin/mftf codecept:run functional --verbose --steps --debug +``` + +```bash +# Run one test +vendor/bin/mftf codecept:run functional Magento/_generated/default/AdminCreateCmsPageTestCest.php --debug +``` + +```bash +# Run all tests in default group +vendor/bin/mftf codecept:run functional --verbose --steps -g default +``` + +<div class="bs-callout-warning"> +<p> +Note: You may want to limit the usage of this Codeception command with arguments and options for "acceptance" only, since it is what's supported by MFTF. +When using this command, you should change "acceptance" to "functional" when referring to Codeception documentation. +</p> +</div> + <!-- LINK DEFINITIONS --> [configuration]: ../configuration.md diff --git a/docs/configuration.md b/docs/configuration.md index ce3adadd7..8cefb12b8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -359,6 +359,15 @@ Global MFTF configuration for the default amount of time (in seconds) that a tes WAIT_TIMEOUT=30 ``` +### ENABLE_PAUSE + +Enables the ability to pause test execution at any point, and enter an interactive shell where you can try commands in action. +When pause is enabled, MFTF will generate pause() command in _failed() hook so that test will pause execution when failed. + +```conf +ENABLE_PAUSE=true +``` + <!-- Link definitions --> [`MAGENTO_CLI_COMMAND_PATH`]: #magento_cli_command_path diff --git a/docs/data.md b/docs/data.md index 7f6fd59f1..100b95317 100644 --- a/docs/data.md +++ b/docs/data.md @@ -116,6 +116,8 @@ A test can also reference data that was returned as a result of [test actions][] Further in the test, the data grabbed by the `someSelector` selector can be referenced using the `stepKey` value. In this case, it is `grabStepKey`. +The `stepKey` value can only be referenced within the test scope that it is defined in (`test`, `before/after`). + The following example shows the usage of `grabValueFrom` in testing, where the returned value is used by action's `stepKey`: ```xml diff --git a/docs/interactive-pause.md b/docs/interactive-pause.md new file mode 100644 index 000000000..250f19936 --- /dev/null +++ b/docs/interactive-pause.md @@ -0,0 +1,71 @@ +# Interactive Pause + +It can be difficut to write a successful test on the first attempt. You will need to try different commands, with different arguments, before you find the correct path. + +Since Codeception 3.0, you can pause execution in any point and enter an interactive shell where you will be able to try commands in action. + +Now this `Interactive Pause` feature is available in MFTF. All you need to do is to set `ENABLE_PAUSE` to `true` in `.env`. + +Check [pause on codeception.com][] for documentation and a video to see `Interactive Pause` in action. + +In short, when a test gets to `$I->pause()` step, it stops and shows a console where you can try all available commands with auto-completion, stash commands, save screenshots, etc. + +## MFTF Run Commands + +The following MFTF run commands support `Interactive Pause` when `ENABLE_PAUSE` is set to `true`. + +```bash +vendor/bin/mftf run:group +``` + +```bash +vendor/bin/mftf run:test +``` + +```bash +vendor/bin/mftf run:manifest +``` + +```bash +vendor/bin/mftf run:failed +``` + +### Use `Interactive Pause` During Test Development + +Here is a typical work flow for this use case: + +- Set `ENABLE_PAUSE` to `true` under `.env` +- Add <pause> action in a test where you want to pause execution for debugging +- Run test +- Execution should pause at <pause> action and invoke interactive console +- Try out commands in interactive console +- Resume test execution by pressing `ENTER` + +### Use `Pause` On Test Failure + +When `ENABLE_PAUSE` is set to `true`, MFTF automatically generates `pause()` action in `_failed()` hook for tests and in `_failed()` function in `MagentoWebDriver`. +This allows you to use `pause` to debug test failure for a long running test. The work flow might look like: + +- Set `ENABLE_PAUSE` to `true` under `.env` +- Run test +- Execution pauses and invokes interactive console right after test fails +- Examine and debug on the spot of failure + +## MFTF Codecept Run Command + +You can also use MFTF's wrapper command to run Codeception directly and activate `Interactive Pause` by passing `--debug` option. +You do not need to set `ENABLE_PAUSE` to `true` for this command if you don't want to pause on test failure. + +```bash +vendor/bin/mftf codecept:run --debug +``` + +<div class="bs-callout-warning"> +<p> +Note: MFTF command "--debug" option has different meaning than Codeception command "--debug" mode option. +</p> +</div> + +<!-- Link definitions --> + +[pause on codeception.com]: https://codeception.com/docs/02-GettingStarted#Interactive-Pause diff --git a/docs/test/action-groups.md b/docs/test/action-groups.md index 70af0621a..05adf795e 100644 --- a/docs/test/action-groups.md +++ b/docs/test/action-groups.md @@ -180,6 +180,34 @@ MFTF resolves `{{myCustomEntity.field1}}` the same as it would in a `selector` o </actionGroup> ``` +## Return a value + +Action groups can return a value using a `return` tag. + +```xml +<actionGroup name="GetOrderIdActionGroup"> + <seeElement selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="assertOrderLink"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="orderId"/> + <return value="{$orderId}" stepKey="returnOrderId"/> +</actionGroup> +``` + +The value returned can be accessed in later steps using action group step key `{$getOrderId}`. +```xml +<actionGroup ref="GetOrderIdActionGroup" stepKey="getOrderId"/> +<!--Filter the Order using Order ID --> +<actionGroup ref="FilterOrderGridByIdActionGroup" stepKey="filterOrderGridById"> + <argument name="orderId" value="{$getOrderId}"/> +</actionGroup> +``` +### Convention to return a value + +The following conventions apply to action groups returning a value: +- Only action groups can return value. Use of `return` tag is dis-allowed in tests and suites. +- An action group does not support multiple `return` tags. +- For [merging action groups](../merging.md#merge-action-groups), `return` is allowed only in one of the merging action groups. +- Value returned by an action group can only be referenced within the scope that the action group is defined in (`test`, `before/after`). + ## Optimizing action group structures Structuring properly an action group increases code reusability and readability. diff --git a/docs/test/actions.md b/docs/test/actions.md index 9a51106de..42ecde053 100644 --- a/docs/test/actions.md +++ b/docs/test/actions.md @@ -151,6 +151,7 @@ The following test actions return a variable: * [grabValueFrom](#grabvaluefrom) * [executeJS](#executejs) * [getOTP](#getotp) +* [return](#return) Learn more in [Using data returned by test actions](../data.md#use-data-returned-by-test-actions). @@ -1240,6 +1241,22 @@ To access this value, use `{$grabInputName}` in later actions. --> <grabValueFrom selector="input#name" stepKey="grabInputName"/> ``` +### return + +Specifies what value is returned by an action group. The value can be then accessed in later steps using the action group stepKey. See [Action groups returning a value](./action-groups.md#return-a-value) for usage information. + +Attribute|Type|Use|Description +---|---|---|--- +`value`|string|required| value returned by action group. +`stepKey`|string|required| A unique identifier of the action. + +#### Example + +```xml +<!-- Returns value of $grabInputName to the calling --> +<return value="{$grabInputName}" stepKey="returnInputName"/> +``` + ### loadSessionSnapshot See [loadSessionSnapshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#loadSessionSnapshot). @@ -1447,7 +1464,7 @@ Attribute|Type|Use|Description ### pause -See [pause docs on codeception.com](https://codeception.com/docs/02-GettingStarted). +See usage of `<pause` in [interactive-pause](../interactive-pause.md) and [pause docs on codeception.com](https://codeception.com/docs/02-GettingStarted#Interactive-Pause). Attribute|Type|Use|Description ---|---|---|--- diff --git a/etc/config/.env.example b/etc/config/.env.example index 53b12036f..64a287adc 100644 --- a/etc/config/.env.example +++ b/etc/config/.env.example @@ -67,6 +67,9 @@ MODULE_ALLOWLIST=Magento_Framework,ConfigurableProductWishlist,ConfigurableProdu #ENABLE_BROWSER_LOG=true BROWSER_LOG_BLOCKLIST=other +#*** Uncomment and set to true to use Codeception's interactive pause functionality +#ENABLE_PAUSE=true + #*** Elastic Search version used for test ***# ELASTICSEARCH_VERSION=7 #*** End of .env ***# diff --git a/etc/di.xml b/etc/di.xml index e561911da..f5184808c 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -8,7 +8,7 @@ <!-- Entity value gets replaced in Dom.php before reading $xml --> <!DOCTYPE config [ - <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSortasserted|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatCurrency|generateDate|getOTP|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|loadSessionSnapshot|loginAsAdmin|magentoCLI|magentoCron|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pause|parseFloat|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertContains|assertStringContainsString|assertStringContainsStringIgnoringCase|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertStringNotContainsString|assertStringNotContainsStringIgnoringCase|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl|helper|assertEqualsWithDelta|assertEqualsCanonicalizing|assertEqualsIgnoringCase|assertNotEqualsWithDelta|assertNotEqualsCanonicalizing|assertNotEqualsIgnoringCase"> + <!ENTITY commonTestActions "acceptPopup|actionGroup|amOnPage|amOnUrl|amOnSubdomain|appendField|assertArrayIsSortasserted|assertElementContainsAttribute|attachFile|cancelPopup|checkOption|clearField|click|clickWithLeftButton|clickWithRightButton|closeAdminNotification|closeTab|comment|conditionalClick|createData|deleteData|updateData|getData|dontSee|dontSeeJsError|dontSeeCheckboxIsChecked|dontSeeCookie|dontSeeCurrentUrlEquals|dontSeeCurrentUrlMatches|dontSeeElement|dontSeeElementInDOM|dontSeeInCurrentUrl|dontSeeInField|dontSeeInFormFields|dontSeeInPageSource|dontSeeInSource|dontSeeInTitle|dontSeeLink|dontSeeOptionIsSelected|doubleClick|dragAndDrop|entity|executeJS|fillField|formatCurrency|generateDate|getOTP|grabAttributeFrom|grabCookie|grabFromCurrentUrl|grabMultiple|grabPageSource|grabTextFrom|grabValueFrom|return|loadSessionSnapshot|loginAsAdmin|magentoCLI|magentoCron|makeScreenshot|maximizeWindow|moveBack|moveForward|moveMouseOver|mSetLocale|mResetLocale|openNewTab|pause|parseFloat|pressKey|reloadPage|resetCookie|submitForm|resizeWindow|saveSessionSnapshot|scrollTo|scrollToTopOfPage|searchAndMultiSelectOption|see|seeCheckboxIsChecked|seeCookie|seeCurrentUrlEquals|seeCurrentUrlMatches|seeElement|seeElementInDOM|seeInCurrentUrl|seeInField|seeInFormFields|seeInPageSource|seeInPopup|seeInSource|seeInTitle|seeLink|seeNumberOfElements|seeOptionIsSelected|selectOption|setCookie|submitForm|switchToIFrame|switchToNextTab|switchToPreviousTab|switchToWindow|typeInPopup|uncheckOption|unselectOption|wait|waitForAjaxLoad|waitForElement|waitForElementChange|waitForElementNotVisible|waitForElementVisible|waitForPwaElementNotVisible|waitForPwaElementVisible|waitForJS|waitForLoadingMaskToDisappear|waitForPageLoad|waitForText|assertArrayHasKey|assertArrayNotHasKey|assertContains|assertStringContainsString|assertStringContainsStringIgnoringCase|assertCount|assertEmpty|assertEquals|assertFalse|assertFileExists|assertFileNotExists|assertGreaterOrEquals|assertGreaterThan|assertGreaterThanOrEqual|assertInstanceOf|assertIsEmpty|assertLessOrEquals|assertLessThan|assertLessThanOrEqual|assertNotContains|assertStringNotContainsString|assertStringNotContainsStringIgnoringCase|assertNotEmpty|assertNotEquals|assertNotInstanceOf|assertNotNull|assertNotRegExp|assertNotSame|assertNull|assertRegExp|assertSame|assertStringStartsNotWith|assertStringStartsWith|assertTrue|expectException|fail|dontSeeFullUrlEquals|dontSee|dontSeeFullUrlMatches|dontSeeInFullUrl|seeFullUrlEquals|seeFullUrlMatches|seeInFullUrl|grabFromFullUrl|helper|assertEqualsWithDelta|assertEqualsCanonicalizing|assertEqualsIgnoringCase|assertNotEqualsWithDelta|assertNotEqualsCanonicalizing|assertNotEqualsIgnoringCase"> ]> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../src/Magento/FunctionalTestingFramework/ObjectManager/etc/config.xsd"> diff --git a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php index f3becf935..401153c7b 100644 --- a/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/BaseGenerateCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\TestGenerator; @@ -21,9 +22,25 @@ use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Class BaseGenerateCommand + * @package Magento\FunctionalTestingFramework\Console + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class BaseGenerateCommand extends Command { const MFTF_NOTICES = "Placeholder text for MFTF notices\n"; + const CODECEPT_RUN = 'codecept:run'; + const CODECEPT_RUN_FUNCTIONAL = self::CODECEPT_RUN . ' functional '; + const CODECEPT_RUN_OPTION_NO_EXIT = ' --no-exit '; + + /** + * Enable pause() + * + * @var boolean + */ + private $enablePause = null; /** * Console output style @@ -218,4 +235,36 @@ protected function showMftfNotices(OutputInterface $output) $output->writeln(self::MFTF_NOTICES); } } + + /** + * Return if pause() is enabled + * + * @return boolean + */ + protected function pauseEnabled() + { + if (null === $this->enablePause) { + if (getenv('ENABLE_PAUSE') === 'true') { + $this->enablePause = true; + } else { + $this->enablePause = false; + } + } + return $this->enablePause; + } + + /** + * Runs the bin/mftf codecept:run command and returns exit code + * + * @param string $commandStr + * @param OutputInterface $output + * @return integer + * @throws \Exception + */ + protected function codeceptRunTest(string $commandStr, OutputInterface $output) + { + $input = new StringInput($commandStr); + $command = $this->getApplication()->find(self::CODECEPT_RUN); + return $command->run($input, $output); + } } diff --git a/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php b/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php new file mode 100644 index 000000000..3992aa677 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/Codecept/CodeceptCommandUtil.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console\Codecept; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArgvInput; + +class CodeceptCommandUtil +{ + const CODECEPTION_AUTOLOAD_FILE = PROJECT_ROOT . '/vendor/codeception/codeception/autoload.php'; + + /** + * Current working directory + * + * @var string + */ + private $cwd = null; + + /** + * Setup Codeception + * + * @param InputInterface $input + * @return void + */ + public function setup(InputInterface $input) + { + require_once realpath(self::CODECEPTION_AUTOLOAD_FILE); + + $tokens = preg_split('{\\s+}', $input->__toString()); + $tokens[0] = str_replace('codecept:', '', $tokens[0]); + \Closure::bind(function &(ArgvInput $input) use ($tokens) { + return $input->setTokens($tokens); + }, null, ArgvInput::class); + } + + /** + * Save Codeception working directory + * + * @return void + * @throws TestFrameworkException + */ + public function setCodeceptCwd() + { + $this->cwd = getcwd(); + chdir(FilePathFormatter::format(TESTS_BP, false)); + } + + /** + * Restore current working directory + * + * @return void + */ + public function restoreCwd() + { + if ($this->cwd) { + chdir($this->cwd); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php b/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php new file mode 100644 index 000000000..de16bad84 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/CodeceptRunCommand.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Codeception\Command\Run; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\FunctionalTestingFramework\Console\Codecept\CodeceptCommandUtil; + +class CodeceptRunCommand extends Run +{ + /** + * Configures the current command + * + * @return void + */ + protected function configure() + { + $this->setName('codecept:run') + ->setDescription( + "Wrapper command to vendor/bin/codecept:run. See https://codeception.com/docs/reference/Commands#Run" + ); + + parent::configure(); + } + + /** + * Executes the current command + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + * @throws \Exception + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $commandUtil = new CodeceptCommandUtil(); + $commandUtil->setup($input); + $commandUtil->setCodeceptCwd(); + + try { + $exitCode = parent::execute($input, $output); + } catch (\Exception $e) { + throw new TestFrameworkException( + 'Make sure cest files are generated before running bin/mftf ' + . $this->getName() + . PHP_EOL + . $e->getMessage() + ); + } + + $commandUtil->restoreCwd(); + return $exitCode; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/CommandList.php b/src/Magento/FunctionalTestingFramework/Console/CommandList.php index f77c2c576..840e7dd86 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CommandList.php +++ b/src/Magento/FunctionalTestingFramework/Console/CommandList.php @@ -30,6 +30,7 @@ public function __construct(array $commands = []) { $this->commands = [ 'build:project' => new BuildProjectCommand(), + 'codecept:run' => new CodeceptRunCommand(), 'doctor' => new DoctorCommand(), 'generate:suite' => new GenerateSuiteCommand(), 'generate:tests' => new GenerateTestsCommand(), diff --git a/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php index 37a90bb77..683be1b53 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunManifestCommand.php @@ -11,6 +11,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; use Magento\FunctionalTestingFramework\Util\Path\FilePathFormatter; @@ -80,12 +81,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int // Delete the Codeception failed file just in case it exists from any previous test runs $this->deleteFailedFile(); - foreach ($manifestFile as $manifestLine) { - if (empty($manifestLine)) { + for ($line = 0; $line < count($manifestFile); $line++) { + if (empty($manifestFile[$line])) { continue; } - $this->runManifestLine($manifestLine, $output); + if ($line == count($manifestFile) - 1) { + $this->runManifestLine($manifestFile[$line], $output, true); + } else { + $this->runManifestLine($manifestFile[$line], $output); + } + $this->aggregateFailed(); } @@ -102,24 +108,38 @@ protected function execute(InputInterface $input, OutputInterface $output): int * * @param string $manifestLine * @param OutputInterface $output + * @param boolean $exit * @return void + * @throws \Exception * * @SuppressWarnings(PHPMD.UnusedLocalVariable) Need this because of the unused $type variable in the closure */ - private function runManifestLine(string $manifestLine, OutputInterface $output) + private function runManifestLine($manifestLine, $output, $exit = false) { - $codeceptionCommand = realpath(PROJECT_ROOT . "/vendor/bin/codecept") - . " run functional --verbose --steps " - . $manifestLine; - - // run the codecept command in a sub process - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); - $subReturnCode = $process->run(function ($type, $buffer) use ($output) { - $output->write($buffer); - }); + if (getenv('ENABLE_PAUSE') === 'true') { + $codeceptionCommand = BaseGenerateCommand::CODECEPT_RUN_FUNCTIONAL + . '--verbose --steps --debug '; + if (!$exit) { + $codeceptionCommand .= BaseGenerateCommand::CODECEPT_RUN_OPTION_NO_EXIT; + } + $codeceptionCommand .= $manifestLine; + $input = new StringInput($codeceptionCommand); + $command = $this->getApplication()->find(BaseGenerateCommand::CODECEPT_RUN); + $subReturnCode = $command->run($input, $output); + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . "/vendor/bin/codecept") + . " run functional --verbose --steps " . $manifestLine; + + // run the codecept command in a sub process + $process = new Process($codeceptionCommand); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $subReturnCode = $process->run(function ($type, $buffer) use ($output) { + $output->write($buffer); + }); + } + $this->returnCode = max($this->returnCode, $subReturnCode); } diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php index 373256cfc..6931e8303 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestCommand.php @@ -119,25 +119,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int * @param OutputInterface $output * @return void * @throws TestFrameworkException + * @throws \Exception */ private function runTests(array $tests, OutputInterface $output) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL; + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; + } + $testsDirectory = FilePathFormatter::format(TESTS_MODULE_PATH) . TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . TestGenerator::DEFAULT_DIR . DIRECTORY_SEPARATOR ; - foreach ($tests as $test) { - $testName = $test . 'Cest.php'; + for ($i = 0; $i < count($tests); $i++) { + $testName = $tests[$i] . 'Cest.php'; if (!realpath($testsDirectory . $testName)) { throw new TestFrameworkException( $testName . " is not available under " . $testsDirectory ); } - $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps'; - $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + + if ($this->pauseEnabled()) { + $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps --debug'; + if ($i != count($tests) - 1) { + $fullCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; + } + $this->returnCode = max($this->returnCode, $this->codeceptRunTest($fullCommand, $output)); + } else { + $fullCommand = $codeceptionCommand . $testsDirectory . $testName . ' --verbose --steps'; + $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + } } } @@ -147,14 +162,32 @@ private function runTests(array $tests, OutputInterface $output) * @param array $suitesConfig * @param OutputInterface $output * @return void + * @throws \Exception */ private function runTestsInSuite(array $suitesConfig, OutputInterface $output) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps '; + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL . '--verbose --steps --debug'; + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') + . ' run functional --verbose --steps '; + } + + $count = count($suitesConfig); + $index = 0; //for tests in suites, run them as a group to run before and after block foreach (array_keys($suitesConfig) as $suite) { $fullCommand = $codeceptionCommand . " -g {$suite}"; - $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + + $index += 1; + if ($this->pauseEnabled()) { + if ($index != $count) { + $fullCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; + } + $this->returnCode = max($this->returnCode, $this->codeceptRunTest($fullCommand, $output)); + } else { + $this->returnCode = max($this->returnCode, $this->executeTestCommand($fullCommand, $output)); + } } } @@ -173,6 +206,7 @@ private function executeTestCommand(string $command, OutputInterface $output) $process->setWorkingDirectory(TESTS_BP); $process->setIdleTimeout(600); $process->setTimeout(0); + return $process->run(function ($type, $buffer) use ($output) { $output->write($buffer); }); diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php index c6a2d0b76..6e6954c70 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestFailedCommand.php @@ -116,19 +116,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int $testManifestList = $this->readTestManifestFile(); $returnCode = 0; - foreach ($testManifestList as $testCommand) { - $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; - $codeceptionCommand .= $testCommand; - - $process = new Process($codeceptionCommand); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); - $returnCode = max($returnCode, $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); + for ($i = 0; $i < count($testManifestList); $i++) { + if ($this->pauseEnabled()) { + $codeceptionCommand = self::CODECEPT_RUN_FUNCTIONAL . $testManifestList[$i] . ' --debug '; + if ($i != count($testManifestList) - 1) { + $codeceptionCommand .= self::CODECEPT_RUN_OPTION_NO_EXIT; } - )); + $returnCode = $this->codeceptRunTest($codeceptionCommand, $output); + } else { + $codeceptionCommand = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional '; + $codeceptionCommand .= $testManifestList[$i]; + + $process = new Process($codeceptionCommand); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $returnCode = max($returnCode, $process->run( + function ($type, $buffer) use ($output) { + $output->write($buffer); + } + )); + } + if (file_exists($this->testsFailedFile)) { $this->failedList = array_merge( $this->failedList, @@ -136,6 +145,7 @@ function ($type, $buffer) use ($output) { ); } } + foreach ($this->failedList as $test) { $this->writeFailedTestToFile($test, $this->testsFailedFile); } diff --git a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php index 6ea37785d..f18b9dc30 100644 --- a/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/RunTestGroupCommand.php @@ -52,6 +52,7 @@ protected function configure() * @throws \Exception * * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function execute(InputInterface $input, OutputInterface $output): int { @@ -94,23 +95,33 @@ protected function execute(InputInterface $input, OutputInterface $output): int $command->run(new ArrayInput($args), $output); } - $commandString = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; + if ($this->pauseEnabled()) { + $commandString = self::CODECEPT_RUN_FUNCTIONAL . '--verbose --steps --debug'; + } else { + $commandString = realpath(PROJECT_ROOT . '/vendor/bin/codecept') . ' run functional --verbose --steps'; + } $exitCode = -1; $returnCodes = []; - foreach ($groups as $group) { - $codeceptionCommandString = $commandString . " -g {$group}"; - - $process = new Process($codeceptionCommandString); - $process->setWorkingDirectory(TESTS_BP); - $process->setIdleTimeout(600); - $process->setTimeout(0); + for ($i = 0; $i < count($groups); $i++) { + $codeceptionCommandString = $commandString . ' -g ' . $groups[$i]; - $returnCodes[] = $process->run( - function ($type, $buffer) use ($output) { - $output->write($buffer); + if ($this->pauseEnabled()) { + if ($i != count($groups) - 1) { + $codeceptionCommandString .= self::CODECEPT_RUN_OPTION_NO_EXIT; } - ); + $returnCodes[] = $this->codeceptRunTest($codeceptionCommandString, $output); + } else { + $process = new Process($codeceptionCommandString); + $process->setWorkingDirectory(TESTS_BP); + $process->setIdleTimeout(600); + $process->setTimeout(0); + $returnCodes[] = $process->run( + function ($type, $buffer) use ($output) { + $output->write($buffer); + } + ); + } } foreach ($returnCodes as $returnCode) { diff --git a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php index fe07dbaaf..806508ef8 100644 --- a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -151,12 +151,17 @@ private function validateInput(InputInterface $input) } if ($input->getOption('path')) { - if ( (count($this->staticCheckObjects) !== 1) - || array_keys($this->staticCheckObjects)[0] !== StaticChecksList::DEPRECATED_ENTITY_USAGE_CHECK_NAME ) + if ((count($this->staticCheckObjects) !== 1) + || !in_array( + array_keys($this->staticCheckObjects)[0], + [ + StaticChecksList::DEPRECATED_ENTITY_USAGE_CHECK_NAME, + StaticChecksList::PAUSE_ACTION_USAGE_CHECK_NAME + ] + ) + ) throw new InvalidArgumentException( - '--path option can only be used for "' - . StaticChecksList::DEPRECATED_ENTITY_USAGE_CHECK_NAME - . '".' + '--path option is not supported for the command."' ); } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index 5a4b42dc4..87dc6a13d 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -191,8 +191,8 @@ private function processParserOutput($parserOutput) if (array_key_exists(self::OBJ_DEPRECATED, $rawEntity)) { $deprecated = $rawEntity[self::OBJ_DEPRECATED]; LoggingUtil::getInstance()->getLogger(self::class)->deprecation( - $deprecated, - ["dataName" => $filename, "deprecatedEntity" => $deprecated] + "The data entity '{$name}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $deprecated] ); } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php index 2c8ab7530..eb4651b3e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/OperationDefinitionObjectHandler.php @@ -215,8 +215,8 @@ private function initialize() if ($deprecated !== null) { LoggingUtil::getInstance()->getLogger(self::class)->deprecation( - $deprecated, - ["operationName" => $dataDefName, "deprecatedOperation" => $deprecated] + $message = "The operation {$dataDefName} is deprecated.", + ["operationType" => $operation, "deprecatedMessage" => $deprecated] ); } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php index 67fe48300..ef1a7faa5 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/OperationDefinitionObject.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\DataGenerator\Objects; +use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil; + /** * Class OperationDefinitionObject * @SuppressWarnings(PHPMD) @@ -341,4 +343,20 @@ public function addQueryParams() $this->apiUrl = $this->apiUrl . $paramName . "=" . $paramValue; } } + + /** + * Function to log a referenced deprecated operation at runtime. + * + * @return void + */ + public function logDeprecated() + { + if ($this->deprecated != null) { + LoggingUtil::getInstance()->getLogger(self::class)->deprecation( + $message = "The operation {$this->name} is deprecated.", + ["operationType" => $this->operation, "deprecatedMessage" => $this->deprecated], + true + ); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index 362d25e75..89055b83f 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -123,6 +123,7 @@ public function executeRequest($dependentEntities) $returnRegex = $this->operationDefinition->getReturnRegex(); $returnIndex = $this->operationDefinition->getReturnIndex(); $method = $this->operationDefinition->getApiMethod(); + $this->operationDefinition->logDeprecated(); AllureHelper::addAttachmentToCurrentStep($apiUrl, 'API Endpoint'); AllureHelper::addAttachmentToCurrentStep(json_encode($headers, JSON_PRETTY_PRINT), 'Request Headers'); diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php b/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php index 5a3b2360b..55b19a7c1 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoActionProxies.php @@ -20,5 +20,122 @@ */ class MagentoActionProxies extends CodeceptionModule { - // TODO: placeholder for proxy functions currently in MagentoWebDriver (MQE-1904) + /** + * Create an entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @param string $entity Name of xml entity to create. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @param array $overrideFields Array of FieldName => Value of override fields. + * @param string $storeCode + * @return void + */ + public function createEntity( + $key, + $scope, + $entity, + $dependentObjectKeys = [], + $overrideFields = [], + $storeCode = '' + ) { + PersistedObjectHandler::getInstance()->createEntity( + $key, + $scope, + $entity, + $dependentObjectKeys, + $overrideFields, + $storeCode + ); + } + + /** + * Retrieves and updates a previously created entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @param string $updateEntity Name of the static XML data to update the entity with. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @return void + */ + public function updateEntity($key, $scope, $updateEntity, $dependentObjectKeys = []) + { + PersistedObjectHandler::getInstance()->updateEntity( + $key, + $scope, + $updateEntity, + $dependentObjectKeys + ); + } + + /** + * Performs GET on given entity and stores entity for use + * + * @param string $key StepKey of getData action. + * @param string $scope + * @param string $entity Name of XML static data to use. + * @param array $dependentObjectKeys StepKeys of other createData actions that are required. + * @param string $storeCode + * @param integer $index + * @return void + */ + public function getEntity($key, $scope, $entity, $dependentObjectKeys = [], $storeCode = '', $index = null) + { + PersistedObjectHandler::getInstance()->getEntity( + $key, + $scope, + $entity, + $dependentObjectKeys, + $storeCode, + $index + ); + } + + /** + * Retrieves and deletes a previously created entity + * + * @param string $key StepKey of the createData action. + * @param string $scope + * @return void + */ + public function deleteEntity($key, $scope) + { + PersistedObjectHandler::getInstance()->deleteEntity($key, $scope); + } + + /** + * Retrieves a field from an entity, according to key and scope given + * + * @param string $stepKey + * @param string $field + * @param string $scope + * @return string + */ + public function retrieveEntityField($stepKey, $field, $scope) + { + return PersistedObjectHandler::getInstance()->retrieveEntityField($stepKey, $field, $scope); + } + + /** + * Get encrypted value by key + * + * @param string $key + * @return string|null + * @throws TestFrameworkException + */ + public function getSecret($key) + { + return CredentialStore::getInstance()->getSecret($key); + } + + /** + * Returns a value to origin of the action + * + * @param mixed $value + * @return mixed + */ + public function return($value) + { + return $value; + } } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php index 9b75cb10d..272f4206d 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoPwaWebDriver.php @@ -17,37 +17,57 @@ */ class MagentoPwaWebDriver extends MagentoWebDriver { + /** + * List of known PWA loading masks by selector + * + * Overriding the MagentoWebDriver array to contain applicable PWA locators. + * + * @var array + */ + protected $loadingMasksLocators = [ + '//div[contains(@class, "indicator-global-")]', + '//div[contains(@class, "indicator-root-")]', + '//img[contains(@class, "indicator-indicator-")]', + '//span[contains(@class, "indicator-message-")]' + ]; + /** * Go to the page. * * Overriding the MagentoWebDriver version because it contains 'waitForPageLoad'. * The AJAX check in 'waitForPageLoad' does NOT work with a PWA. * - * @param string $page + * @param string $page + * @param integer $timeout * @throws \Exception * @return void */ - public function amOnPage($page) + public function amOnPage($page, $timeout = null) { WebDriver::amOnPage($page); + $this->waitForLoadingMaskToDisappear($timeout); } /** * Wait for a PWA Element to NOT be visible using JavaScript. * Add the WAIT_TIMEOUT variable to your .env file for this action. * - * @param null $selector - * @param null $timeout + * @param string $selector + * @param integer $timeout * @throws \Exception * @return void */ public function waitForPwaElementNotVisible($selector, $timeout = null) { + $timeout = $timeout ?? $this->_getConfig()['pageload_timeout']; + // Determine what type of Selector is used. // Then use the correct JavaScript to locate the Element. if (\Codeception\Util\Locator::isXPath($selector)) { + $this->waitForLoadingMaskToDisappear($timeout); $this->waitForJS("return !document.evaluate(`$selector`, document);", $timeout); } else { + $this->waitForLoadingMaskToDisappear($timeout); $this->waitForJS("return !document.querySelector(`$selector`);", $timeout); } } @@ -56,18 +76,22 @@ public function waitForPwaElementNotVisible($selector, $timeout = null) * Wait for a PWA Element to be visible using JavaScript. * Add the WAIT_TIMEOUT variable to your .env file for this action. * - * @param null $selector - * @param null $timeout + * @param string $selector + * @param integer $timeout * @throws \Exception * @return void */ public function waitForPwaElementVisible($selector, $timeout = null) { + $timeout = $timeout ?? $this->_getConfig()['pageload_timeout']; + // Determine what type of Selector is used. // Then use the correct JavaScript to locate the Element. if (\Codeception\Util\Locator::isXPath($selector)) { + $this->waitForLoadingMaskToDisappear($timeout); $this->waitForJS("return !!document && !!document.evaluate(`$selector`, document);", $timeout); } else { + $this->waitForLoadingMaskToDisappear($timeout); $this->waitForJS("return !!document && !!document.querySelector(`$selector`);", $timeout); } } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 984881e4e..00e295606 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -15,6 +15,7 @@ use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; use Codeception\Util\Uri; +use Codeception\Lib\ModuleContainer; use Magento\FunctionalTestingFramework\DataTransport\WebApiExecutor; use Magento\FunctionalTestingFramework\DataTransport\Auth\WebApiAuth; use Magento\FunctionalTestingFramework\DataTransport\Auth\Tfa\OTP; @@ -53,7 +54,9 @@ class MagentoWebDriver extends WebDriver { use AttachmentSupport; - use Pause; + use Pause { + pause as codeceptPause; + } const MAGENTO_CRON_INTERVAL = 60; const MAGENTO_CRON_COMMAND = 'cron:run'; @@ -63,7 +66,7 @@ class MagentoWebDriver extends WebDriver * * @var array */ - public static $loadingMasksLocators = [ + protected $loadingMasksLocators = [ '//div[contains(@class, "loading-mask")]', '//div[contains(@class, "admin_data-grid-loading-mask")]', '//div[contains(@class, "admin__data-grid-loading-mask")]', @@ -180,6 +183,16 @@ public function _after(TestInterface $test) // DO NOT RESET SESSIONS } + /** + * Return ModuleContainer + * + * @return ModuleContainer + */ + public function getModuleContainer() + { + return $this->moduleContainer; + } + /** * Returns URL of a host. * @@ -439,7 +452,9 @@ public function waitForPageLoad($timeout = null) */ public function waitForLoadingMaskToDisappear($timeout = null) { - foreach (self::$loadingMasksLocators as $maskLocator) { + $timeout = $timeout ?? $this->_getConfig()['pageload_timeout']; + + foreach ($this->loadingMasksLocators as $maskLocator) { // Get count of elements found for looping. // Elements are NOT useful for interaction, as they cannot be fed to codeception actions. $loadingMaskElements = $this->_findElements($maskLocator); @@ -748,19 +763,25 @@ public function dragAndDrop($source, $target, $xOffset = null, $yOffset = null) $snodes = $this->matchFirstOrFail($this->baseElement, $source); $tnodes = $this->matchFirstOrFail($this->baseElement, $target); $action = new WebDriverActions($this->webDriver); - if ($xOffset !== null || $yOffset !== null) { $targetX = intval($tnodes->getLocation()->getX() + $xOffset); $targetY = intval($tnodes->getLocation()->getY() + $yOffset); - $travelX = intval($targetX - $snodes->getLocation()->getX()); $travelY = intval($targetY - $snodes->getLocation()->getY()); $action->moveToElement($snodes); $action->clickAndHold($snodes); + // Fix Start + $action->moveByOffset(-1, -1); + $action->moveByOffset(1, 1); + // Fix End $action->moveByOffset($travelX, $travelY); $action->release()->perform(); } else { $action->clickAndHold($snodes); + // Fix Start + $action->moveByOffset(-1, -1); + $action->moveByOffset(1, 1); + // Fix End $action->moveToElement($tnodes); $action->release($tnodes)->perform(); } @@ -823,6 +844,9 @@ public function _failed(TestInterface $test, $fail) if ($this->pngReport === null && $this->htmlReport === null) { $this->saveScreenshot(); + if (getenv('ENABLE_PAUSE') === 'true') { + $this->pause(true); + } } if ($this->current_test == null) { @@ -864,7 +888,7 @@ public function saveScreenshot() */ public function amOnPage($page) { - parent::amOnPage($page); + (0 === strpos($page, 'http')) ? parent::amOnUrl($page) : parent::amOnPage($page); $this->waitForPageLoad(); } @@ -954,120 +978,6 @@ public function getOTP() return OTP::getOTP(); } - /** - * Create an entity - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $key StepKey of the createData action. - * @param string $scope - * @param string $entity Name of xml entity to create. - * @param array $dependentObjectKeys StepKeys of other createData actions that are required. - * @param array $overrideFields Array of FieldName => Value of override fields. - * @param string $storeCode - * @return void - */ - public function createEntity( - $key, - $scope, - $entity, - $dependentObjectKeys = [], - $overrideFields = [], - $storeCode = '' - ) { - PersistedObjectHandler::getInstance()->createEntity( - $key, - $scope, - $entity, - $dependentObjectKeys, - $overrideFields, - $storeCode - ); - } - - /** - * Retrieves and updates a previously created entity - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $key StepKey of the createData action. - * @param string $scope - * @param string $updateEntity Name of the static XML data to update the entity with. - * @param array $dependentObjectKeys StepKeys of other createData actions that are required. - * @return void - */ - public function updateEntity($key, $scope, $updateEntity, $dependentObjectKeys = []) - { - PersistedObjectHandler::getInstance()->updateEntity( - $key, - $scope, - $updateEntity, - $dependentObjectKeys - ); - } - - /** - * Performs GET on given entity and stores entity for use - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $key StepKey of getData action. - * @param string $scope - * @param string $entity Name of XML static data to use. - * @param array $dependentObjectKeys StepKeys of other createData actions that are required. - * @param string $storeCode - * @param integer $index - * @return void - */ - public function getEntity($key, $scope, $entity, $dependentObjectKeys = [], $storeCode = '', $index = null) - { - PersistedObjectHandler::getInstance()->getEntity( - $key, - $scope, - $entity, - $dependentObjectKeys, - $storeCode, - $index - ); - } - - /** - * Retrieves and deletes a previously created entity - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $key StepKey of the createData action. - * @param string $scope - * @return void - */ - public function deleteEntity($key, $scope) - { - PersistedObjectHandler::getInstance()->deleteEntity($key, $scope); - } - - /** - * Retrieves a field from an entity, according to key and scope given - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $stepKey - * @param string $field - * @param string $scope - * @return string - */ - public function retrieveEntityField($stepKey, $field, $scope) - { - return PersistedObjectHandler::getInstance()->retrieveEntityField($stepKey, $field, $scope); - } - - /** - * Get encrypted value by key - * TODO: move this function to MagentoActionProxies after MQE-1904 - * - * @param string $key - * @return string|null - * @throws TestFrameworkException - */ - public function getSecret($key) - { - return CredentialStore::getInstance()->getSecret($key); - } - /** * Waits proper amount of time to perform Cron execution * @@ -1120,4 +1030,23 @@ public function switchToIFrame($locator = null) $this->webDriver->switchTo()->frame($els[0]); } } + + /** + * Invoke Codeption pause() + * + * @param boolean $pauseOnFail + * @return void + */ + public function pause($pauseOnFail = false) + { + if (!\Codeception\Util\Debug::isEnabled()) { + return; + } + + if ($pauseOnFail) { + print(PHP_EOL . "Failure encountered. Pausing execution..." . PHP_EOL . PHP_EOL); + } + + $this->codeceptPause(); + } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index 73695c182..8f4302077 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -79,8 +79,8 @@ private function __construct() if ($deprecated !== null) { LoggingUtil::getInstance()->getLogger(self::class)->deprecation( - $deprecated, - ["pageName" => $filename, "deprecatedPage" => $deprecated] + "The page '{$pageName}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $deprecated] ); } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php index 5d658bac0..6ac3456fd 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -89,8 +89,8 @@ private function __construct() $elementDeprecated = $elementData[self::OBJ_DEPRECATED] ?? null; if ($elementDeprecated !== null) { LoggingUtil::getInstance()->getLogger(ElementObject::class)->deprecation( - $elementDeprecated, - ["elementName" => $elementName, "deprecatedElement" => $elementDeprecated] + "The element '{$elementName}' is deprecated.", + ["fileName" => $filename, "deprecatedMessage" => $elementDeprecated] ); } $elements[$elementName] = new ElementObject( diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php index 5da16ec6a..547e6a633 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/DeprecatedEntityUsageCheck.php @@ -37,6 +37,7 @@ class DeprecatedEntityUsageCheck implements StaticCheckInterface { const EXTENDS_REGEX_PATTERN = '/extends=["\']([^\'"]*)/'; const ACTIONGROUP_REGEX_PATTERN = '/ref=["\']([^\'"]*)/'; + const DEPRECATED_REGEX_PATTERN = '/deprecated=["\']([^\'"]*)/'; const ERROR_LOG_FILENAME = 'mftf-deprecated-entity-usage-checks'; const ERROR_LOG_MESSAGE = 'MFTF Deprecated Entity Usage Check'; @@ -177,7 +178,6 @@ private function loadAllXmlFiles($input) MftfApplicationConfig::LEVEL_DEFAULT, true ); - putenv('CUSTOM_MODULE_PATHS=' . realpath($path)); $modulePaths[] = realpath($path); $includeRootPath = false; } else { @@ -225,6 +225,9 @@ private function findReferenceErrorsInActionFiles($files, $checkTestRef = false) /** @var SplFileInfo $filePath */ foreach ($files as $filePath) { $contents = file_get_contents($filePath); + if ($this->isDeprecated($contents)) { + continue; + } 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); @@ -316,6 +319,17 @@ private function findReferenceErrorsInActionFiles($files, $checkTestRef = false) return $testErrors; } + /** + * Checks if entity is deprecated in action files. + * @param string $contents + * @return boolean + */ + private function isDeprecated($contents) + { + preg_match_all(self::DEPRECATED_REGEX_PATTERN, $contents, $deprecatedEntity); + return (!empty($deprecatedEntity[1])); + } + /** * Find reference errors in a set of data files * @@ -335,6 +349,11 @@ private function findReferenceErrorsInDataFiles($files) $entities = $domDocument->getElementsByTagName('entity'); foreach ($entities as $entity) { /** @var DOMElement $entity */ + $deprecated = $entity->getAttribute('deprecated'); + // skip check if entity is deprecated + if (!empty($deprecated)) { + continue; + } $entityName = $entity->getAttribute('name'); $metadataType = $entity->getAttribute('type'); $parentEntityName = $entity->getAttribute('extends'); @@ -609,9 +628,9 @@ private function findViolatingReferences($references) $name = $key; list($section,) = explode('.', $key, 2); /** @var SectionObject $references[$section] */ - $file = $references[$section]->getFilename(); + $file = StaticChecksList::getFilePath($references[$section]->getFilename()); } else { - $file = $entity->getFilename(); + $file = StaticChecksList::getFilePath($entity->getFilename()); } $violatingReferences[$this->getSubjectFromClassType($classType)][] = [ 'name' => $name, @@ -633,16 +652,18 @@ private function setErrorOutput($violatingReferences, $path) { $testErrors = []; + $filePath = StaticChecksList::getFilePath($path->getRealPath()); + if (!empty($violatingReferences)) { // Build error output - $errorOutput = "\nFile \"{$path->getRealPath()}\" contains:\n"; + $errorOutput = "\nFile \"{$filePath}\" contains:\n"; foreach ($violatingReferences as $subject => $data) { $errorOutput .= "\t- {$subject}:\n"; foreach ($data as $item) { $errorOutput .= "\t\t\"" . $item['name'] . "\" in " . $item['file'] . "\n"; } } - $testErrors[$path->getRealPath()][] = $errorOutput; + $testErrors[$filePath][] = $errorOutput; } return $testErrors; diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php new file mode 100644 index 000000000..e7d106bf0 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/PauseActionUsageCheck.php @@ -0,0 +1,229 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\StaticCheck; + +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Finder\Finder; +use Exception; +use Magento\FunctionalTestingFramework\Util\Script\ScriptUtil; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Class PauseActionUsageCheck + * @package Magento\FunctionalTestingFramework\StaticCheck + */ +class PauseActionUsageCheck implements StaticCheckInterface +{ + const ERROR_LOG_FILENAME = 'mftf-pause-action-usage-checks'; + const ERROR_LOG_MESSAGE = 'MFTF Pause Action Usage Check'; + + /** + * Array containing all errors found after running the execute() function. + * @var array + */ + private $errors = []; + + /** + * String representing the output summary found after running the execute() function. + * @var string + */ + private $output; + + /** + * ScriptUtil instance + * + * @var ScriptUtil + */ + private $scriptUtil; + + /** + * Test xml files to scan + * + * @var Finder|array + */ + private $testXmlFiles = []; + + /** + * Action group xml files to scan + * + * @var Finder|array + */ + private $actionGroupXmlFiles = []; + + /** + * Suite xml files to scan + * + * @var Finder|array + */ + private $suiteXmlFiles = []; + + /** + * Root suite xml files to scan + * + * @var Finder|array + */ + private $rootSuiteXmlFiles = []; + + /** + * Checks usage of pause action in action groups, tests and suites and prints out error to file. + * + * @param InputInterface $input + * @return void + * @throws Exception + */ + public function execute(InputInterface $input) + { + $this->scriptUtil = new ScriptUtil(); + $modulePaths = []; + $includeRootPath = true; + $path = $input->getOption('path'); + if ($path) { + if (!realpath($path)) { + throw new \InvalidArgumentException('Invalid --path option: ' . $path); + } + $modulePaths[] = realpath($path); + $includeRootPath = false; + } else { + $modulePaths = $this->scriptUtil->getAllModulePaths(); + } + + $this->testXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Test'); + $this->actionGroupXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'ActionGroup'); + $this->suiteXmlFiles = $this->scriptUtil->getModuleXmlFilesByScope($modulePaths, 'Suite'); + if ($includeRootPath) { + $this->rootSuiteXmlFiles = $this->scriptUtil->getRootSuiteXmlFiles(); + } + $this->errors = []; + $this->errors += $this->validatePauseActionUsageInActionGroups($this->actionGroupXmlFiles); + $this->errors += $this->validatePauseActionUsageInTests($this->testXmlFiles); + $this->errors += $this->validatePauseActionUsageInSuites($this->suiteXmlFiles); + $this->errors += $this->validatePauseActionUsageInSuites($this->rootSuiteXmlFiles); + + $this->output = $this->scriptUtil->printErrorsToFile( + $this->errors, + StaticChecksList::getErrorFilesPath() . DIRECTORY_SEPARATOR . self::ERROR_LOG_FILENAME . '.txt', + self::ERROR_LOG_MESSAGE + ); + } + + /** + * Finds usages of pause action in action group files + * @param array $actionGroupXmlFiles + * @return array + */ + private function validatePauseActionUsageInActionGroups($actionGroupXmlFiles) + { + $actionGroupErrors = []; + foreach ($actionGroupXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $actionGroup = $domDocument->getElementsByTagName('actionGroup')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($actionGroup); + $actionGroupErrors = array_merge($actionGroupErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $actionGroupErrors; + } + + /** + * Finds usages of pause action in test files + * @param array $testXmlFiles + * @return array + */ + private function validatePauseActionUsageInTests($testXmlFiles) + { + $testErrors = []; + foreach ($testXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $test = $domDocument->getElementsByTagName('test')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($test); + $testErrors = array_merge($testErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $testErrors; + } + + /** + * Finds usages of pause action in suite files + * @param array $suiteXmlFiles + * @return array + */ + private function validatePauseActionUsageInSuites($suiteXmlFiles) + { + $suiteErrors = []; + foreach ($suiteXmlFiles as $filePath) { + $domDocument = new \DOMDocument(); + $domDocument->load($filePath); + $suite = $domDocument->getElementsByTagName('suite')->item(0); + $violatingStepKeys = $this->findViolatingPauseStepKeys($suite); + $suiteErrors = array_merge($suiteErrors, $this->setErrorOutput($violatingStepKeys, $filePath)); + } + return $suiteErrors; + } + + /** + * Finds violating pause action step keys + * @param \DomNode $entity + * @return array + */ + private function findViolatingPauseStepKeys($entity) + { + $violatingStepKeys = []; + $entityName = $entity->getAttribute('name'); + $references = $entity->getElementsByTagName('pause'); + + foreach ($references as $reference) { + $pauseStepKey = $reference->getAttribute('stepKey'); + $violatingStepKeys[$entityName][] = $pauseStepKey; + } + return $violatingStepKeys; + } + + /** + * Return array containing all errors found after running the execute() function. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * Return string of a short human readable result of the check. For example: "No errors found." + * @return string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Build and return error output for pause action usages + * + * @param array $violatingReferences + * @param SplFileInfo $path + * @return mixed + */ + private function setErrorOutput($violatingReferences, $path) + { + $testErrors = []; + + $filePath = StaticChecksList::getFilePath($path->getRealPath()); + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$filePath}\""; + $errorOutput .= "\ncontains pause action(s):\n\t\t"; + foreach ($violatingReferences as $entityName => $stepKey) { + $errorOutput .= "\n\t {$entityName} has pause action at stepKey(s): " . implode(", ", $stepKey); + } + $testErrors[$filePath][] = $errorOutput; + } + return $testErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php index 7cd894e00..07c52ce8c 100644 --- a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticChecksList.php @@ -17,6 +17,7 @@ class StaticChecksList implements StaticCheckListInterface { const DEPRECATED_ENTITY_USAGE_CHECK_NAME = 'deprecatedEntityUsage'; + const PAUSE_ACTION_USAGE_CHECK_NAME = 'pauseActionUsage'; const STATIC_RESULTS = 'tests' . DIRECTORY_SEPARATOR .'_output' . DIRECTORY_SEPARATOR . 'static-results'; /** @@ -45,7 +46,8 @@ public function __construct(array $checks = []) 'testDependencies' => new TestDependencyCheck(), 'actionGroupArguments' => new ActionGroupArgumentsCheck(), self::DEPRECATED_ENTITY_USAGE_CHECK_NAME => new DeprecatedEntityUsageCheck(), - 'annotations' => new AnnotationsCheck() + 'annotations' => new AnnotationsCheck(), + self::PAUSE_ACTION_USAGE_CHECK_NAME => new PauseActionUsageCheck() ] + $checks; // Static checks error files directory @@ -69,4 +71,22 @@ public static function getErrorFilesPath() { return self::$errorFilesPath; } + + /** + * Return relative path to files for unit testing purposes. + * @param string $fileNames + * @return string + */ + public static function getFilePath($fileNames) + { + if (!empty($fileNames)) { + $relativeFileNames = ltrim( + str_replace(MAGENTO_BP, '', $fileNames) + ); + if (!empty($relativeFileNames)) { + return $relativeFileNames; + } + } + return $fileNames; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php index 67930f09a..f90a25322 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Suite\Generators; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -32,6 +33,10 @@ class GroupClassGenerator 'comment' => 'print' ]; const GROUP_DIR_NAME = 'Group'; + const FUNCTION_PLACEHOLDER = 'PLACEHOLDER'; + const FUNCTION_START = 'this->getModuleForAction("'; + const FUNCTION_END = '")'; + const FUNCTION_REPLACE_REGEX = '/(PLACEHOLDER->([^\(]+))\(/'; /** * Mustache_Engine instance for template loading @@ -142,7 +147,7 @@ private function buildHookMustacheArray($hookObj) //deleteData contains either url or createDataKey, if it contains the former it needs special formatting if ($action->getType() !== "createData" && !array_key_exists(TestGenerator::REQUIRED_ENTITY_REFERENCE, $action->getCustomActionAttributes())) { - $actions = $this->buildWebDriverActionsMustacheArray($action, $actions); + $actions = $this->buildModuleActionsMustacheArray($action, $actions); continue; } @@ -165,7 +170,7 @@ private function buildHookMustacheArray($hookObj) } /** - * Takes an action object and array of generated action steps. Converst the action object into generated php and + * Takes an action object and array of generated action steps. Convert the action object into generated php and * appends the entry to the given array. The result is returned by the function. * * @param ActionObject $action @@ -173,14 +178,22 @@ private function buildHookMustacheArray($hookObj) * @return array * @throws TestReferenceException */ - private function buildWebDriverActionsMustacheArray($action, $actionEntries) + private function buildModuleActionsMustacheArray($action, $actionEntries) { - $step = TestGenerator::getInstance()->generateStepsPhp([$action], TestGenerator::SUITE_SCOPE, 'webDriver'); + $step = TestGenerator::getInstance()->generateStepsPhp( + [$action], + TestGenerator::SUITE_SCOPE, + self::FUNCTION_PLACEHOLDER + ); $rawPhp = str_replace(["\t"], "", $step); $multipleCommands = explode(PHP_EOL, $rawPhp, -1); $multipleCommands = array_filter($multipleCommands); foreach ($multipleCommands as $command) { - $actionEntries = $this->replaceReservedTesterFunctions($command . PHP_EOL, $actionEntries, 'webDriver'); + $actionEntries = $this->replaceReservedTesterFunctions( + $command . PHP_EOL, + $actionEntries, + self::FUNCTION_PLACEHOLDER + ); } return $actionEntries; @@ -204,7 +217,17 @@ private function replaceReservedTesterFunctions($formattedStep, $actionEntries, $resultingStep = str_replace($testActionCall, $replacement, $formattedStep); $actionEntries[] = ['action' => $resultingStep]; } else { - $actionEntries[] = ['action' => $formattedStep]; + $placeholder = self::FUNCTION_PLACEHOLDER; + $begin = self::FUNCTION_START; + $end = self::FUNCTION_END; + $resultingStep = preg_replace_callback( + self::FUNCTION_REPLACE_REGEX, + function ($matches) use ($placeholder, $begin, $end) { + return str_replace($placeholder, $begin . $matches[2] . $end, $matches[1]) . '('; + }, + $formattedStep + ); + $actionEntries[] = ['action' => $resultingStep]; } } @@ -225,16 +248,16 @@ private function buildPersistenceMustacheArray($action, $entityArray) $action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE]; // append entries for any required entities to this entry - if (array_key_exists('requiredEntities', $action->getCustomActionAttributes())) { - $entityArray[self::REQUIRED_ENTITY_KEY] = - $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); + $requiredEntities = $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); + if (!array_key_exists(-1, $requiredEntities)) { + $entityArray[self::REQUIRED_ENTITY_KEY] = $requiredEntities; } // append entries for customFields if specified by the user. if (array_key_exists('customFields', $action->getCustomActionAttributes())) { $entityArray['customFields'] = $action->getStepKey() . 'Fields'; } - + return $entityArray; } diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index a9fdcd6df..dc2fde915 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -6,6 +6,11 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore; use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; +use Magento\FunctionalTestingFramework\Module\MagentoAssert; +use Magento\FunctionalTestingFramework\Module\MagentoActionProxies; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Codeception\Lib\ModuleContainer; +use Codeception\Module; /** * Group class is Codeception Extension which is allowed to handle to all internal events. @@ -30,10 +35,16 @@ class {{suiteName}} extends \Codeception\GroupObject {{/helpers}} private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of {{suiteName}} suite %s block ********/\n"; private static $HOOK_EXECUTION_END = "\n/******** Execution of {{suiteName}} suite %s block complete ********/\n"; + /** @var MagentoWebDriver */ + private $webDriver; + /** @var ModuleContainer */ + private $moduleContainer; {{#before}} public function _before(\Codeception\Event\TestEvent $e) { + $this->webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + $this->moduleContainer = $this->webDriver->getModuleContainer(); {{#helpers}} /** @var \Magento\FunctionalTestingFramework\Helper\HelperContainer $helperContainer */ $this->helperContainer = $this->getModule('\Magento\FunctionalTestingFramework\Helper\HelperContainer'); @@ -51,15 +62,11 @@ class {{suiteName}} extends \Codeception\GroupObject } } - private function executePreConditions() { if ($this->currentTestRun == 1) { print sprintf(self::$HOOK_EXECUTION_INIT, "before"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { {{> testActions}} } catch (\Exception $exception) { @@ -67,9 +74,9 @@ class {{suiteName}} extends \Codeception\GroupObject } // reset configuration and close session - $webDriver->_resetConfig(); - $webDriver->webDriver->close(); - $webDriver->webDriver = null; + $this->webDriver->_resetConfig(); + $this->webDriver->webDriver->close(); + $this->webDriver->webDriver = null; print sprintf(self::$HOOK_EXECUTION_END, "before"); } @@ -82,15 +89,11 @@ class {{suiteName}} extends \Codeception\GroupObject $this->executePostConditions($e); } - private function executePostConditions(\Codeception\Event\TestEvent $e) { if ($this->currentTestRun == $this->testCount) { print sprintf(self::$HOOK_EXECUTION_INIT, "after"); - /** @var MagentoWebDriver $webDriver */ - $webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); - try { // Find out if Test in Suite failed, will cause potential failures in suite after $cest = $e->getTest(); @@ -120,7 +123,7 @@ class {{suiteName}} extends \Codeception\GroupObject PersistedObjectHandler::getInstance()->clearSuiteObjects(); - $this->closeSession($webDriver); + $this->closeSession($this->webDriver); print sprintf(self::$HOOK_EXECUTION_END, "after"); } @@ -131,13 +134,12 @@ class {{suiteName}} extends \Codeception\GroupObject * Close session method closes current session. * If config 'close_all_sessions' is set to 'true' all sessions will be closed. * - * @param MagentoWebDriver $webDriver * return void */ - private function closeSession(MagentoWebDriver $webDriver): void + private function closeSession(): void { - $webDriverConfig = $webDriver->_getConfig(); - $webDriver->_closeSession(); + $webDriverConfig = $this->webDriver->_getConfig(); + $this->webDriver->_closeSession(); if (isset($webDriverConfig['close_all_sessions']) && $webDriverConfig['close_all_sessions'] === "true") { $wdHost = sprintf( '%s://%s:%s%s', @@ -153,4 +155,20 @@ class {{suiteName}} extends \Codeception\GroupObject } } } + + /** + * Return the module for an action. + * + * @param string $action + * @return Module + * @throws \Exception + */ + private function getModuleForAction($action) + { + $module = $this->moduleContainer->moduleForAction($action); + if ($module === null) { + throw new TestFrameworkException('Invalid action "' . $action . '"' . PHP_EOL); + } + return $module; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache index 73079f169..c07958778 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache @@ -1,9 +1,9 @@ {{#actions}} {{#webDriverInit}} -if ($webDriver->webDriver != null) { - $webDriver->_restart(); +if ($this->webDriver->webDriver != null) { + $this->webDriver->_restart(); } else { - $webDriver->_initializeSession(); + $this->webDriver->_initializeSession(); } {{/webDriverInit}} {{#action}} @@ -13,9 +13,9 @@ if ($webDriver->webDriver != null) { PersistedObjectHandler::getInstance()->createEntity( "{{stepKey}}", "suite", - "{{entityName}}"{{#requiredEntities}}, - [$this->{{entityName}}{{^last}}, {{/last}}]{{/requiredEntities}}{{#customFields}}, - ${{customFields}}{{/customFields}} + "{{entityName}}", + [{{#requiredEntities}}"{{entityName}}"{{^last}}, {{/last}}{{/requiredEntities}}]{{#customFields}}, + ${{customFields}}{{/customFields}} ); {{/createData}} {{#deleteData}} diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 648471c7a..8e4aa973c 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -77,6 +77,7 @@ class ActionObject const ACTION_TYPE_COMMENT = 'comment'; const ACTION_TYPE_HELPER = 'helper'; const INVISIBLE_STEP_ACTIONS = ['retrieveEntityField', 'getSecret']; + const PAUSE_ACTION_INTERNAL_ATTRIBUTE = 'pauseOnFail'; /** * The unique identifier for the action @@ -726,7 +727,12 @@ private function resolveParameterization($isParameterized, $replacement, $match, $resolvedReplacement = $replacement; } if (get_class($object) == PageObject::class && $object->getArea() == PageObject::ADMIN_AREA) { - $resolvedReplacement = "/{{_ENV.MAGENTO_BACKEND_NAME}}/" . $resolvedReplacement; + $urlSegments = [ + '{{_ENV.MAGENTO_BACKEND_BASE_URL}}', + '{{_ENV.MAGENTO_BACKEND_NAME}}', + $resolvedReplacement + ]; + $resolvedReplacement = implode('/', $urlSegments); } return $resolvedReplacement; } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index 618cddc09..84b960f1b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -65,8 +65,8 @@ public function extractActionGroup($actionGroupData) if (array_key_exists(self::OBJ_DEPRECATED, $actionGroupData)) { $deprecated = $actionGroupData[self::OBJ_DEPRECATED]; LoggingUtil::getInstance()->getLogger(ActionGroupObject::class)->deprecation( - $deprecated, - ["actionGroupName" => $actionGroupData[self::FILENAME], "deprecatedActionGroup" => $deprecated] + "The action group '{$actionGroupData[self::NAME]}' is deprecated.", + ["fileName" => $actionGroupData[self::FILENAME], "deprecatedMessage" => $deprecated] ); } $actionGroupReference = $actionGroupData[self::EXTENDS_ACTION_GROUP] ?? null; diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php index c11a6e512..e9c163d31 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestHookObjectExtractor.php @@ -57,19 +57,26 @@ public function extractHook($parentName, $hookType, $testHook) /** * Creates the default failed hook object with a single saveScreenshot action. + * And a pause action when ENABLE_PAUSE is set to true. * * @param string $parentName * @return TestHookObject */ public function createDefaultFailedHook($parentName) { - - $saveScreenshotStep = [new ActionObject("saveScreenshot", "saveScreenshot", [])]; + $defaultSteps['saveScreenshot'] = new ActionObject("saveScreenshot", "saveScreenshot", []); + if (getenv('ENABLE_PAUSE') === 'true') { + $defaultSteps['pauseWhenFailed'] = new ActionObject( + 'pauseWhenFailed', + 'pause', + [ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE => true] + ); + } $hook = new TestHookObject( TestObjectExtractor::TEST_FAILED_HOOK, $parentName, - $saveScreenshotStep + $defaultSteps ); return $hook; diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 2aad30c75..b1d6e1b02 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -29,6 +29,12 @@ </xs:choice> </xs:group> + <xs:group name="returnTags"> + <xs:choice> + <xs:element type="returnType" name="return" minOccurs="0" maxOccurs="1"/> + </xs:choice> + </xs:group> + <!-- Complex Types --> <xs:complexType name="helperType"> @@ -346,6 +352,26 @@ </xs:simpleContent> </xs:complexType> + <xs:complexType name="returnType"> + <xs:annotation> + <xs:documentation> + Used in an action group to return a value. Must be used only once in action group. Do not use in tests or suites. + </xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="value" use="required" type="xs:string"> + <xs:annotation> + <xs:documentation> + Value or variable to be returned. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attributeGroup ref="commonActionAttributes"/> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:simpleType name="sortEnum" final="restriction"> <xs:annotation> <xs:documentation> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd index def2964af..d68cf43db 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd @@ -68,6 +68,12 @@ </xs:choice> </xs:group> + <xs:group name="returnTypeTags"> + <xs:choice> + <xs:group ref="returnTags"/> + </xs:choice> + </xs:group> + <!-- Complex Types --> <xs:complexType name="failType"> diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd index bd32ba879..f45c33acd 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/mergedActionGroupSchema.xsd @@ -15,29 +15,21 @@ </xs:choice> </xs:complexType> <xs:complexType name="actionsRefType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:group ref="actionTypeTags"/> - <xs:element name="arguments"> - <xs:complexType> - <xs:sequence> - <xs:element name="argument" maxOccurs="unbounded" minOccurs="0"> - <xs:complexType> - <xs:attribute type="xs:string" name="name" use="required"/> - <xs:attribute type="xs:string" name="defaultValue"/> - <xs:attribute type="dataTypeEnum" name="type" default="entity"/> - </xs:complexType> - </xs:element> - </xs:sequence> - </xs:complexType> - </xs:element> - <xs:element name="annotations"> - <xs:complexType> - <xs:sequence> - <xs:element name="description"/> - </xs:sequence> - </xs:complexType> - </xs:element> - </xs:choice> + <xs:sequence> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:group ref="actionTypeTags"/> + <xs:element ref="arguments"/> + <xs:element ref="annotations"/> + </xs:choice> + <xs:sequence minOccurs="0"> + <xs:group ref="returnTags"/> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:group ref="actionTypeTags"/> + <xs:element ref="arguments"/> + <xs:element ref="annotations"/> + </xs:choice> + </xs:sequence> + </xs:sequence> <xs:attribute type="xs:string" name="name" use="required"/> <xs:attribute type="xs:string" name="filename"/> <xs:attribute type="xs:string" name="insertBefore"/> @@ -57,4 +49,28 @@ <xs:enumeration value="entity"/> </xs:restriction> </xs:simpleType> + + <!-- elements --> + + <xs:element name="arguments"> + <xs:complexType> + <xs:sequence> + <xs:element name="argument" maxOccurs="unbounded" minOccurs="0"> + <xs:complexType> + <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:string" name="defaultValue"/> + <xs:attribute type="dataTypeEnum" name="type" default="entity"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="annotations"> + <xs:complexType> + <xs:sequence> + <xs:element name="description"/> + </xs:sequence> + </xs:complexType> + </xs:element> </xs:schema> diff --git a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php index 5955235ce..880dd9736 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php +++ b/src/Magento/FunctionalTestingFramework/Util/Logger/MftfLogger.php @@ -46,12 +46,14 @@ public function __construct($name, array $handlers = [], array $processors = []) public function deprecation($message, array $context = [], $verbose = false) { $message = "DEPRECATION: " . $message; - // print during test generation - if ($this->phase === MftfApplicationConfig::GENERATION_PHASE && $verbose) { + // print during test generation including metadata + if ((array_key_exists('operationType', $context) || + $this->phase === MftfApplicationConfig::GENERATION_PHASE) && $verbose) { print ($message . json_encode($context) . "\n"); } - // suppress logging during test execution - if ($this->phase !== MftfApplicationConfig::EXECUTION_PHASE) { + // suppress logging during test execution except metadata + if (array_key_exists('operationType', $context) || + $this->phase !== MftfApplicationConfig::EXECUTION_PHASE) { parent::warning($message, $context); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php b/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php index d59df5c11..cb0f4f5ae 100644 --- a/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php +++ b/src/Magento/FunctionalTestingFramework/Util/MftfGlobals.php @@ -76,12 +76,15 @@ public static function getBackendBaseUrl($withTrailingSeparator = true) { if (!self::$backendBaseUrl) { try { + $backendName = getenv('MAGENTO_BACKEND_NAME'); $bUrl = getenv('MAGENTO_BACKEND_BASE_URL'); - if ($bUrl) { - self::$backendBaseUrl = UrlFormatter::format($bUrl, false); + if ($bUrl && $backendName) { + self::$backendBaseUrl = UrlFormatter::format( + UrlFormatter::format($bUrl) . $backendName, + false + ); } else { $baseUrl = getenv('MAGENTO_BASE_URL'); - $backendName = getenv('MAGENTO_BACKEND_NAME'); if ($baseUrl && $backendName) { self::$backendBaseUrl = UrlFormatter::format( UrlFormatter::format($baseUrl) . $backendName, diff --git a/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php b/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php index 14f9c27e0..11afa5cc1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Script/ScriptUtil.php @@ -101,7 +101,7 @@ public function getModuleXmlFilesByScope($modulePaths, $scope) if (!realpath($modulePath . $scopePath)) { continue; } - $finder->files()->followLinks()->in($modulePath . $scopePath)->name("*.xml"); + $finder->files()->followLinks()->in($modulePath . $scopePath)->name("*.xml")->sortByName(); $found = true; } return $found ? $finder->files() : []; diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 8253a75ba..8caffc565 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1172,6 +1172,16 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $selector ); break; + case "return": + $actionOrigin = $actionObject->getActionOrigin(); + $actionOriginStepKey = $actionOrigin[ActionGroupObject::ACTION_GROUP_ORIGIN_TEST_REF]; + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $actionOriginStepKey, + $actor, + $actionObject, + $value + ); + break; case "formatCurrency": $testSteps .= $this->wrapFunctionCallWithReturnValue( $stepKey, @@ -1431,6 +1441,16 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $testSteps .= $dateGenerateCode; break; + case "pause": + $pauseAttr = $actionObject->getCustomActionAttributes( + ActionObject::PAUSE_ACTION_INTERNAL_ATTRIBUTE + ); + if ($pauseAttr) { + $testSteps .= sprintf("\t\t$%s->%s(%s);", $actor, $actionObject->getType(), 'true'); + } else { + $testSteps .= sprintf("\t\t$%s->%s();", $actor, $actionObject->getType()); + } + break; case "comment": $input = $input === null ? strtr($value, ['$' => '\$', '{' => '\{', '}' => '\}']) : $input; // Combining userInput from native XML comment and <comment/> action to fall-through 'default' case @@ -2020,18 +2040,20 @@ private function resolveRuntimeReference($args, $regex, $func) $newArgs = []; foreach ($args as $key => $arg) { + $newArgs[$key] = $arg; preg_match_all($regex, $arg, $matches); if (!empty($matches[0])) { - $fullMatch = $matches[0][0]; - $refVariable = $matches[1][0]; - unset($matches); - $replacement = "{$func}(\"{$refVariable}\")"; + foreach ($matches[0] as $matchKey => $fullMatch) { + $refVariable = $matches[1][$matchKey]; + + $replacement = $this->getReplacement($func, $refVariable); - $outputArg = $this->processQuoteBreaks($fullMatch, $arg, $replacement); - $newArgs[$key] = $outputArg; + $outputArg = $this->processQuoteBreaks($fullMatch, $newArgs[$key], $replacement); + $newArgs[$key] = $outputArg; + } + unset($matches); continue; } - $newArgs[$key] = $arg; } // override passed in args for use later. @@ -2303,4 +2325,20 @@ private function parseUserInput($userInput) return $this->addUniquenessFunctionCall($userInput); } + + /** + * Supports fallback for BACKEND URL + * + * @param string $func + * @param string $refVariable + * @return string + */ + private function getReplacement($func, $refVariable): string + { + if ($refVariable === 'MAGENTO_BACKEND_BASE_URL') { + return "({$func}(\"{$refVariable}\") ? rtrim({$func}(\"{$refVariable}\"), \"/\") : \"\")"; + } + + return "{$func}(\"{$refVariable}\")"; + } }