From 237913d585a5016fbfd9517021ffbd4409f7d368 Mon Sep 17 00:00:00 2001 From: Tom Erskine Date: Wed, 14 Mar 2018 10:04:02 -0500 Subject: [PATCH 01/77] MQE-801: Allow Test Generation of a single test via arg Updated TestGenerator.php to check emptiness of array --- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index affbbd36d..c7c478a90 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -97,7 +97,7 @@ public function getExportDir() */ private function loadAllTestObjects() { - if ($this->tests === null) { + if ($this->tests === null|| empty($this->tests)) { return TestObjectHandler::getInstance()->getAllObjects(); } From 2a0ec6cafb18d6ecf2dc37337ed235f6406db16d Mon Sep 17 00:00:00 2001 From: Tom Reece Date: Tue, 20 Mar 2018 16:18:45 -0500 Subject: [PATCH 02/77] MQE-801: Tweak to fix static check failure --- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index c7c478a90..2ac2d0abd 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -97,7 +97,7 @@ public function getExportDir() */ private function loadAllTestObjects() { - if ($this->tests === null|| empty($this->tests)) { + if ($this->tests === null || empty($this->tests)) { return TestObjectHandler::getInstance()->getAllObjects(); } From dd3dd48328d2955964cfb09489ba2f9093d3fca8 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk Date: Wed, 21 Mar 2018 14:49:02 +0200 Subject: [PATCH 03/77] MQE-880: Url parameter no longer resolved correctly if only page name is used MQE-881: Cannot Pass Arguments In Action Groups To X, Y Attributes On Actions MQE-882: Cannot input tags in entity xml --- .../Test/Objects/ActionObject.php | 8 ++++---- .../FunctionalTestingFramework/Util/TestGenerator.php | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 24971cef5..3d5a98654 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -20,7 +20,7 @@ class ActionObject { const __ENV = "_ENV"; - const DATA_ENABLED_ATTRIBUTES = ["userInput", "parameterArray", "expected", "actual"]; + const DATA_ENABLED_ATTRIBUTES = ["userInput", "parameterArray", "expected", "actual", "x", "y"]; const SELECTOR_ENABLED_ATTRIBUTES = [ 'selector', 'dependentSelector', @@ -40,7 +40,7 @@ class ActionObject const ACTION_ATTRIBUTE_URL = 'url'; const ACTION_ATTRIBUTE_SELECTOR = 'selector'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; - const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/{{[\w]+\.[\w\[\]]+}}/'; + const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/{{[\w]+\.?[\w\[\]]+}}/'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN_WITH_PARAMS= '/{{[\w]+\.[\w]+\(.+\)}}/'; /** @@ -371,7 +371,7 @@ private function resolveDataInputReferences() foreach ($relevantDataAttributes as $dataAttribute) { $varInput = $this->actionAttributes[$dataAttribute]; $replacement = $this->findAndReplaceReferences(DataObjectHandler::getInstance(), $varInput); - if ($replacement != null) { + if ($replacement !== null) { $this->resolvedCustomAttributes[$dataAttribute] = $replacement; } } @@ -482,7 +482,7 @@ private function findAndReplaceReferences($objectHandler, $inputString) $replacement = $this->resolveEntityDataObjectReference($obj, $match); } - if ($replacement == null) { + if ($replacement === null) { if (get_class($objectHandler) != DataObjectHandler::class) { return $this->findAndReplaceReferences(DataObjectHandler::getInstance(), $outputString); } else { diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 2ac2d0abd..8d8498276 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1588,6 +1588,9 @@ private function wrapFunctionCall($actor, $action, ...$args) if (!$isFirst) { $output .= ', '; } + if ($args[$i] === "") { + $args[$i] = '"' . $args[$i] . '"'; + } $output .= $args[$i]; $isFirst = false; } @@ -1619,6 +1622,9 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio if (!$isFirst) { $output .= ', '; } + if ($args[$i] === "") { + $args[$i] = '"' . $args[$i] . '"'; + } $output .= $args[$i]; $isFirst = false; } From 5003492b72b5ee0c5b88024e9b72d1aaea4d0087 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk Date: Wed, 21 Mar 2018 15:58:27 +0200 Subject: [PATCH 04/77] MQE-679: 2 or more params in a url are not properly replaced MQE-877: Passing Multiple Element References In Selector Parameters Does Not Generate Correctly When Element(s) Are Parameterized --- .../DataGenerator/Persist/CurlHandler.php | 2 +- .../Test/Objects/ActionObject.php | 19 ++----------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index 15c3677d7..c72cafc25 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -206,7 +206,7 @@ private function resolveUrlReference($urlIn, $entityObjects) EntityDataObject::CEST_UNIQUE_VALUE ); if (null !== $param) { - $urlOut = str_replace($paramValue, $param, $urlIn); + $urlOut = str_replace($paramValue, $param, $urlOut); continue; } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 3d5a98654..7e5ea17e0 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -40,8 +40,7 @@ class ActionObject const ACTION_ATTRIBUTE_URL = 'url'; const ACTION_ATTRIBUTE_SELECTOR = 'selector'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; - const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/{{[\w]+\.?[\w\[\]]+}}/'; - const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN_WITH_PARAMS= '/{{[\w]+\.[\w]+\(.+\)}}/'; + const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.?[\w\[\]]+}})|({{[\w]+\.[\w]+\(.+\)}})/'; /** * The unique identifier for the action @@ -444,7 +443,7 @@ private function stripAndReturnParameters($reference) private function findAndReplaceReferences($objectHandler, $inputString) { //look for parameter area, if so use different regex - $regex = $this->resolveRegexPatternForReference($inputString); + $regex = ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN; preg_match_all($regex, $inputString, $matches); @@ -514,20 +513,6 @@ private function validateUrlAreaAgainstActionType($obj) } } - /** - * Determines whether the given $inputString has (params), and returns the appropriate regex for use in matching. - * @param string $inputString - * @return string - */ - private function resolveRegexPatternForReference($inputString) - { - if (preg_match(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN_WITH_PARAMS, $inputString) === 1) { - return ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN_WITH_PARAMS; - } else { - return ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN; - } - } - /** * Gets the object's dataByName with given $match, differentiating behavior between and nodes. * @param string $obj From 7a8925503ed8f25bf86836e1659727b60b9f8acf Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk Date: Thu, 22 Mar 2018 19:49:51 +0200 Subject: [PATCH 05/77] Make possible to create and run MFTF test in framework to verify Framework basic functionality --- .env.example | 37 + .gitignore | 3 + RoboFile.php | 190 + codeception.dist.yml | 27 + composer.json | 9 +- composer.lock | 5384 +++++++++++++++++ dev/tests/functional/MFTF.suite.dist.yml | 35 + .../DevDocs/Page/MFTFDocPage.xml | 14 + .../DevDocs/Section/ContentSection.xml | 14 + .../DevDocs/Test/DevDocsTest.xml | 24 + dev/tests/functional/MFTF/_bootstrap.php | 7 + dev/tests/functional/_bootstrap.php | 35 + .../Util/ModuleResolver.php | 4 + 13 files changed, 5781 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 RoboFile.php create mode 100755 codeception.dist.yml create mode 100644 composer.lock create mode 100644 dev/tests/functional/MFTF.suite.dist.yml create mode 100644 dev/tests/functional/MFTF/FunctionalTest/DevDocs/Page/MFTFDocPage.xml create mode 100644 dev/tests/functional/MFTF/FunctionalTest/DevDocs/Section/ContentSection.xml create mode 100644 dev/tests/functional/MFTF/FunctionalTest/DevDocs/Test/DevDocsTest.xml create mode 100755 dev/tests/functional/MFTF/_bootstrap.php create mode 100755 dev/tests/functional/_bootstrap.php diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..432851acb --- /dev/null +++ b/.env.example @@ -0,0 +1,37 @@ +#Copyright © Magento, Inc. All rights reserved. +#See COPYING.txt for license details. + +#*** Set the base URL for your Magento instance ***# +MAGENTO_BASE_URL= + +#*** Set the Admin Username and Password for your Magento instance ***# +MAGENTO_BACKEND_NAME= +MAGENTO_ADMIN_USERNAME= +MAGENTO_ADMIN_PASSWORD= + +#*** Path to CLI entry point and command parameter name. Uncomment and change if folder structure differs from standard Magento installation +#MAGENTO_CLI_COMMAND_PATH=dev/tests/acceptance/utils/command.php +#MAGENTO_CLI_COMMAND_PARAMETER=command + +#*** Selenium Server Protocol, Host, Port, and Path, with local defaults. Uncomment and change if not running Selenium locally. +#SELENIUM_HOST=127.0.0.1 +#SELENIUM_PORT=4444 +#SELENIUM_PROTOCOL=http +#SELENIUM_PATH=/wd/hub + +#*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***# +#MAGENTO_RESTAPI_SERVER_HOST= +#MAGENTO_RESTAPI_SERVER_PORT= + +#*** Uncomment these properties to set up a dev environment with symlinked projects ***# +#TESTS_BP= +#FW_BP= +#TESTS_MODULE_PATH= + +#*** These properties impact the modules loaded into MFTF, you can point to your own full path, or a custom set of modules located with the core set +MODULE_WHITELIST=Magento_Framework,Magento_ConfigurableProductWishlist,Magento_ConfigurableProductCatalogSearch +#CUSTOM_MODULE_PATHS= + +#*** Bool property which allows the user to toggle debug output during test execution +#MFTF_DEBUG= +#*** End of .env ***# diff --git a/.gitignore b/.gitignore index c3c8d97b0..da2119a16 100755 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ build/* clover.xml coverage/ .vscode +codeception.yml +dev/tests/functional/MFTF.suite.yml +dev/tests/functional/_output \ No newline at end of file diff --git a/RoboFile.php b/RoboFile.php new file mode 100644 index 000000000..efb08c8fa --- /dev/null +++ b/RoboFile.php @@ -0,0 +1,190 @@ +_exec('cp -vn .env.example .env'); + $this->_exec('cp -vf codeception.dist.yml codeception.yml'); + $this->_exec('cp -vf dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'functional.suite.dist.yml dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'functional.suite.yml'); + } + + /** + * Duplicate the Example configuration files for the Project. + * Build the Codeception project. + * + * @return void + */ + function buildProject() + { + $this->cloneFiles(); + $this->_exec('vendor'. DIRECTORY_SEPARATOR .'bin'. DIRECTORY_SEPARATOR .'codecept build'); + } + + /** + * Generate all Tests in PHP. + * + * @param array $opts + * @return void + */ + function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => null]) + { + $GLOBALS['GENERATE_TESTS'] = true; + + if ($opts['force']) + { + $GLOBALS['FORCE_PHP_GENERATE'] = true; + } + + require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; + \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance()->createAllTestFiles($opts['config'], $opts['nodes']); + $this->say("Generate Tests Command Run"); + } + + /** + * Generate a suite based on name(s) passed in as args. + * + * @param array $args + * @throws Exception + * @return void + */ + function generateSuite(array $args) + { + if (empty($args)) { + throw new Exception("Please provide suite name(s) after generate:suite command"); + } + + require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; + $sg = \Magento\FunctionalTestingFramework\Suite\SuiteGenerator::getInstance(); + + foreach ($args as $arg) { + $sg->generateSuite($arg); + } + } + + /** + * Run all Functional tests. + * + * @return void + */ + function functional() + { + $this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run functional --skip-group skip'); + } + + /** + * Run all Tests with the specified @group tag, excluding @group 'skip'. + * + * @param string $args + * @return void + */ + function group($args = '') + { + $this->taskExec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run --verbose --steps --skip-group skip --group')->args($args)->run(); + } + + /** + * Run all Functional tests located under the Directory Path provided. + * + * @param string $args + * @return void + */ + function folder($args = '') + { + $this->taskExec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run ')->args($args)->run(); + } + + /** + * Run all Tests marked with the @group tag 'example'. + * + * @return void + */ + function example() + { + $this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run --group example --skip-group skip'); + } + + /** + * Generate the HTML for the Allure report based on the Test XML output - Allure v1.4.X + * + * @return \Robo\Result + */ + function allure1Generate() + { + return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' -o tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); + } + + /** + * Generate the HTML for the Allure report based on the Test XML output - Allure v2.3.X + * + * @return \Robo\Result + */ + function allure2Generate() + { + return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' --output tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .' --clean'); + } + + /** + * Open the HTML Allure report - Allure v1.4.X + * + * @return void + */ + function allure1Open() + { + $this->_exec('allure report open --report-dir tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); + } + + /** + * Open the HTML Allure report - Allure v2.3.X + * + * @return void + */ + function allure2Open() + { + $this->_exec('allure open --port 0 tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .''); + } + + /** + * Generate and open the HTML Allure report - Allure v1.4.X + * + * @return void + */ + function allure1Report() + { + $result1 = $this->allure1Generate(); + + if ($result1->wasSuccessful()) { + $this->allure1Open(); + } + } + + /** + * Generate and open the HTML Allure report - Allure v2.3.X + * + * @return void + */ + function allure2Report() + { + $result1 = $this->allure2Generate(); + + if ($result1->wasSuccessful()) { + $this->allure2Open(); + } + } +} diff --git a/codeception.dist.yml b/codeception.dist.yml new file mode 100755 index 000000000..25093c681 --- /dev/null +++ b/codeception.dist.yml @@ -0,0 +1,27 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. +actor: Tester +paths: + tests: dev/tests/functional + log: dev/tests/functional/_output + data: dev/tests/functional/_data + support: src/Magento/FunctionalTestingFramework + envs: etc/_envs +settings: + bootstrap: _bootstrap.php + colors: true + memory_limit: 1024M +extensions: + enabled: + - Codeception\Extension\RunFailed + - Yandex\Allure\Adapter\AllureAdapter + config: + Yandex\Allure\Adapter\AllureAdapter: + deletePreviousResults: true + outputDirectory: allure-results + ignoredAnnotations: + - env + - zephyrId + - useCaseId +params: + - .env \ No newline at end of file diff --git a/composer.json b/composer.json index 4720c4acd..f86d9ea9d 100755 --- a/composer.json +++ b/composer.json @@ -10,11 +10,15 @@ }, "require": { "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0", + "allure-framework/allure-codeception": "~1.2.6", "codeception/codeception": "~2.3.4", + "consolidation/robo": "^1.0.0", "epfremme/swagger-php": "^2.0", "flow/jsonpath": ">0.2", "fzaninotto/faker": "^1.6", - "mustache/mustache": "~2.5" + "mustache/mustache": "~2.5", + "symfony/process": ">=2.7 <3.4", + "vlucas/phpdotenv": "^2.4" }, "require-dev": { "squizlabs/php_codesniffer": "1.5.3", @@ -30,7 +34,8 @@ }, "autoload": { "psr-4": { - "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework" + "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", + "MFTF\\": "dev/tests/functional/MFTF" } }, "autoload-dev": { diff --git a/composer.lock b/composer.lock new file mode 100644 index 000000000..5a43c38e2 --- /dev/null +++ b/composer.lock @@ -0,0 +1,5384 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "88a74b2a547a3fffeef374ec9f3865ea", + "packages": [ + { + "name": "allure-framework/allure-codeception", + "version": "1.2.6", + "source": { + "type": "git", + "url": "https://github.com/allure-framework/allure-codeception.git", + "reference": "e2672e57e6839edb770fa5de5f2b3fcc47917aa1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/e2672e57e6839edb770fa5de5f2b3fcc47917aa1", + "reference": "e2672e57e6839edb770fa5de5f2b3fcc47917aa1", + "shasum": "" + }, + "require": { + "allure-framework/allure-php-api": "~1.1.0", + "codeception/codeception": "~2.1", + "php": ">=5.4.0", + "symfony/filesystem": ">=2.6", + "symfony/finder": ">=2.6" + }, + "type": "library", + "autoload": { + "psr-0": { + "Yandex": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" + } + ], + "description": "A Codeception adapter for Allure report.", + "homepage": "http://allure.qatools.ru/", + "keywords": [ + "allure", + "attachments", + "cases", + "codeception", + "report", + "steps", + "testing" + ], + "time": "2017-12-27T11:38:21+00:00" + }, + { + "name": "allure-framework/allure-php-api", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/allure-framework/allure-php-adapter-api.git", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", + "shasum": "" + }, + "require": { + "jms/serializer": ">=0.16.0", + "moontoast/math": ">=1.1.0", + "php": ">=5.4.0", + "phpunit/phpunit": ">=4.0.0", + "ramsey/uuid": ">=3.0.0", + "symfony/http-foundation": ">=2.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Yandex": [ + "src/", + "test/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" + } + ], + "description": "PHP API for Allure adapter", + "homepage": "http://allure.qatools.ru/", + "keywords": [ + "allure", + "api", + "php", + "report" + ], + "time": "2016-12-07T12:15:46+00:00" + }, + { + "name": "behat/gherkin", + "version": "v4.4.5", + "source": { + "type": "git", + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "shasum": "" + }, + "require": { + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.4-dev" + } + }, + "autoload": { + "psr-0": { + "Behat\\Gherkin": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + } + ], + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", + "keywords": [ + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" + ], + "time": "2016-10-30T11:50:56+00:00" + }, + { + "name": "codeception/codeception", + "version": "2.3.9", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Codeception.git", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "shasum": "" + }, + "require": { + "behat/gherkin": "~4.4.0", + "codeception/stub": "^1.0", + "ext-json": "*", + "ext-mbstring": "*", + "facebook/webdriver": ">=1.1.3 <2.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/psr7": "~1.0", + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": ">=1.4 <3.0", + "symfony/browser-kit": ">=2.7 <5.0", + "symfony/console": ">=2.7 <5.0", + "symfony/css-selector": ">=2.7 <5.0", + "symfony/dom-crawler": ">=2.7 <5.0", + "symfony/event-dispatcher": ">=2.7 <5.0", + "symfony/finder": ">=2.7 <5.0", + "symfony/yaml": ">=2.7 <5.0" + }, + "require-dev": { + "codeception/specify": "~0.3", + "facebook/graph-sdk": "~5.3", + "flow/jsonpath": "~0.2", + "monolog/monolog": "~1.8", + "pda/pheanstalk": "~3.0", + "php-amqplib/php-amqplib": "~2.4", + "predis/predis": "^1.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <5.0", + "vlucas/phpdotenv": "^2.4.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "flow/jsonpath": "For using JSONPath in REST module", + "league/factory-muffin": "For DataFactory module", + "league/factory-muffin-faker": "For Faker support in DataFactory module", + "phpseclib/phpseclib": "for SFTP option in FTP Module", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" + }, + "bin": [ + "codecept" + ], + "type": "library", + "extra": { + "branch-alias": [] + }, + "autoload": { + "psr-4": { + "Codeception\\": "src\\Codeception", + "Codeception\\Extension\\": "ext" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" + } + ], + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", + "keywords": [ + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" + ], + "time": "2018-02-26T23:29:41+00:00" + }, + { + "name": "codeception/stub", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "shasum": "" + }, + "require": { + "phpunit/phpunit-mock-objects": ">2.3 <7.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4.8 <8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2018-02-18T13:56:56+00:00" + }, + { + "name": "consolidation/annotated-command", + "version": "2.8.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", + "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", + "shasum": "" + }, + "require": { + "consolidation/output-formatters": "^3.1.12", + "php": ">=5.4.0", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8", + "satooshi/php-coveralls": "^1.0.2 | dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "time": "2018-02-23T16:32:04+00:00" + }, + { + "name": "consolidation/config", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/34ca8d7c1ee60a7b591b10617114cf1210a2e92c", + "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "grasmash/expander": "^1", + "php": ">=5.4.0" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "2.*", + "symfony/console": "^2.5|^3|^4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "suggest": { + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "time": "2017-12-22T17:28:19+00:00" + }, + { + "name": "consolidation/log", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/dbc7c535f319a4a2d5a5077738f8eb7c10df8821", + "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/log": "~1.0", + "symfony/console": "^2.8|^3|^4" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "dev-master", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "time": "2017-11-29T01:44:16+00:00" + }, + { + "name": "consolidation/output-formatters", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/da889e4bce19f145ca4ec5b1725a946f4eb625a9", + "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4" + }, + "require-dev": { + "g-1-a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^5.7.27", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7", + "symfony/console": "3.2.3", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" + }, + "suggest": { + "symfony/var-dumper": "For using the var_dump formatter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\OutputFormatters\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2018-03-20T15:18:32+00:00" + }, + { + "name": "consolidation/robo", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/consolidation/Robo.git", + "reference": "9ef2724f72feb017517a755564516dbde99e15e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/9ef2724f72feb017517a755564516dbde99e15e4", + "reference": "9ef2724f72feb017517a755564516dbde99e15e4", + "shasum": "" + }, + "require": { + "consolidation/annotated-command": "^2.8.2", + "consolidation/config": "^1.0.1", + "consolidation/log": "~1", + "consolidation/output-formatters": "^3.1.13", + "grasmash/yaml-expander": "^1.3", + "league/container": "^2.2", + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/filesystem": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4", + "symfony/process": "^2.5|^3|^4" + }, + "replace": { + "codegyre/robo": "< 1.0" + }, + "require-dev": { + "codeception/aspect-mock": "^1|^2.1.1", + "codeception/base": "^2.3.7", + "codeception/verify": "^0.3.2", + "goaop/framework": "~2.1.2", + "greg-1-anderson/composer-test-scenarios": "^1", + "natxet/cssmin": "3.0.4", + "patchwork/jsqueeze": "~2", + "pear/archive_tar": "^1.4.2", + "phpunit/php-code-coverage": "~2|~4", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.8" + }, + "suggest": { + "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", + "natxet/CssMin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." + }, + "bin": [ + "robo" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev", + "dev-state": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Robo\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "Modern task runner", + "time": "2018-02-28T01:03:54+00:00" + }, + { + "name": "container-interop/container-interop", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/container-interop/container-interop.git", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8", + "shasum": "" + }, + "require": { + "psr/container": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Interop\\Container\\": "src/Interop/Container/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", + "homepage": "https://github.com/container-interop/container-interop", + "time": "2017-02-14T19:40:03+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-02-24T16:22:25+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "epfremme/swagger-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/epfremmer/swagger-php.git", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2", + "doctrine/collections": "^1.3", + "jms/serializer": "^1.1", + "php": ">=5.5", + "phpoption/phpoption": "^1.1", + "symfony/yaml": "^2.7|^3.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "~4.8|~5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "package", + "autoload": { + "psr-4": { + "Epfremme\\Swagger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edward Pfremmer", + "email": "epfremme@nerdery.com" + } + ], + "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", + "time": "2016-09-26T17:24:17+00:00" + }, + { + "name": "facebook/webdriver", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "86b5ca2f67173c9d34340845dd690149c886a605" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/86b5ca2f67173c9d34340845dd690149c886a605", + "reference": "86b5ca2f67173c9d34340845dd690149c886a605", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "guzzle/guzzle": "^3.4.1", + "php-coveralls/php-coveralls": "^1.0.2", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^2.6", + "symfony/var-dumper": "^3.3 || ^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2017-11-15T11:08:09+00:00" + }, + { + "name": "flow/jsonpath", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/FlowCommunications/JSONPath.git", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "peekmo/jsonpath": "dev-master", + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Flow\\JSONPath": "src/", + "Flow\\JSONPath\\Test": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "time": "2018-03-04T16:39:47+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.7.1", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2017-08-15T16:48:10+00:00" + }, + { + "name": "grasmash/expander", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "time": "2017-12-21T22:14:55+00:00" + }, + { + "name": "grasmash/yaml-expander", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-expander.git", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlExpander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in a yaml file.", + "time": "2017-12-16T16:06:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0 || ^5.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2017-06-22T18:50:49+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "jms/metadata", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "doctrine/cache": "~1.0", + "symfony/cache": "~3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "time": "2016-12-05T10:18:33+00:00" + }, + { + "name": "jms/parser-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/parser-lib.git", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "shasum": "" + }, + "require": { + "phpoption/phpoption": ">=0.9,<2.0-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "description": "A library for easily creating recursive-descent parsers.", + "time": "2012-11-18T18:08:43+00:00" + }, + { + "name": "jms/serializer", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/serializer.git", + "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/e7c53477ff55c21d1b1db7d062edc050a24f465f", + "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/instantiator": "^1.0.3", + "jms/metadata": "~1.1", + "jms/parser-lib": "1.*", + "php": "^5.5|^7.0", + "phpcollection/phpcollection": "~0.1", + "phpoption/phpoption": "^1.1" + }, + "conflict": { + "twig/twig": "<1.12" + }, + "require-dev": { + "doctrine/orm": "~2.1", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "phpunit/phpunit": "^4.8|^5.0", + "propel/propel1": "~1.7", + "psr/container": "^1.0", + "symfony/dependency-injection": "^2.7|^3.3|^4.0", + "symfony/expression-language": "^2.6|^3.0", + "symfony/filesystem": "^2.1", + "symfony/form": "~2.1|^3.0", + "symfony/translation": "^2.1|^3.0", + "symfony/validator": "^2.2|^3.0", + "symfony/yaml": "^2.1|^3.0", + "twig/twig": "~1.12|~2.0" + }, + "suggest": { + "doctrine/cache": "Required if you like to use cache functionality.", + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", + "symfony/yaml": "Required if you'd like to serialize data to YAML format." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\Serializer": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", + "homepage": "http://jmsyst.com/libs/serializer", + "keywords": [ + "deserialization", + "jaxb", + "json", + "serialization", + "xml" + ], + "time": "2018-02-04T17:48:54+00:00" + }, + { + "name": "league/container", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "time": "2017-05-10T09:20:27+00:00" + }, + { + "name": "moontoast/math", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/moontoast-math.git", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^0.9.0", + "phpunit/phpunit": "^4.7|>=5.0 <5.4", + "satooshi/php-coveralls": "^0.6.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Moontoast\\Math\\": "src/Moontoast/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A mathematics library, providing functionality for large numbers", + "homepage": "https://github.com/ramsey/moontoast-math", + "keywords": [ + "bcmath", + "math" + ], + "time": "2017-02-16T16:54:46+00:00" + }, + { + "name": "mustache/mustache", + "version": "v2.12.0", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mustache": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", + "keywords": [ + "mustache", + "templating" + ], + "time": "2017-07-11T12:54:05+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19T19:58:43+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-09-27T21:40:39+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05T18:14:27+00:00" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05T17:38:23+00:00" + }, + { + "name": "phpcollection/phpcollection", + "version": "0.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-collection.git", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "shasum": "" + }, + "require": { + "phpoption/phpoption": "1.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4-dev" + } + }, + "autoload": { + "psr-0": { + "PhpCollection": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "General-Purpose Collection Library for PHP", + "keywords": [ + "collection", + "list", + "map", + "sequence", + "set" + ], + "time": "2015-05-17T12:39:23+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2017-09-11T18:02:19+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30T07:14:17+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14T14:27:02+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-0": { + "PhpOption\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "time": "2015-07-25T16:39:46+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.7.5", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-02-19T10:16:54+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2017-12-06T09:29:45+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27T05:48:46+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "6bd77b57707c236833d2b57b968e403df060c9d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6bd77b57707c236833d2b57b968e403df060c9d9", + "reference": "6bd77b57707c236833d2b57b968e403df060c9d9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.5", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-02-26T07:01:09+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2018-01-06T05:45:45+00:00" + }, + { + "name": "psr/container", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2017-02-14T16:28:37+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "ramsey/uuid", + "version": "3.7.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.0|^2.0", + "php": "^5.4 || ^7.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2018-01-20T00:28:24+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04T06:30:41+00:00" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01T13:46:46+00:00" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03T08:09:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01T08:51:00+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03T13:19:02+00:00" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27T15:39:26+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03T12:35:26+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29T09:07:27+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03T06:23:57+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28T20:34:47+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "490f27762705c8489bd042fe3e9377a191dba9b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/490f27762705c8489bd042fe3e9377a191dba9b4", + "reference": "490f27762705c8489bd042fe3e9377a191dba9b4", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/dom-crawler": "~2.8|~3.0|~4.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:37:34+00:00" + }, + { + "name": "symfony/console", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "067339e9b8ec30d5f19f5950208893ff026b94f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/067339e9b8ec30d5f19f5950208893ff026b94f7", + "reference": "067339e9b8ec30d5f19f5950208893ff026b94f7", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/debug": "~2.8|~3.0|~4.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.3|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~2.8|~3.0|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.3|~4.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-02-26T15:46:28+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "544655f1fc078a9cd839fdda2b7b1e64627c826a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/544655f1fc078a9cd839fdda2b7b1e64627c826a", + "reference": "544655f1fc078a9cd839fdda2b7b1e64627c826a", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-02-03T14:55:07+00:00" + }, + { + "name": "symfony/debug", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "9b1071f86e79e1999b3d3675d2e0e7684268b9bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/9b1071f86e79e1999b3d3675d2e0e7684268b9bc", + "reference": "9b1071f86e79e1999b3d3675d2e0e7684268b9bc", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/http-kernel": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-02-28T21:49:22+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "2bb5d3101cc01f4fe580e536daf4f1959bc2d24d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2bb5d3101cc01f4fe580e536daf4f1959bc2d24d", + "reference": "2bb5d3101cc01f4fe580e536daf4f1959bc2d24d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2018-02-22T10:48:49+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "58990682ac3fdc1f563b7e705452921372aad11d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/58990682ac3fdc1f563b7e705452921372aad11d", + "reference": "58990682ac3fdc1f563b7e705452921372aad11d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-02-14T10:03:57+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/253a4490b528597aa14d2bf5aeded6f5e5e4a541", + "reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-02-22T10:48:49+00:00" + }, + { + "name": "symfony/finder", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/a479817ce0a9e4adfd7d39c6407c95d97c254625", + "reference": "a479817ce0a9e4adfd7d39c6407c95d97c254625", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-03-05T18:28:11+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "6f5935723c11b4125fc9927db6ad2feaa196e175" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6f5935723c11b4125fc9927db6ad2feaa196e175", + "reference": "6f5935723c11b4125fc9927db6ad2feaa196e175", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2018-02-22T10:48:49+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", + "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "~1.0|~2.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php70\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-01-30T19:27:44+00:00" + }, + { + "name": "symfony/process", + "version": "v3.3.16", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "104b5acd605e3a7bd3ee92423540adb86f33d356" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/104b5acd605e3a7bd3ee92423540adb86f33d356", + "reference": "104b5acd605e3a7bd3ee92423540adb86f33d356", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:02:23+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "6af42631dcf89e9c616242c900d6c52bd53bd1bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/6af42631dcf89e9c616242c900d6c52bd53bd1bb", + "reference": "6af42631dcf89e9c616242c900d6c52bd53bd1bb", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-02-16T09:50:28+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07T12:08:54+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause-Attribution" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2016-09-01T10:05:43+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29T19:49:41+00:00" + } + ], + "packages-dev": [ + { + "name": "brainmaestro/composer-git-hooks", + "version": "v2.4.3", + "source": { + "type": "git", + "url": "https://github.com/BrainMaestro/composer-git-hooks.git", + "reference": "2e4d8bd95ee916f3951a55438752f8adab8b69fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/2e4d8bd95ee916f3951a55438752f8adab8b69fb", + "reference": "2e4d8bd95ee916f3951a55438752f8adab8b69fb", + "shasum": "" + }, + "require": { + "symfony/console": "^3.2 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7" + }, + "bin": [ + "cghooks" + ], + "type": "library", + "autoload": { + "psr-4": { + "BrainMaestro\\GitHooks\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ezinwa Okpoechi", + "email": "brainmaestro@outlook.com" + } + ], + "description": "Easily manage git hooks in your composer config", + "keywords": [ + "HOOK", + "composer", + "git" + ], + "time": "2018-02-27T10:23:55+00:00" + }, + { + "name": "codacy/coverage", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/codacy/php-codacy-coverage.git", + "reference": "546ac2e000a004571c1000ecff4d68a98706db3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/546ac2e000a004571c1000ecff4d68a98706db3a", + "reference": "546ac2e000a004571c1000ecff4d68a98706db3a", + "shasum": "" + }, + "require": { + "gitonomy/gitlib": ">=1.0", + "php": ">=5.3.3", + "symfony/console": "~2.5|~3.0|~4.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.5" + }, + "bin": [ + "bin/codacycoverage" + ], + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakob Pupke", + "email": "jakob.pupke@gmail.com" + } + ], + "description": "Sends PHP test coverage information to Codacy.", + "homepage": "https://github.com/codacy/php-codacy-coverage", + "time": "2017-12-20T14:31:46+00:00" + }, + { + "name": "codeception/aspect-mock", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Codeception/AspectMock.git", + "reference": "bf3c000599c0dc75ecb52e19dee2b8ed294cf7ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/AspectMock/zipball/bf3c000599c0dc75ecb52e19dee2b8ed294cf7ba", + "reference": "bf3c000599c0dc75ecb52e19dee2b8ed294cf7ba", + "shasum": "" + }, + "require": { + "goaop/framework": "^2.0.0", + "php": ">=5.6.0", + "symfony/finder": "~2.4|~3.0" + }, + "require-dev": { + "codeception/base": "~2.1", + "codeception/specify": "~0.3", + "codeception/verify": "~0.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "AspectMock": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Bodnarchuk", + "email": "davert@codeception.com" + } + ], + "description": "Experimental Mocking Framework powered by Aspects", + "time": "2017-10-24T10:20:17+00:00" + }, + { + "name": "gitonomy/gitlib", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/gitonomy/gitlib.git", + "reference": "1c2b0605115786613cb517798046c8ab57c17097" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gitonomy/gitlib/zipball/1c2b0605115786613cb517798046c8ab57c17097", + "reference": "1c2b0605115786613cb517798046c8ab57c17097", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0", + "symfony/process": "^2.3|^3.0|^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|^5.7", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Add some log" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Gitonomy\\Git\\": "src/Gitonomy/Git/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexandre Salomé", + "email": "alexandre.salome@gmail.com", + "homepage": "http://alexandre-salome.fr" + }, + { + "name": "Julien DIDIER", + "email": "genzo.wm@gmail.com", + "homepage": "http://www.jdidier.net" + } + ], + "description": "Library for accessing git", + "homepage": "http://gitonomy.com", + "time": "2018-01-10T11:34:47+00:00" + }, + { + "name": "goaop/framework", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/goaop/framework.git", + "reference": "6e2a0fe13c1943db02a67588cfd27692bddaffa5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/goaop/framework/zipball/6e2a0fe13c1943db02a67588cfd27692bddaffa5", + "reference": "6e2a0fe13c1943db02a67588cfd27692bddaffa5", + "shasum": "" + }, + "require": { + "doctrine/annotations": "~1.0", + "goaop/parser-reflection": "~1.2", + "jakubledl/dissect": "~1.0", + "php": ">=5.6.0" + }, + "require-dev": { + "adlawson/vfs": "^0.12", + "doctrine/orm": "^2.5", + "phpunit/phpunit": "^4.8", + "symfony/console": "^2.7|^3.0" + }, + "suggest": { + "symfony/console": "Enables the usage of the command-line tool." + }, + "bin": [ + "bin/aspect" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Go\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lisachenko Alexander", + "homepage": "https://github.com/lisachenko" + } + ], + "description": "Framework for aspect-oriented programming in PHP.", + "homepage": "http://go.aopphp.com/", + "keywords": [ + "aop", + "aspect", + "library", + "php" + ], + "time": "2017-07-12T11:46:25+00:00" + }, + { + "name": "goaop/parser-reflection", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/goaop/parser-reflection.git", + "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/goaop/parser-reflection/zipball/d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", + "reference": "d9c1dcc7ce4a5284fe3530e011faf9c9c10e1166", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^1.2|^2.0|^3.0", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Go\\ParserReflection\\": "src" + }, + "files": [ + "src/bootstrap.php" + ], + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander Lisachenko", + "email": "lisachenko.it@gmail.com" + } + ], + "description": "Provides reflection information, based on raw source", + "time": "2018-03-19T15:57:41+00:00" + }, + { + "name": "guzzle/guzzle", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": ">=2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "*", + "monolog/monolog": "1.*", + "phpunit/phpunit": "3.7.*", + "psr/log": "1.0.*", + "symfony/class-loader": "*", + "zendframework/zend-cache": "<2.3", + "zendframework/zend-log": "<2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.8-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "abandoned": "guzzlehttp/guzzle", + "time": "2014-01-28T22:29:15+00:00" + }, + { + "name": "jakubledl/dissect", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/jakubledl/dissect.git", + "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jakubledl/dissect/zipball/d3a391de31e45a247e95cef6cf58a91c05af67c4", + "reference": "d3a391de31e45a247e95cef6cf58a91c05af67c4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/console": "~2.1" + }, + "suggest": { + "symfony/console": "for the command-line tool" + }, + "bin": [ + "bin/dissect.php", + "bin/dissect" + ], + "type": "library", + "autoload": { + "psr-0": { + "Dissect": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "unlicense" + ], + "authors": [ + { + "name": "Jakub Lédl", + "email": "jakubledl@gmail.com" + } + ], + "description": "Lexing and parsing in pure PHP", + "homepage": "https://github.com/jakubledl/dissect", + "keywords": [ + "ast", + "lexing", + "parser", + "parsing" + ], + "time": "2013-01-29T21:29:14+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-02-28T20:30:58+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", + "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3|^4", + "symfony/dependency-injection": "^2.3.0|^3|^4", + "symfony/filesystem": "^2.3.0|^3|^4" + }, + "require-dev": { + "phpunit/phpunit": "^4.8|^5.7", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "time": "2017-12-13T13:21:38+00:00" + }, + { + "name": "php-coveralls/php-coveralls", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-coveralls/php-coveralls.git", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-coveralls/php-coveralls/zipball/37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "reference": "37f8f83fe22224eb9d9c6d593cdeb33eedd2a9ad", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-simplexml": "*", + "guzzle/guzzle": "^2.8 || ^3.0", + "php": "^5.3.3 || ^7.0", + "psr/log": "^1.0", + "symfony/config": "^2.1 || ^3.0 || ^4.0", + "symfony/console": "^2.1 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.0 || ^3.0 || ^4.0", + "symfony/yaml": "^2.0 || ^3.0 || ^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0" + }, + "suggest": { + "symfony/http-kernel": "Allows Symfony integration" + }, + "bin": [ + "bin/coveralls" + ], + "type": "library", + "autoload": { + "psr-4": { + "Satooshi\\": "src/Satooshi/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kitamura Satoshi", + "email": "with.no.parachute@gmail.com", + "homepage": "https://www.facebook.com/satooshi.jp" + } + ], + "description": "PHP client library for Coveralls API", + "homepage": "https://github.com/php-coveralls/php-coveralls", + "keywords": [ + "ci", + "coverage", + "github", + "test" + ], + "time": "2017-12-06T23:17:56+00:00" + }, + { + "name": "phpmd/phpmd", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/4e9924b2c157a3eb64395460fcf56b31badc8374", + "reference": "4e9924b2c157a3eb64395460fcf56b31badc8374", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "pdepend/pdepend": "^2.5", + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "project", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "http://phpmd.org/", + "keywords": [ + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "time": "2017-01-20T14:41:10+00:00" + }, + { + "name": "rregeer/phpunit-coverage-check", + "version": "0.1.4", + "source": { + "type": "git", + "url": "https://github.com/richardregeer/phpunit-coverage-check.git", + "reference": "c703ab643d54f959c5c219dd2b08892a0e3dd80d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/richardregeer/phpunit-coverage-check/zipball/c703ab643d54f959c5c219dd2b08892a0e3dd80d", + "reference": "c703ab643d54f959c5c219dd2b08892a0e3dd80d", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "bin": [ + "bin/coverage-check" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Richard Regeer", + "email": "rich2309@gmail.com" + } + ], + "description": "Check the code coverage using the clover report of phpunit", + "keywords": [ + "ci", + "code coverage", + "php", + "phpunit", + "testing", + "unittest" + ], + "time": "2017-10-13T06:34:31+00:00" + }, + { + "name": "sebastian/finder-facade", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/finder-facade.git", + "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", + "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f", + "shasum": "" + }, + "require": { + "symfony/finder": "~2.3|~3.0|~4.0", + "theseer/fdomdocument": "~1.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", + "homepage": "https://github.com/sebastianbergmann/finder-facade", + "time": "2017-11-18T17:31:49+00:00" + }, + { + "name": "sebastian/phpcpd", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpcpd.git", + "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/dfed51c1288790fc957c9433e2f49ab152e8a564", + "reference": "dfed51c1288790fc957c9433e2f49ab152e8a564", + "shasum": "" + }, + "require": { + "php": "^5.6|^7.0", + "phpunit/php-timer": "^1.0.6", + "sebastian/finder-facade": "^1.1", + "sebastian/version": "^1.0|^2.0", + "symfony/console": "^2.7|^3.0|^4.0" + }, + "bin": [ + "phpcpd" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Copy/Paste Detector (CPD) for PHP code.", + "homepage": "https://github.com/sebastianbergmann/phpcpd", + "time": "2017-11-16T08:49:28+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "1.5.3", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "396178ada8499ec492363587f037125bf7b07fcc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/396178ada8499ec492363587f037125bf7b07fcc", + "reference": "396178ada8499ec492363587f037125bf7b07fcc", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.1.2" + }, + "suggest": { + "phpunit/php-timer": "dev-master" + }, + "bin": [ + "scripts/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-phpcs-fixer": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/CommentParser/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenises PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2014-05-01T03:07:07+00:00" + }, + { + "name": "symfony/config", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "05e10567b529476a006b00746c5f538f1636810e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/05e10567b529476a006b00746c5f538f1636810e", + "reference": "05e10567b529476a006b00746c5f538f1636810e", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-02-14T10:03:57+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "12e901abc1cb0d637a0e5abe9923471361d96b07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/12e901abc1cb0d637a0e5abe9923471361d96b07", + "reference": "12e901abc1cb0d637a0e5abe9923471361d96b07", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<3.3.7", + "symfony/finder": "<3.3", + "symfony/proxy-manager-bridge": "<3.4", + "symfony/yaml": "<3.4" + }, + "provide": { + "psr/container-implementation": "1.0" + }, + "require-dev": { + "symfony/config": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2018-03-04T03:54:53+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v3.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "eb17cfa072cab26537ac37e9c4ece6c0361369af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/eb17cfa072cab26537ac37e9c4ece6c0361369af", + "reference": "eb17cfa072cab26537ac37e9c4ece6c0361369af", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Stopwatch Component", + "homepage": "https://symfony.com", + "time": "2018-02-17T14:55:25+00:00" + }, + { + "name": "theseer/fdomdocument", + "version": "1.6.6", + "source": { + "type": "git", + "url": "https://github.com/theseer/fDOMDocument.git", + "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca", + "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "lib-libxml": "*", + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "lead" + } + ], + "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", + "homepage": "https://github.com/theseer/fDOMDocument", + "time": "2017-06-30T11:53:12+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0" + }, + "platform-dev": [] +} diff --git a/dev/tests/functional/MFTF.suite.dist.yml b/dev/tests/functional/MFTF.suite.dist.yml new file mode 100644 index 000000000..4bb561b7a --- /dev/null +++ b/dev/tests/functional/MFTF.suite.dist.yml @@ -0,0 +1,35 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +# Codeception Test Suite Configuration +# +# Suite for acceptance tests. +# Perform tests in browser using the WebDriver or PhpBrowser. +# If you need both WebDriver and PHPBrowser tests - create a separate suite. + +class_name: AcceptanceTester +namespace: Magento\FunctionalTestingFramework +modules: + enabled: + - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver + - \Magento\FunctionalTestingFramework\Helper\Acceptance + - \Magento\FunctionalTestingFramework\Helper\MagentoFakerData + - \Magento\FunctionalTestingFramework\Module\MagentoAssert + - Asserts + config: + \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: + url: "%MAGENTO_BASE_URL%" + backend_name: "%MAGENTO_BACKEND_NAME%" + browser: 'chrome' + window_size: maximize + username: "%MAGENTO_ADMIN_USERNAME%" + password: "%MAGENTO_ADMIN_PASSWORD%" + pageload_timeout: 30 + host: %SELENIUM_HOST% + port: %SELENIUM_PORT% + protocol: %SELENIUM_PROTOCOL% + path: %SELENIUM_PATH% + capabilities: + chromeOptions: + args: ["--start-maximized", "--disable-extensions", "--enable-automation"] + diff --git a/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Page/MFTFDocPage.xml b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Page/MFTFDocPage.xml new file mode 100644 index 000000000..2b706ed67 --- /dev/null +++ b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Page/MFTFDocPage.xml @@ -0,0 +1,14 @@ + + + + + +
+ + diff --git a/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Section/ContentSection.xml b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Section/ContentSection.xml new file mode 100644 index 000000000..cf82b69e9 --- /dev/null +++ b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Section/ContentSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Test/DevDocsTest.xml b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Test/DevDocsTest.xml new file mode 100644 index 000000000..eceeb3f58 --- /dev/null +++ b/dev/tests/functional/MFTF/FunctionalTest/DevDocs/Test/DevDocsTest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + <description value="Magento Functional Testing Framework Documentation is available."/> + <severity value="CRITICAL"/> + <group value="mftf"/> + </annotations> + + <amOnPage stepKey="openMFTFDevDocPage" url="{{MFTFDocPage.url}}" /> + <see stepKey="verifyPageIntroText" selector="{{contentSection.pageIntro}}" userInput="Introduction to the Magento Functional Testing Framework" /> + </test> +</tests> diff --git a/dev/tests/functional/MFTF/_bootstrap.php b/dev/tests/functional/MFTF/_bootstrap.php new file mode 100755 index 000000000..6a524e867 --- /dev/null +++ b/dev/tests/functional/MFTF/_bootstrap.php @@ -0,0 +1,7 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_bootstrap.php'; \ No newline at end of file diff --git a/dev/tests/functional/_bootstrap.php b/dev/tests/functional/_bootstrap.php new file mode 100755 index 000000000..e51658f23 --- /dev/null +++ b/dev/tests/functional/_bootstrap.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define('PROJECT_ROOT', dirname(dirname(dirname(__DIR__)))); +require_once PROJECT_ROOT . '/vendor/autoload.php'; +$RELATIVE_FW_PATH = PROJECT_ROOT; + +//Load constants from .env file +if (file_exists(PROJECT_ROOT . '/.env')) { + $env = new \Dotenv\Loader(PROJECT_ROOT . '/.env'); + $env->load(); + + if (array_key_exists('TESTS_MODULE_PATH', $_ENV) xor array_key_exists('TESTS_BP', $_ENV)) { + throw new Exception('You must define both parameters TESTS_BP and TESTS_MODULE_PATH or neither parameter'); + } + + foreach ($_ENV as $key => $var) { + defined($key) || define($key, $var); + } +} +defined('FW_BP') || define('FW_BP', PROJECT_ROOT); + +// add the debug flag here +$debug_mode = $_ENV['MFTF_DEBUG'] ?? false; +if (!(bool)$debug_mode && extension_loaded('xdebug')) { + xdebug_disable(); +} + +$RELATIVE_TESTS_MODULE_PATH = '/MFTF/FunctionalTest'; + +defined('TESTS_BP') || define('TESTS_BP', __DIR__); +defined('TESTS_MODULE_PATH') || define('TESTS_MODULE_PATH', TESTS_BP . $RELATIVE_TESTS_MODULE_PATH); diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 060366f9a..b6b83870d 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -322,6 +322,10 @@ private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) */ private function printMagentoVersionInfo() { + $forceGeneration = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; + if ($forceGeneration) { + return; + } $url = ConfigSanitizerUtil::sanitizeUrl(getenv('MAGENTO_BASE_URL')) . $this->versionUrl; print "Fetching version information from {$url}"; $ch = curl_init($url); From 88e6ac55160b13854ab9d6dd775c6350e4eff8fe Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 22 Mar 2018 12:57:11 -0500 Subject: [PATCH 06/77] MQE-879: Add support for tests actions in suite pre/post conditions - update mustahche template to use new actions - update Magento Webdriver to account for no test context - update suiteSchema.xml and di.xml --- etc/di.xml | 8 +- .../Module/MagentoWebDriver.php | 14 +- .../Suite/Generators/GroupClassGenerator.php | 134 ++++++++++++++++-- .../Suite/SuiteGenerator.php | 3 +- .../Suite/Util/SuiteObjectExtractor.php | 8 ++ .../Suite/etc/suiteSchema.xsd | 14 +- .../Suite/views/SuiteClass.mustache | 20 +-- .../views/partials/dataPersistence.mustache | 8 -- .../Suite/views/partials/testActions.mustache | 33 +++++ .../Test/Util/ActionObjectExtractor.php | 6 +- .../Util/TestGenerator.php | 4 +- 11 files changed, 206 insertions(+), 46 deletions(-) delete mode 100644 src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache create mode 100644 src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache diff --git a/etc/di.xml b/etc/di.xml index f9e7e146f..dfcfeafe7 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -358,6 +358,10 @@ <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\SuiteData</argument> <argument name="idAttributes" xsi:type="array"> <item name="/suites/suite" xsi:type="string">name</item> + <item name="/suites/suite/(before|after)/remove" xsi:type="string">keyForRemoval</item> + <item name="/suites/suite/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> + <item name="/suites/suite/(before|after)/createData/requiredEntity" xsi:type="string">createDataKey</item> + <item name="/suites/suite/(before|after)/createData/field" xsi:type="string">key</item> <item name="/suites/suite/include/(group|test|module)" xsi:type="string">name</item> <item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item> </argument> @@ -370,8 +374,10 @@ <arguments> <argument name="assocArrayAttributes" xsi:type="array"> <item name="/suites/suite" xsi:type="string">name</item> - <item name="/suites/suite/(before|after)/(createData|deleteData)" xsi:type="string">stepKey</item> + <item name="/suites/suite/(before|after)/remove" xsi:type="string">keyForRemoval</item> + <item name="/suites/suite/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item> <item name="/suites/suite/(before|after)/createData/requiredEntity" xsi:type="string">createDataKey</item> + <item name="/suites/suite/(before|after)/createData/field" xsi:type="string">key</item> <item name="/suites/suite/include/(group|test|module)" xsi:type="string">name</item> <item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item> </argument> diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 9e50687a3..606437cd6 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -435,7 +435,7 @@ public function scrollToTopOfPage() * @param string $command * @returns string */ - public function executeMagentoCLICommand($command) + public function magentoCLI($command) { $apiURL = $this->config['url'] . getenv('MAGENTO_CLI_COMMAND_PATH'); @@ -527,6 +527,10 @@ public function _failed(TestInterface $test, $fail) $this->saveScreenshot(); } + if ($this->current_test == null) { + throw new \RuntimeException("Suite condition failure: \n" . $fail->getMessage()); + } + $this->addAttachment($this->pngReport, $test->getMetadata()->getName() . '.png', 'image/png'); $this->addAttachment($this->htmlReport, $test->getMetadata()->getName() . '.html', 'text/html'); @@ -540,8 +544,12 @@ public function _failed(TestInterface $test, $fail) */ public function saveScreenshot() { - $test = $this->current_test; - $filename = preg_replace('~\W~', '.', Descriptor::getTestSignature($test)); + $testDescription = "unknown." . uniqid(); + if ($this->current_test != null) { + $testDescription = Descriptor::getTestSignature($this->current_test); + } + + $filename = preg_replace('~\W~', '.', $testDescription); $outputDir = codecept_output_dir(); $this->_saveScreenshot($this->pngReport = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png'); $this->_savePageSource($this->htmlReport = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html'); diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php index cfc28ea5b..46c2caaed 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -26,7 +26,11 @@ class GroupClassGenerator const REQUIRED_ENTITY_KEY = 'requiredEntities'; const LAST_REQUIRED_ENTITY_TAG = 'last'; const MUSTACHE_VAR_TAG = 'var'; - + const MAGENTO_CLI_COMMAND_COMMAND = 'command'; + const DATA_PERSISTENCE_ACTIONS = ["createData", "deleteData"]; + const REPLACEMENT_ACTIONS = [ + 'comment' => 'print' + ]; const GROUP_DIR_NAME = 'Group'; /** @@ -83,14 +87,30 @@ private function createClassContent($suiteObject) $mustacheData[self::BEFORE_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getBeforeHook()); $mustacheData[self::AFTER_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getAfterHook()); - $mustacheData[self::MUSTACHE_VAR_TAG] = array_merge( - $mustacheData[self::BEFORE_MUSTACHE_KEY]['createData'] ?? [], - $mustacheData[self::AFTER_MUSTACHE_KEY]['createData'] ?? [] + $mustacheData[self::MUSTACHE_VAR_TAG] = $this->extractClassVar( + $mustacheData[self::BEFORE_MUSTACHE_KEY], + $mustacheData[self::AFTER_MUSTACHE_KEY] ); return $this->mustacheEngine->render(self::MUSTACHE_TEMPLATE_NAME, $mustacheData); } + /** + * Function which takes the before and after arrays containing the steps for the hook objects and extracts + * any variables names needed by the class template. + * + * @param array $beforeArray + * @param array $afterArray + * @return array + */ + private function extractClassVar($beforeArray, $afterArray) + { + $beforeVar = $beforeArray[self::MUSTACHE_VAR_TAG] ?? []; + $afterVar = $afterArray[self::MUSTACHE_VAR_TAG] ?? []; + + return array_merge($beforeVar, $afterVar); + } + /** * Function which takes hook objects and transforms data into array for mustache template engine. * @@ -100,26 +120,112 @@ private function createClassContent($suiteObject) private function buildHookMustacheArray($hookObj) { $mustacheHookArray = []; + $actions = []; + $hasWebDriverActions = false; foreach ($hookObj->getActions() as $action) { /** @var ActionObject $action */ + $index = count($actions); + if (!in_array($action->getType(), self::DATA_PERSISTENCE_ACTIONS)) { + if (!$hasWebDriverActions) { + $hasWebDriverActions = true; + } + + $actions = $this->buildWebDriverActionsMustacheArray($action, $actions, $index); + continue; + } + + // add these as vars to be created a class level in the template + if ($action->getType() == 'createData') { + $mustacheHookArray[self::MUSTACHE_VAR_TAG][] = [self::ENTITY_MERGE_KEY => $action->getStepKey()]; + } + $entityArray = []; $entityArray[self::ENTITY_MERGE_KEY] = $action->getStepKey(); - $entityArray[self::ENTITY_NAME_TAG] = - $action->getCustomActionAttributes()['entity'] ?? - $action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE]; - - // if there is more than 1 custom attribute, we can assume there are required entities - if (count($action->getCustomActionAttributes()) > 1) { - $entityArray[self::REQUIRED_ENTITY_KEY] = - $this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes()); - } + $entityArray[$action->getType()] = $action->getStepKey(); - $mustacheHookArray[$action->getType()][] = $entityArray; + $entityArray = $this->buildPersistenceMustacheArray($action, $entityArray); + $actions[$index] = $entityArray; + } + $mustacheHookArray['actions'] = $actions; + if ($hasWebDriverActions) { + array_unshift($mustacheHookArray['actions'], ['webDriverInit' => true]); + $mustacheHookArray['actions'][] = ['webDriverReset' => true]; } return $mustacheHookArray; } + /** + * Takes an action object and array of generated action steps. Converst the action object into generated php and + * appends the entry to the given array. The result is returned by the function. + * + * @param ActionObject $action + * @param array $actionEntries + * @return array + */ + private function buildWebDriverActionsMustacheArray($action, $actionEntries) + { + $step = TestGenerator::getInstance()->generateStepsPhp([$action], false, 'webDriver'); + $rawPhp = str_replace(["\t", "\n"], "", $step); + $multipleCommands = explode(";", $rawPhp, -1); + foreach ($multipleCommands as $command) { + $actionEntries = $this->replaceReservedTesterFunctions($command . ";", $actionEntries, 'webDriver'); + } + + return $actionEntries; + } + + /** + * Takes a generated php step, an array containing generated php entries for the template, and the actor name + * for the generated step. + * + * @param string $formattedStep + * @param array $actionEntries + * @param string $actor + * @return array + */ + private function replaceReservedTesterFunctions($formattedStep, $actionEntries, $actor) + { + foreach (self::REPLACEMENT_ACTIONS as $testAction => $replacement) { + $testActionCall = "\${$actor}->{$testAction}"; + if (substr($formattedStep, 0, strlen($testActionCall)) == $testActionCall) { + $resultingStep = str_replace($testActionCall, $replacement, $formattedStep); + $actionEntries[] = ['action' => $resultingStep]; + } else { + $actionEntries[] = ['action' => $formattedStep]; + } + } + + return $actionEntries; + } + + /** + * Takes an action object of persistence type and formats an array entiry for mustache template interpretation. + * + * @param ActionObject $action + * @param array $entityArray + * @return array + */ + private function buildPersistenceMustacheArray($action, $entityArray) + { + $entityArray[self::ENTITY_NAME_TAG] = + $action->getCustomActionAttributes()['entity'] ?? + $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()); + } + + // append entries for customFields if specified by the user. + if (array_key_exists('customFields', $action->getCustomActionAttributes())) { + $entityArray['customFields'] = $action->getStepKey() . 'Fields'; + } + + return $entityArray; + } + /** * Function which takes any required entities under a 'createData' tag and transforms data into array to be consumed * by mustache template. diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index b09d26d94..0d52bbf0a 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -119,7 +119,8 @@ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; - if ($groupNamespace) { + if ($groupNamespace && + !in_array($groupNamespace, $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG])) { $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace; } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 76bc409ef..030eb579c 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Suite\Util; use Exception; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; @@ -37,6 +38,7 @@ public function __construct() * * @param array $parsedSuiteData * @return array + * @throws XmlException */ public function parseSuiteDataIntoObjects($parsedSuiteData) { @@ -78,6 +80,12 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) $parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK] ); } + if (count($suiteHooks) == 1) { + throw new XmlException(sprintf( + "Suites that contain hooks must contain both a 'before' and an 'after' hook. Suite: \"%s\"", + $parsedSuite[self::NAME] + )); + } // create the new suite object $suiteObjects[$parsedSuite[self::NAME]] = new SuiteObject( diff --git a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd index c57b58d02..34ef37594 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd @@ -48,17 +48,17 @@ <xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/> </xs:choice> </xs:complexType> - <xs:complexType name="suiteHookType"> - <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:group ref="dataOperationTags" maxOccurs="unbounded" minOccurs="0"/> - </xs:choice> - </xs:complexType> + <!--<xs:complexType name="suiteHookType">--> + <!--<xs:choice minOccurs="0" maxOccurs="unbounded">--> + <!--<xs:group ref="dataOperationTags" maxOccurs="unbounded" minOccurs="0"/>--> + <!--</xs:choice>--> + <!--</xs:complexType>--> <xs:complexType name="suiteType"> <xs:choice minOccurs="0" maxOccurs="unbounded"> <xs:element type="includeType" name="include" maxOccurs="1"/> <xs:element type="excludeType" name="exclude" maxOccurs="1"/> - <xs:element type="suiteHookType" name="before" maxOccurs="1"/> - <xs:element type="suiteHookType" name="after" maxOccurs="1"/> + <xs:element type="hookType" name="before" maxOccurs="1"/> + <xs:element type="hookType" name="after" maxOccurs="1"/> </xs:choice> <xs:attribute type="xs:string" name="name"/> </xs:complexType> diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index 87fd6caf8..a1eac08fc 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -19,7 +19,8 @@ class {{suiteName}} extends \Codeception\GroupObject public static $group = '{{suiteName}}'; private static $TEST_COUNT = {{testCount}}; private static $CURRENT_TEST_RUN = 0; - + 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}} private ${{stepKey}}; {{/var}} @@ -31,7 +32,11 @@ class {{suiteName}} extends \Codeception\GroupObject self::$CURRENT_TEST_RUN++; if (self::$CURRENT_TEST_RUN == 1) { - {{> dataPersistence}} + print sprintf(self::$HOOK_EXECUTION_INIT, "before"); + + {{> testActions}} + + print sprintf(self::$HOOK_EXECUTION_END, "before"); } } {{/before}} @@ -40,14 +45,11 @@ class {{suiteName}} extends \Codeception\GroupObject public function _after(\Codeception\Event\TestEvent $e) { if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { - {{> dataPersistence}} - } - } + print sprintf(self::$HOOK_EXECUTION_INIT, "after"); - public function _failed(\Codeception\Event\TestEvent $e) - { - if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { - {{> dataPersistence}} + {{> testActions}} + + print sprintf(self::$HOOK_EXECUTION_END, "after"); } } {{/after}} diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache deleted file mode 100644 index 91602e594..000000000 --- a/src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache +++ /dev/null @@ -1,8 +0,0 @@ -{{#createData}} -${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}"); -$this->{{stepKey}} = new DataPersistenceHandler(${{entityName}}, [{{#requiredEntities}}$this->{{entityName}}{{^last}}, {{/last}}{{/requiredEntities}}]); -$this->{{stepKey}}->createEntity(); -{{/createData}} -{{#deleteData}} -$this->{{entityName}}->deleteEntity(); -{{/deleteData}} diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache new file mode 100644 index 000000000..2f6c41ef4 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache @@ -0,0 +1,33 @@ +{{#actions}} +{{#webDriverInit}} +$webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver'); + +// close any open sessions +if ($webDriver->webDriver != null) { + $webDriver->webDriver->close(); + $webDriver->webDriver = null; +} + +// initialize the webdriver session +$webDriver->_initializeSession(); + +// execute user specified actions +{{/webDriverInit}} +{{#webDriverReset}} +// reset configuration and close session +$this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); +$webDriver->webDriver->close(); +$webDriver->webDriver = null; +{{/webDriverReset}} +{{#action}} +{{{action}}} +{{/action}} +{{#createData}} +${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}"); +$this->{{stepKey}} = new DataPersistenceHandler(${{entityName}}, [{{#requiredEntities}}$this->{{entityName}}{{^last}}, {{/last}}{{/requiredEntities}}]{{#customFields}}, ${{customFields}}{{/customFields}}); +$this->{{stepKey}}->createEntity(); +{{/createData}} +{{#deleteData}} +$this->{{entityName}}->deleteEntity(); +{{/deleteData}} +{{/actions}} diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index 7f50e0b3c..eb05c7e89 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -178,7 +178,7 @@ private function extractFieldReferences($actionData, $actionAttributes) return $actionAttributes; } - $attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][self::NODE_NAME] = 'fields'; + $attributes = []; foreach ($actionAttributes as $attributeName => $attributeValue) { if (!is_array($attributeValue) || $attributeValue[self::NODE_NAME] != self::DATA_PERSISTENCE_CUSTOM_FIELD) { $attributes[$attributeName] = $attributeValue; @@ -188,6 +188,10 @@ private function extractFieldReferences($actionData, $actionAttributes) $attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][] = $attributeName; } + if (array_key_exists(self::ACTION_OBJECT_PERSISTENCE_FIELDS, $attributes)) { + $attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][self::NODE_NAME] = 'fields'; + } + return $attributes; } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 2ac2d0abd..6ca656b71 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -388,18 +388,18 @@ private function generateClassAnnotations($annotationType, $annotationName) * * @param array $actionObjects * @param array|bool $hookObject + * @param string $actor * @return string * @throws TestReferenceException * @throws \Exception * @SuppressWarnings(PHPMD) */ - private function generateStepsPhp($actionObjects, $hookObject = false) + public function generateStepsPhp($actionObjects, $hookObject = false, $actor = "I") { //TODO: Refactor Method according to PHPMD warnings, remove @SuppressWarnings accordingly. $testSteps = ""; foreach ($actionObjects as $actionObject) { - $actor = "I"; $stepKey = $actionObject->getStepKey(); $customActionAttributes = $actionObject->getCustomActionAttributes(); $attribute = null; From cfb142b01c17cef806cdd1aba5d75d7c1026888b Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Tue, 20 Mar 2018 11:04:00 -0500 Subject: [PATCH 07/77] MQE-779: Default Directory exists for tests not referenced in other suites - Non-suite tests generate to _generate/default directory - Error is thrown if suite name = default - Manifest.txt and group directories generate under _generated - Verification tests updated MQE-799: Default Directory exists for tests not referenced in other suites - Updated phpDocs - Changed constructor for SingleRunTestManifest.php to call correct parameters for parent DefaultTestManifest and set singleRun config MQE-779: Default Directory exists for tests not referenced in other suites - Added underscore to namespacing for cests in TestGenerator - Removed unncessary manifest path setting in SingleRunManifest which is handled by parent constructor MQE-779: Default Directory exists for tests not referenced in other suites - Fixed static check failures MQE-779: Default Directory exists for tests not referenced in other suites - Updated namespace in verification tests to _default from default to match TestGenerator changes for same. --- .../ActionGroupWithDataOverrideTest.txt | 2 +- .../Resources/ActionGroupWithDataTest.txt | 2 +- ...hDefaultArgumentAndStringSelectorParam.txt | 2 +- ...eParameterSelectorsFromDefaultArgument.txt | 2 +- .../Resources/ActionGroupWithNoArguments.txt | 2 +- .../ActionGroupWithNoDefaultTest.txt | 2 +- ...thPassedArgumentAndStringSelectorParam.txt | 2 +- .../ActionGroupWithPersistedData.txt | 2 +- ...WithSimpleDataUsageFromDefaultArgument.txt | 2 +- ...pWithSimpleDataUsageFromPassedArgument.txt | 2 +- ...leParameterSelectorFromDefaultArgument.txt | 2 +- ...gleParameterSelectorFromPassedArgument.txt | 2 +- .../ActionGroupWithStepKeyReferences.txt | 2 +- .../ActionGroupWithTopLevelPersistedData.txt | 2 +- .../ArgumentWithSameNameAsElement.txt | 2 +- .../verification/Resources/AssertTest.txt | 2 +- .../Resources/BasicActionGroupTest.txt | 2 +- .../Resources/BasicFunctionalTest.txt | 2 +- .../verification/Resources/BasicMergeTest.txt | 2 +- .../Resources/CharacterReplacementTest.txt | 2 +- .../Resources/DataReplacementTest.txt | 2 +- .../Resources/HookActionsTest.txt | 2 +- .../Resources/LocatorFunctionTest.txt | 2 +- .../Resources/MergedActionGroupTest.txt | 2 +- .../Resources/MergedReferencesTest.txt | 2 +- .../Resources/MultipleActionGroupsTest.txt | 2 +- .../Resources/PageReplacementTest.txt | 2 +- .../Resources/ParameterArrayTest.txt | 2 +- .../Resources/PersistedReplacementTest.txt | 2 +- .../Resources/PersistenceCustomFieldsTest.txt | 2 +- .../Resources/SectionReplacementTest.txt | 2 +- .../Tests/SuiteGenerationTest.php | 3 ++- .../Suite/Util/SuiteObjectExtractor.php | 3 +++ .../Util/Manifest/DefaultTestManifest.php | 10 ++++----- .../Util/Manifest/ParallelTestManifest.php | 9 ++++---- .../Util/Manifest/SingleRunTestManifest.php | 10 ++++----- .../Util/Manifest/TestManifestFactory.php | 11 +++++----- .../Util/TestGenerator.php | 21 ++++++++++++------- 38 files changed, 71 insertions(+), 58 deletions(-) diff --git a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt index b46508433..75bbfeb93 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt index ec74168b8..74e571714 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt index de83088a1..9c56afff4 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDefaultArgumentAndStringSelectorParam.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt index d1457b121..aa213c80f 100644 --- a/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithMultipleParameterSelectorsFromDefaultArgument.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt index 591241f09..45807663a 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoArguments.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt index 0c022f825..2eee844b3 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt index b3a6cc89f..c16a6a26e 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt index 2a15e8394..45eca0bee 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt index 37c9977ed..3774e639d 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromDefaultArgument.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt index 9e385f72b..a1e5a316b 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSimpleDataUsageFromPassedArgument.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt index 6fbf041b6..d04e2ba80 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromDefaultArgument.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt index b74fb7179..98c561e3c 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt index 8a56a6906..ac97746b8 100644 --- a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt index aaa1378ca..04c70911b 100644 --- a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 05fddcfa3..26ea8178e 100644 --- a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt +++ b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/AssertTest.txt b/dev/tests/verification/Resources/AssertTest.txt index c34065533..60eed6a8d 100644 --- a/dev/tests/verification/Resources/AssertTest.txt +++ b/dev/tests/verification/Resources/AssertTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/BasicActionGroupTest.txt b/dev/tests/verification/Resources/BasicActionGroupTest.txt index dae0f2aba..86835364e 100644 --- a/dev/tests/verification/Resources/BasicActionGroupTest.txt +++ b/dev/tests/verification/Resources/BasicActionGroupTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 9d4a0a3dd..26a838071 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/BasicMergeTest.txt b/dev/tests/verification/Resources/BasicMergeTest.txt index 0b457be66..e199f2c55 100644 --- a/dev/tests/verification/Resources/BasicMergeTest.txt +++ b/dev/tests/verification/Resources/BasicMergeTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/CharacterReplacementTest.txt b/dev/tests/verification/Resources/CharacterReplacementTest.txt index 18c3ed1f8..4ae325fba 100644 --- a/dev/tests/verification/Resources/CharacterReplacementTest.txt +++ b/dev/tests/verification/Resources/CharacterReplacementTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index f2cc79e27..a6d620a2c 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/HookActionsTest.txt b/dev/tests/verification/Resources/HookActionsTest.txt index 0bcd95ca5..4a49e8e9d 100644 --- a/dev/tests/verification/Resources/HookActionsTest.txt +++ b/dev/tests/verification/Resources/HookActionsTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/LocatorFunctionTest.txt b/dev/tests/verification/Resources/LocatorFunctionTest.txt index ff3afbb3e..37e5ae9fb 100644 --- a/dev/tests/verification/Resources/LocatorFunctionTest.txt +++ b/dev/tests/verification/Resources/LocatorFunctionTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/MergedActionGroupTest.txt b/dev/tests/verification/Resources/MergedActionGroupTest.txt index 01655eed4..055d8dbe3 100644 --- a/dev/tests/verification/Resources/MergedActionGroupTest.txt +++ b/dev/tests/verification/Resources/MergedActionGroupTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/MergedReferencesTest.txt b/dev/tests/verification/Resources/MergedReferencesTest.txt index 0774d06f1..2e42b6587 100644 --- a/dev/tests/verification/Resources/MergedReferencesTest.txt +++ b/dev/tests/verification/Resources/MergedReferencesTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt index 4272c72fe..3946495ed 100644 --- a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt +++ b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/PageReplacementTest.txt b/dev/tests/verification/Resources/PageReplacementTest.txt index ce83ac9a8..5621e5c7a 100644 --- a/dev/tests/verification/Resources/PageReplacementTest.txt +++ b/dev/tests/verification/Resources/PageReplacementTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/ParameterArrayTest.txt b/dev/tests/verification/Resources/ParameterArrayTest.txt index 3bb986b10..2e196225d 100644 --- a/dev/tests/verification/Resources/ParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ParameterArrayTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/PersistedReplacementTest.txt b/dev/tests/verification/Resources/PersistedReplacementTest.txt index 81321e7ed..ca09c767e 100644 --- a/dev/tests/verification/Resources/PersistedReplacementTest.txt +++ b/dev/tests/verification/Resources/PersistedReplacementTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt index f4d607c0b..53db9bcf8 100644 --- a/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt +++ b/dev/tests/verification/Resources/PersistenceCustomFieldsTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Resources/SectionReplacementTest.txt b/dev/tests/verification/Resources/SectionReplacementTest.txt index 73b34fe30..b89eb130b 100644 --- a/dev/tests/verification/Resources/SectionReplacementTest.txt +++ b/dev/tests/verification/Resources/SectionReplacementTest.txt @@ -1,5 +1,5 @@ <?php -namespace Magento\AcceptanceTest\_generated\Backend; +namespace Magento\AcceptanceTest\_default\Backend; use Magento\FunctionalTestingFramework\AcceptanceTester; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index 44428d128..fc9897f9c 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -6,6 +6,7 @@ namespace tests\verification\Tests; +use Magento\Framework\Module\Dir; use Magento\FunctionalTestingFramework\Suite\SuiteGenerator; use Magento\FunctionalTestingFramework\Util\TestManifest; use Symfony\Component\Yaml\Yaml; @@ -79,7 +80,7 @@ public function testSuiteGeneration1() DIRECTORY_SEPARATOR; // Validate test manifest contents - $actualManifest = $suiteResultBaseDir . 'testManifest.txt'; + $actualManifest = dirname($suiteResultBaseDir). DIRECTORY_SEPARATOR . 'testManifest.txt'; $actualTestReferences = explode(PHP_EOL, file_get_contents($actualManifest)); for ($i = 0; $i < count($actualTestReferences); $i++) { diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 030eb579c..16d65630c 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -49,6 +49,9 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) // skip non array items parsed from suite (suite objects will always be arrays) continue; } + if ($parsedSuite[self::NAME] == 'default') { + throw new XmlException("A Suite can not have the name \"default\""); + } $suiteHooks = []; diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index 20173d508..bcfa877ca 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -28,13 +28,13 @@ class DefaultTestManifest extends BaseTestManifest /** * DefaultTestManifest constructor. - * @param string $path + * @param string $manifestPath + * @param string $testPath */ - public function __construct($path) + public function __construct($manifestPath, $testPath) { - $this->manifestPath = $path . DIRECTORY_SEPARATOR . 'testManifest.txt'; - parent::__construct($path, self::DEFAULT_CONFIG); - + $this->manifestPath = $manifestPath . DIRECTORY_SEPARATOR . 'testManifest.txt'; + parent::__construct($testPath, self::DEFAULT_CONFIG); $fileResource = fopen($this->manifestPath, 'w'); fclose($fileResource); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php index d9ec813ac..f130ecff7 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php @@ -31,12 +31,13 @@ class ParallelTestManifest extends BaseTestManifest /** * TestManifest constructor. * - * @param string $path + * @param string $manifestPath + * @param string $testPath */ - public function __construct($path) + public function __construct($manifestPath, $testPath) { - $this->dirPath = $path . DIRECTORY_SEPARATOR . 'groups'; - parent::__construct($path, self::PARALLEL_CONFIG); + $this->dirPath = $manifestPath . DIRECTORY_SEPARATOR . 'groups'; + parent::__construct($testPath, self::PARALLEL_CONFIG); } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php index ace4e43f7..2896f857f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php @@ -12,13 +12,13 @@ class SingleRunTestManifest extends DefaultTestManifest /** * SingleRunTestManifest constructor. - * @param string $path + * @param string $manifestPath + * @param string $testPath */ - public function __construct($path) + public function __construct($manifestPath, $testPath) { - $this->manifestPath = $path . DIRECTORY_SEPARATOR . 'testManifest.txt'; - parent::__construct($path, self::SINGLE_RUN_CONFIG); - + parent::__construct($manifestPath, $testPath); + $this->runTypeConfig = self::SINGLE_RUN_CONFIG; $fileResource = fopen($this->manifestPath, 'w'); fclose($fileResource); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php index e4bac25de..bc54a47ea 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php @@ -19,21 +19,22 @@ private function __construct() /** * Static function which takes path and config to return the appropriate manifest output type. * - * @param String $path + * @param String $manifestPath + * @param String $testPath * @param String $runConfig * @return BaseTestManifest */ - public static function makeManifest($path, $runConfig) + public static function makeManifest($manifestPath, $testPath, $runConfig) { switch ($runConfig) { case 'singleRun': - return new SingleRunTestManifest($path); + return new SingleRunTestManifest($manifestPath, $testPath); case 'parallel': - return new ParallelTestManifest($path); + return new ParallelTestManifest($manifestPath, $testPath); default: - return new DefaultTestManifest($path); + return new DefaultTestManifest($manifestPath, $testPath); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 6ca656b71..b6c2071ea 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -29,6 +29,7 @@ class TestGenerator { const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; const GENERATED_DIR = '_generated'; + const DEFAULT_DIR = 'default'; /** * Path to the export dir. @@ -60,11 +61,13 @@ class TestGenerator private function __construct($exportDir, $tests) { // private constructor for factory - $this->exportDirName = $exportDir ?? self::GENERATED_DIR; - $this->exportDirectory = rtrim( - TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . self::GENERATED_DIR . DIRECTORY_SEPARATOR . $exportDir, - DIRECTORY_SEPARATOR - ); + $this->exportDirName = $exportDir ?? self::DEFAULT_DIR; + $exportDir = $exportDir ?? self::DEFAULT_DIR; + $this->exportDirectory = TESTS_MODULE_PATH + . DIRECTORY_SEPARATOR + . self::GENERATED_DIR + . DIRECTORY_SEPARATOR + . $exportDir; $this->tests = $tests; } @@ -141,7 +144,11 @@ public function createAllTestFiles($runConfig = null, $nodes = null) DirSetupUtil::createGroupDir($this->exportDirectory); // create our manifest file here - $testManifest = TestManifestFactory::makeManifest($this->exportDirectory, $runConfig); + $testManifest = TestManifestFactory::makeManifest( + dirname($this->exportDirectory), + $this->exportDirectory, + $runConfig + ); $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes); foreach ($testPhpArray as $testPhpFile) { @@ -172,7 +179,7 @@ private function assembleTestPhp($testObject) } $cestPhp = "<?php\n"; - $cestPhp .= "namespace Magento\AcceptanceTest\\" . $this->exportDirName . "\Backend;\n\n"; + $cestPhp .= "namespace Magento\AcceptanceTest\\_" . $this->exportDirName . "\Backend;\n\n"; $cestPhp .= $usePhp; $cestPhp .= $classAnnotationsPhp; $cestPhp .= sprintf("class %s\n", $className); From 45962be154ee5da6b0f04d788ba610796ff030e7 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Fri, 23 Mar 2018 10:29:04 +0200 Subject: [PATCH 08/77] Make possible to create and run MFTF test in framework to verify Framework basic functionality --- RoboFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoboFile.php b/RoboFile.php index efb08c8fa..f6d5f96de 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -22,7 +22,7 @@ function cloneFiles() { $this->_exec('cp -vn .env.example .env'); $this->_exec('cp -vf codeception.dist.yml codeception.yml'); - $this->_exec('cp -vf dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'functional.suite.dist.yml dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'functional.suite.yml'); + $this->_exec('cp -vf dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'MFTF.suite.dist.yml dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'MFTF.suite.yml'); } /** From c96c88a6775c71508e3d59d7e943a5f32fa1ad4f Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Fri, 23 Mar 2018 10:32:18 +0200 Subject: [PATCH 09/77] Make possible to create and run MFTF test in framework to verify Framework basic functionality --- RoboFile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RoboFile.php b/RoboFile.php index f6d5f96de..0b891094b 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -22,7 +22,7 @@ function cloneFiles() { $this->_exec('cp -vn .env.example .env'); $this->_exec('cp -vf codeception.dist.yml codeception.yml'); - $this->_exec('cp -vf dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'MFTF.suite.dist.yml dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR .'MFTF.suite.yml'); + $this->_exec('cp -vf dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR .'MFTF.suite.dist.yml dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR .'MFTF.suite.yml'); } /** From 08c089adb4f87e95e3772505bb46a96d90ad2e54 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Fri, 23 Mar 2018 10:34:34 +0200 Subject: [PATCH 10/77] Make possible to create and run MFTF test in framework to verify Framework basic functionality --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 432851acb..536ea1fa5 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ #See COPYING.txt for license details. #*** Set the base URL for your Magento instance ***# -MAGENTO_BASE_URL= +MAGENTO_BASE_URL=http://devdocs.magento.com/ #*** Set the Admin Username and Password for your Magento instance ***# MAGENTO_BACKEND_NAME= From 778a626709b55c643436c702980c7ac509789261 Mon Sep 17 00:00:00 2001 From: Serhii Dzhepa <serjgray@gmail.com> Date: Fri, 23 Mar 2018 13:53:31 +0200 Subject: [PATCH 11/77] magento/magento2-functional-testing-framework#56: Add "block" type to list of existing section element types --- .../FunctionalTestingFramework/Page/etc/SectionObject.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd index 04463838d..a2457f4e4 100644 --- a/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd +++ b/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd @@ -106,6 +106,7 @@ <xs:enumeration value="multiselect"/> <xs:enumeration value="wysiwyg"/> <xs:enumeration value="iframe"/> + <xs:enumeration value="block"/> </xs:restriction> </xs:simpleType> From 97630624d3b677844fc945b9730144921303e41d Mon Sep 17 00:00:00 2001 From: Vasyl Malanka <vsl.malanka@gmail.com> Date: Fri, 23 Mar 2018 20:12:09 +0200 Subject: [PATCH 12/77] Added exception throwing on test generation if project is not built #61 --- RoboFile.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/RoboFile.php b/RoboFile.php index 0b891094b..6ea259ed3 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -52,11 +52,32 @@ function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => nu $GLOBALS['FORCE_PHP_GENERATE'] = true; } - require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; + require 'dev' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; + + if (!$this->isProjectBuilt()) { + throw new Exception('Please run vendor/bin/robo build:project and configure your environment (.env) first.'); + } + \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance()->createAllTestFiles($opts['config'], $opts['nodes']); $this->say("Generate Tests Command Run"); } + /** + * Check if MFTF has been properly configured + * @return bool + */ + private function isProjectBuilt() + { + $actorFile = __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR . 'FunctionalTestingFramework' . DIRECTORY_SEPARATOR . '_generated' . DIRECTORY_SEPARATOR . 'AcceptanceTesterActions.php'; + + $login = getenv('MAGENTO_ADMIN_USERNAME'); + $password = getenv('MAGENTO_ADMIN_PASSWORD'); + $baseUrl = getenv('MAGENTO_BASE_URL'); + $backendName = getenv('MAGENTO_BACKEND_NAME'); + + return (file_exists($actorFile) && $login && $password && $baseUrl && $backendName); + } + /** * Generate a suite based on name(s) passed in as args. * From fcb309007468913958629877bb8a2293478bfbc0 Mon Sep 17 00:00:00 2001 From: StasKozar <staskozar.91@gmail.com> Date: Sat, 24 Mar 2018 13:37:14 +0200 Subject: [PATCH 13/77] magento/magento2-functional-testing-framework#58: Allow browser type to be configured in env file --- .env.example | 3 +++ dev/tests/functional/MFTF.suite.dist.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 536ea1fa5..382a35bd3 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,9 @@ MAGENTO_ADMIN_PASSWORD= #SELENIUM_PROTOCOL=http #SELENIUM_PATH=/wd/hub +#*** Browser for running tests, default chrome. Uncomment and change if you want to run tests on another browser (ex. firefox). +#BROWSER=chrome + #*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***# #MAGENTO_RESTAPI_SERVER_HOST= #MAGENTO_RESTAPI_SERVER_PORT= diff --git a/dev/tests/functional/MFTF.suite.dist.yml b/dev/tests/functional/MFTF.suite.dist.yml index 4bb561b7a..a54620385 100644 --- a/dev/tests/functional/MFTF.suite.dist.yml +++ b/dev/tests/functional/MFTF.suite.dist.yml @@ -20,7 +20,7 @@ modules: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: url: "%MAGENTO_BASE_URL%" backend_name: "%MAGENTO_BACKEND_NAME%" - browser: 'chrome' + browser: '%BROWSER%' window_size: maximize username: "%MAGENTO_ADMIN_USERNAME%" password: "%MAGENTO_ADMIN_PASSWORD%" From 8c8a7718539674cebc211736b2bab22c0af665be Mon Sep 17 00:00:00 2001 From: StasKozar <staskozar.91@gmail.com> Date: Sat, 24 Mar 2018 16:25:14 +0200 Subject: [PATCH 14/77] magento/magento2-functional-testing-framework#57: Debug flag exists in robo generate:tests to print action sequence of test steps --- RoboFile.php | 8 +++- .../Test/Objects/TestObject.php | 18 +++++++++ .../Util/TestGenerator.php | 38 +++++++++++++++++-- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/RoboFile.php b/RoboFile.php index 0b891094b..9b4570b7b 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; /** This is project's console commands configuration for Robo task runner. * @@ -43,7 +44,7 @@ function buildProject() * @param array $opts * @return void */ - function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => null]) + function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => null, 'debug' => false]) { $GLOBALS['GENERATE_TESTS'] = true; @@ -51,9 +52,12 @@ function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => nu { $GLOBALS['FORCE_PHP_GENERATE'] = true; } + + $output = $this->getOutput() ?? null; require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; - \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance()->createAllTestFiles($opts['config'], $opts['nodes']); + \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance() + ->createAllTestFiles($opts['config'], $opts['nodes'], $opts['debug'], $output); $this->say("Generate Tests Command Run"); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index de64100b8..8afdfd3d1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -159,4 +159,22 @@ public function getOrderedActions() $mergeUtil = new ActionMergeUtil($this->getName(), "Test"); return $mergeUtil->resolveActionSteps($this->parsedSteps); } + + /** + * Get information about actions and steps in test. + * + * @return array + */ + public function getDebugInformation() + { + $debugInformation = []; + $mergeUtil = new ActionMergeUtil($this->getName(), "Test"); + $orderList = $mergeUtil->resolveActionSteps($this->parsedSteps); + + foreach ($orderList as $action) { + $debugInformation[] = "\t" . $action->getType() . ' ' . $action->getStepKey(); + } + + return $debugInformation; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 2ac2d0abd..0fb54a992 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -20,6 +20,8 @@ use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Robo\Common\IO; +use Symfony\Component\Console\Output\OutputInterface; /** * Class TestGenerator @@ -27,6 +29,8 @@ */ class TestGenerator { + use IO; + const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; const GENERATED_DIR = '_generated'; @@ -132,17 +136,22 @@ private function createCestFile($testPhp, $filename) * * @param string $runConfig * @param int $nodes + * @param bool $debug + * @param OutputInterface|null $output * @return void * @throws TestReferenceException * @throws \Exception */ - public function createAllTestFiles($runConfig = null, $nodes = null) + public function createAllTestFiles($runConfig = null, $nodes = null, $debug = false, $output = null) { DirSetupUtil::createGroupDir($this->exportDirectory); + if ($output !== null) { + $this->setOutput($output); + } // create our manifest file here $testManifest = TestManifestFactory::makeManifest($this->exportDirectory, $runConfig); - $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes); + $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes, $debug); foreach ($testPhpArray as $testPhpFile) { $this->createCestFile($testPhpFile[1], $testPhpFile[0]); @@ -189,22 +198,28 @@ private function assembleTestPhp($testObject) * * @param BaseTestManifest $testManifest * @param int $nodes + * @param bool $debug * @return array * @throws TestReferenceException * @throws \Exception */ - private function assembleAllTestPhp($testManifest, $nodes) + private function assembleAllTestPhp($testManifest, $nodes, $debug = false) { /** @var TestObject[] $testObjects */ $testObjects = $this->loadAllTestObjects(); $cestPhpArray = []; foreach ($testObjects as $test) { + $this->debug('Start creating test: ' . $test->getCodeceptionName(), $debug); $php = $this->assembleTestPhp($test); $cestPhpArray[] = [$test->getCodeceptionName(), $php]; //write to manifest here if config is not single run $testManifest->addTest($test); + $debugInformation = $test->getDebugInformation(); + + $this->debug($debugInformation, $debug); + $this->debug('Finish creating test ' . $test->getCodeceptionName(), $debug); } $testManifest->generate($nodes); @@ -212,6 +227,23 @@ private function assembleAllTestPhp($testManifest, $nodes) return $cestPhpArray; } + /** + * Output information in console when debug flag is enabled. + * + * @param array|string $messages + * @param bool $debug + * @return void + */ + private function debug($messages, $debug = false) + { + if ($debug && $messages) { + $messages = (array) $messages; + foreach ($messages as $message) { + $this->say($message); + } + } + } + /** * Creates a PHP string for the necessary Allure and AcceptanceTester use statements. * Since we don't support other dependencies at this time, this function takes no parameter. From 44ced3fb7108077fa926a6ffcae8db0cabde9968 Mon Sep 17 00:00:00 2001 From: StasKozar <staskozar.91@gmail.com> Date: Sat, 24 Mar 2018 16:34:40 +0200 Subject: [PATCH 15/77] magento/magento2-functional-testing-framework#58: Allow browser type to be configured in env file --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 382a35bd3..c926402c1 100644 --- a/.env.example +++ b/.env.example @@ -20,7 +20,7 @@ MAGENTO_ADMIN_PASSWORD= #SELENIUM_PATH=/wd/hub #*** Browser for running tests, default chrome. Uncomment and change if you want to run tests on another browser (ex. firefox). -#BROWSER=chrome +BROWSER=chrome #*** Uncomment and set host & port if your dev environment needs different value other than MAGENTO_BASE_URL for Rest API Requests ***# #MAGENTO_RESTAPI_SERVER_HOST= From 91ef18865c46877a80cb39e4013d908c9e8650bb Mon Sep 17 00:00:00 2001 From: StasKozar <staskozar.91@gmail.com> Date: Sat, 24 Mar 2018 17:33:03 +0200 Subject: [PATCH 16/77] magento/magento2-functional-testing-framework#57: Debug flag exists in robo generate:tests to print action sequence of test steps --- RoboFile.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/RoboFile.php b/RoboFile.php index 9b4570b7b..2f5c7988d 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; /** This is project's console commands configuration for Robo task runner. * @@ -52,12 +51,10 @@ function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => nu { $GLOBALS['FORCE_PHP_GENERATE'] = true; } - - $output = $this->getOutput() ?? null; require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance() - ->createAllTestFiles($opts['config'], $opts['nodes'], $opts['debug'], $output); + ->createAllTestFiles($opts['config'], $opts['nodes'], $opts['debug'], $this->getOutput()); $this->say("Generate Tests Command Run"); } From 5f3438cf22ac2ff999328f00a937ac36caaab547 Mon Sep 17 00:00:00 2001 From: StasKozar <staskozar.91@gmail.com> Date: Sat, 24 Mar 2018 17:40:28 +0200 Subject: [PATCH 17/77] magento/magento2-functional-testing-framework#57: Debug flag exists in robo generate:tests to print action sequence of test steps --- .../FunctionalTestingFramework/Test/Objects/TestObject.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 8afdfd3d1..70a138d35 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -168,8 +168,7 @@ public function getOrderedActions() public function getDebugInformation() { $debugInformation = []; - $mergeUtil = new ActionMergeUtil($this->getName(), "Test"); - $orderList = $mergeUtil->resolveActionSteps($this->parsedSteps); + $orderList = $this->getOrderedActions(); foreach ($orderList as $action) { $debugInformation[] = "\t" . $action->getType() . ' ' . $action->getStepKey(); From 43891bd043b6438d20de296b0da2afe476019e9e Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Mon, 26 Mar 2018 14:27:54 +0300 Subject: [PATCH 18/77] MQE-881: Cannot Pass Arguments In Action Groups To X, Y Attributes On Actions MQE-877: Passing Multiple Element References In Selector Parameters Does Not Generate Correctly When Element(s) Are Parameterized - added verification tests --- dev/tests/verification/Resources/BasicFunctionalTest.txt | 2 ++ dev/tests/verification/TestModule/Data/ReplacementData.xml | 4 ++++ .../verification/TestModule/Test/BasicFunctionalTest.xml | 2 ++ .../FunctionalTestingFramework/Test/Objects/ActionObject.php | 2 +- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 +- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 9d4a0a3dd..fc705a8c0 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -70,6 +70,8 @@ class BasicFunctionalTestCest $I->click(".functionalTestSelector"); $I->clickWithLeftButton(".functionalTestSelector"); $I->clickWithRightButton(".functionalTestSelector"); + $I->clickWithLeftButton("#element#element .200", 200, 300); + $I->clickWithRightButton("#element .4123#element", 200, 300); $I->closeTab(); $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector", true); $I->dontSee("someInput", ".functionalTestSelector"); diff --git a/dev/tests/verification/TestModule/Data/ReplacementData.xml b/dev/tests/verification/TestModule/Data/ReplacementData.xml index 1b5417f75..0b327689c 100644 --- a/dev/tests/verification/TestModule/Data/ReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/ReplacementData.xml @@ -26,4 +26,8 @@ <data key="sampleField2">moreData</data> <requiredEntity type="test2">originalValue2</requiredEntity> </entity> + <entity name="offset" type="simpleData"> + <data key="x">200</data> + <data key="y">300</data> + </entity> </entities> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index a2f0d11b0..04181382d 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -32,6 +32,8 @@ <click selector=".functionalTestSelector" stepKey="clickKey1"/> <clickWithLeftButton selector=".functionalTestSelector" stepKey="clickWithLeftButtonKey1"/> <clickWithRightButton selector=".functionalTestSelector" stepKey="clickWithRightButtonKey1"/> + <clickWithLeftButton selector="{{SampleSection.simpleElement}}{{SampleSection.simpleElementOneParam(offset.x)}}" x="{{offset.x}}" y="{{offset.y}}" stepKey="clickWithLeftButtonKeyXY1" /> + <clickWithRightButton selector="{{SampleSection.simpleElementOneParam('4123')}}{{SampleSection.simpleElement}}" x="{{offset.x}}" y="{{offset.y}}" stepKey="clickWithRightButtonKeyXY1" /> <closeTab stepKey="closeTabKey1"/> <conditionalClick selector=".functionalTestSelector" dependentSelector=".functionalDependentTestSelector" visible="true" stepKey="conditionalClickKey1"/> <dontSee userInput="someInput" selector=".functionalTestSelector" stepKey="dontSeeKey1" /> diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 7e5ea17e0..10cd77937 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -40,7 +40,7 @@ class ActionObject const ACTION_ATTRIBUTE_URL = 'url'; const ACTION_ATTRIBUTE_SELECTOR = 'selector'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; - const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.?[\w\[\]]+}})|({{[\w]+\.[\w]+\(.+\)}})/'; + const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\(.+\)}})/'; /** * The unique identifier for the action diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 8d8498276..6853fc6f2 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -832,7 +832,7 @@ private function generateStepsPhp($actionObjects, $hookObject = false) break; case "switchToNextTab": case "switchToPreviousTab": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $this->stripWrappedQuotes($input)); + $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $input); break; case "clickWithLeftButton": case "clickWithRightButton": From af460ab66977699093926810a69a87147a846fb7 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Sat, 24 Mar 2018 15:58:55 +0200 Subject: [PATCH 19/77] magento/magento2-functional-testing-framework#60: Build interactive configuration tool for populating .env properties --- .env.example | 6 +- RoboFile.php | 1 + bin/mftf | 30 ++++++ bootstrap.php | 8 ++ .../Console/BuildProjectCommand.php | 89 +++++++++++++++++ .../Console/SetupEnvCommand.php | 64 ++++++++++++ .../Util/Env/EnvProcessor.php | 99 +++++++++++++++++++ 7 files changed, 294 insertions(+), 3 deletions(-) create mode 100755 bin/mftf create mode 100644 bootstrap.php create mode 100644 src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php create mode 100644 src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php create mode 100644 src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php diff --git a/.env.example b/.env.example index 536ea1fa5..ddecfaa34 100644 --- a/.env.example +++ b/.env.example @@ -5,9 +5,9 @@ MAGENTO_BASE_URL=http://devdocs.magento.com/ #*** Set the Admin Username and Password for your Magento instance ***# -MAGENTO_BACKEND_NAME= -MAGENTO_ADMIN_USERNAME= -MAGENTO_ADMIN_PASSWORD= +MAGENTO_BACKEND_NAME=admin +MAGENTO_ADMIN_USERNAME=admin +MAGENTO_ADMIN_PASSWORD=123123q #*** Path to CLI entry point and command parameter name. Uncomment and change if folder structure differs from standard Magento installation #MAGENTO_CLI_COMMAND_PATH=dev/tests/acceptance/utils/command.php diff --git a/RoboFile.php b/RoboFile.php index 0b891094b..fc61be61c 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -33,6 +33,7 @@ function cloneFiles() */ function buildProject() { + $this->writeln("<error>This command will be removed in MFTF v3.0.0. Please use bin/mftf build:project instead.</error>\n"); $this->cloneFiles(); $this->_exec('vendor'. DIRECTORY_SEPARATOR .'bin'. DIRECTORY_SEPARATOR .'codecept build'); } diff --git a/bin/mftf b/bin/mftf new file mode 100755 index 000000000..623e3bc61 --- /dev/null +++ b/bin/mftf @@ -0,0 +1,30 @@ +#!/usr/bin/env php + +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +if (PHP_SAPI !== 'cli') { + echo 'bin/mftf must be run as a CLI application'; + exit(1); +} + +try { + require_once __DIR__ . '/../bootstrap.php'; + $application = new Symfony\Component\Console\Application(); + $application->setName('Magento Functional Testing Framework CLI'); + $application->setVersion('1.0.0'); + $application->add(new Magento\FunctionalTestingFramework\Console\SetupEnvCommand()); + $application->add(new Magento\FunctionalTestingFramework\Console\BuildProjectCommand()); + $application->run(); +} catch (\Exception $e) { + while ($e) { + echo $e->getMessage(); + echo $e->getTraceAsString(); + echo "\n\n"; + $e = $e->getPrevious(); + } + exit(1); +} diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 000000000..7a708e8a8 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require_once 'vendor/autoload.php'; +define('BP', __DIR__); diff --git a/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php new file mode 100644 index 000000000..6e29ac071 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/BuildProjectCommand.php @@ -0,0 +1,89 @@ +<?php +// @codingStandardsIgnoreFile +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Process; +use Magento\FunctionalTestingFramework\Util\Env\EnvProcessor; + +class BuildProjectCommand extends Command +{ + /** + * Env processor manages .env files. + * + * @var \Magento\FunctionalTestingFramework\Util\Env\EnvProcessor + */ + private $envProcessor; + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + $this->setName('build:project'); + $this->setDescription('Generate configuration files for the project. Build the Codeception project.'); + $this->envProcessor = new EnvProcessor(BP . DIRECTORY_SEPARATOR . '.env'); + $env = $this->envProcessor->getEnv(); + foreach ($env as $key => $value) { + $this->addOption($key, null, InputOption::VALUE_REQUIRED, '', $value); + } + } + + /** + * Executes the current command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + * @throws \Symfony\Component\Console\Exception\LogicException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $fileSystem = new Filesystem(); + $fileSystem->copy( + BP . DIRECTORY_SEPARATOR . 'codeception.dist.yml', + BP . DIRECTORY_SEPARATOR . 'codeception.yml' + ); + $output->writeln("codeception.yml configuration successfully applied.\n"); + $fileSystem->copy( + BP . DIRECTORY_SEPARATOR . 'dev' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . + 'functional' . DIRECTORY_SEPARATOR . 'MFTF.suite.dist.yml', + BP . DIRECTORY_SEPARATOR . 'dev' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . + 'functional' . DIRECTORY_SEPARATOR . 'MFTF.suite.yml' + ); + $output->writeln("MFTF.suite.yml configuration successfully applied.\n"); + + $setupEnvCommand = new SetupEnvCommand(); + $commandInput = []; + $options = $input->getOptions(); + $env = array_keys($this->envProcessor->getEnv()); + foreach ($options as $key => $value) { + if (in_array($key, $env)) { + $commandInput['--' . $key] = $value; + } + } + $commandInput = new ArrayInput($commandInput); + $setupEnvCommand->run($commandInput, $output); + + $process = new Process('vendor/bin/codecept build'); + $process->run(); + if ($process->isSuccessful()) { + $output->writeln("Codeception build run successfully.\n"); + } + + $output->writeln('<info>The project built successfully.</info>'); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php b/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php new file mode 100644 index 000000000..e0ef6053a --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/SetupEnvCommand.php @@ -0,0 +1,64 @@ +<?php +// @codingStandardsIgnoreFile +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Exception\InvalidOptionException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\FunctionalTestingFramework\Util\Env\EnvProcessor; + +class SetupEnvCommand extends Command +{ + /** + * Env processor manages .env files. + * + * @var \Magento\FunctionalTestingFramework\Util\Env\EnvProcessor + */ + private $envProcessor; + + /** + * Configures the current command. + * + * @return void + */ + protected function configure() + { + $this->setName('setup:env'); + $this->setDescription("Generate .env file."); + $this->envProcessor = new EnvProcessor(BP . DIRECTORY_SEPARATOR . '.env'); + $env = $this->envProcessor->getEnv(); + foreach ($env as $key => $value) { + $this->addOption($key, null, InputOption::VALUE_REQUIRED, '', $value); + } + } + + /** + * Executes the current command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + * @throws \Symfony\Component\Console\Exception\LogicException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $config = $this->envProcessor->getEnv(); + $userEnv = []; + foreach ($config as $key => $value) { + if ($input->getOption($key) === '') { + throw new InvalidOptionException(sprintf("Parameter $key cannot be empty.", $key)); + } + $userEnv[$key] = $input->getOption($key); + } + $this->envProcessor->putEnvFile($userEnv); + $output->writeln(".env configuration successfully applied.\n"); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php new file mode 100644 index 000000000..7129ff091 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Env/EnvProcessor.php @@ -0,0 +1,99 @@ +<?php +// @codingStandardsIgnoreFile +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\FunctionalTestingFramework\Util\Env; + +/** + * Helper class EnvProcessor for reading and writing .env files. + * + * @package Magento\FunctionalTestingFramework\Util\Env + */ +class EnvProcessor +{ + /** + * File .env location. + * + * @var string + */ + private $envFile = ''; + + /** + * File .env.example location. + * + * @var string + */ + private $envExampleFile = ''; + + /** + * Array of environment variables form file. + * + * @var array + */ + private $env = []; + + /** + * EnvProcessor constructor. + * @param string $envFile + */ + public function __construct( + string $envFile = '' + ) { + $this->envFile = $envFile; + $this->envExampleFile = $envFile . '.example'; + } + + /** + * Serves for parsing '.env.example' file into associative array. + * + * @return array + */ + public function parseEnvFile(): array + { + $envLines = file( + $this->envExampleFile, + FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES + ); + $env = []; + foreach ($envLines as $line) { + // do not use commented out lines + if (strpos($line, '#') !== 0) { + list($key, $value) = explode('=', $line); + $env[$key] = $value; + } + } + return $env; + } + + /** + * Serves for putting array with environment variables into .env file. + * + * @param array $config + * @return void + */ + public function putEnvFile(array $config = []) + { + $envData = ''; + foreach ($config as $key => $value) { + $envData .= $key . '=' . $value . PHP_EOL; + } + file_put_contents($this->envFile, $envData); + } + + /** + * Retrieves '.env.example' file as associative array. + * + * @return array + */ + public function getEnv(): array + { + if (empty($this->env)) { + $this->env = $this->parseEnvFile(); + } + return $this->env; + } +} From bdd366d13bcf7da4a2fb6078b0d8ecc4de32407f Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Mon, 26 Mar 2018 14:59:40 +0300 Subject: [PATCH 20/77] Make possible to create and run MFTF test in framework - update robo functional method to use MFTF test suite --- RoboFile.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RoboFile.php b/RoboFile.php index 0b891094b..a085095d1 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -79,13 +79,13 @@ function generateSuite(array $args) } /** - * Run all Functional tests. + * Run all MFTF tests. * * @return void */ - function functional() + function mftf() { - $this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run functional --skip-group skip'); + $this->_exec('.' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'codecept run MFTF --skip-group skip'); } /** From c71b417e2920ab11a98144170e0df977ee9550d2 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Mon, 26 Mar 2018 16:38:29 +0300 Subject: [PATCH 21/77] MQE-882: Cannot input <data> tags in entity xml - added verification test --- dev/tests/verification/Resources/BasicFunctionalTest.txt | 2 ++ dev/tests/verification/TestModule/Data/ReplacementData.xml | 4 ++++ .../verification/TestModule/Test/BasicFunctionalTest.xml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index fc705a8c0..0732c7f10 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -60,6 +60,8 @@ class BasicFunctionalTestCest */ public function BasicFunctionalTest(AcceptanceTester $I) { + $I->comment(""); + $I->comment(""); $someVarDefinition = $I->grabValueFrom(); $I->acceptPopup(); $I->amOnPage("/test/url"); diff --git a/dev/tests/verification/TestModule/Data/ReplacementData.xml b/dev/tests/verification/TestModule/Data/ReplacementData.xml index 0b327689c..4d91fa84e 100644 --- a/dev/tests/verification/TestModule/Data/ReplacementData.xml +++ b/dev/tests/verification/TestModule/Data/ReplacementData.xml @@ -30,4 +30,8 @@ <data key="x">200</data> <data key="y">300</data> </entity> + <entity name="emptyData" type="backend"> + <data key="noData"></data> + <data key="definitelyNoData"/> + </entity> </entities> diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index 04181382d..77122d42f 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -22,6 +22,8 @@ <after> <amOnPage url="/afterUrl" stepKey="afterAmOnPageKey"/> </after> + <comment stepKey="basicCommentWithNoData" userInput="{{emptyData.noData}}"/> + <comment stepKey="basicCommentWithDefinitelyNoData" userInput="{{emptyData.definitelyNoData}}"/> <grabValueFrom stepKey="someVarDefinition"/> <acceptPopup stepKey="acceptPopupKey1"/> <amOnPage stepKey="amOnPageKey1" url="/test/url"/> From 6ab66f33532be046c876507da3e055d0a8f9348b Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Wed, 28 Mar 2018 17:23:26 +0300 Subject: [PATCH 22/77] MQE-830: Output error details about test failure - Added new extension to allow for pulling and operation on testContext at test runtime - Tests now no longer allow for exceptions in _after block to overwrite the original exception (case when test fails, runs _after and fails there too) - Tests no longer run the _after block if the test failure was in the _after block to begin --- .../ActionGroupWithDataOverrideTest.txt | 1 - .../Resources/ActionGroupWithDataTest.txt | 1 - .../ActionGroupWithNoDefaultTest.txt | 1 - .../ActionGroupWithPersistedData.txt | 1 - .../ActionGroupWithTopLevelPersistedData.txt | 1 - .../ArgumentWithSameNameAsElement.txt | 1 - .../Resources/BasicFunctionalTest.txt | 1 - .../verification/Resources/BasicMergeTest.txt | 1 - .../Resources/HookActionsTest.txt | 1 - .../Resources/MergedActionGroupTest.txt | 1 - .../Resources/MergedReferencesTest.txt | 1 - .../Resources/MultipleActionGroupsTest.txt | 1 - .../Extension/TestContextExtension.php | 71 +++++++++++++++++++ .../Util/TestGenerator.php | 7 +- 14 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php diff --git a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt index 75bbfeb93..0809289b3 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataOverrideTest.txt @@ -57,7 +57,6 @@ class ActionGroupWithDataOverrideTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt index 74e571714..fe3cf57c1 100644 --- a/dev/tests/verification/Resources/ActionGroupWithDataTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithDataTest.txt @@ -57,7 +57,6 @@ class ActionGroupWithDataTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt index 2eee844b3..5cc1d7f39 100644 --- a/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt +++ b/dev/tests/verification/Resources/ActionGroupWithNoDefaultTest.txt @@ -57,7 +57,6 @@ class ActionGroupWithNoDefaultTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt index 45eca0bee..2707770d5 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPersistedData.txt @@ -57,7 +57,6 @@ class ActionGroupWithPersistedDataCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt index 04c70911b..743864b79 100644 --- a/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt +++ b/dev/tests/verification/Resources/ActionGroupWithTopLevelPersistedData.txt @@ -57,7 +57,6 @@ class ActionGroupWithTopLevelPersistedDataCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt index 26ea8178e..bb59a6758 100644 --- a/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt +++ b/dev/tests/verification/Resources/ArgumentWithSameNameAsElement.txt @@ -57,7 +57,6 @@ class ArgumentWithSameNameAsElementCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index ff331f1fe..289a62e3f 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -46,7 +46,6 @@ class BasicFunctionalTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/BasicMergeTest.txt b/dev/tests/verification/Resources/BasicMergeTest.txt index e199f2c55..2cecb803f 100644 --- a/dev/tests/verification/Resources/BasicMergeTest.txt +++ b/dev/tests/verification/Resources/BasicMergeTest.txt @@ -48,7 +48,6 @@ class BasicMergeTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/HookActionsTest.txt b/dev/tests/verification/Resources/HookActionsTest.txt index 4a49e8e9d..2e9eb609b 100644 --- a/dev/tests/verification/Resources/HookActionsTest.txt +++ b/dev/tests/verification/Resources/HookActionsTest.txt @@ -73,7 +73,6 @@ class HookActionsTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/MergedActionGroupTest.txt b/dev/tests/verification/Resources/MergedActionGroupTest.txt index 055d8dbe3..ab9b46f8d 100644 --- a/dev/tests/verification/Resources/MergedActionGroupTest.txt +++ b/dev/tests/verification/Resources/MergedActionGroupTest.txt @@ -57,7 +57,6 @@ class MergedActionGroupTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/MergedReferencesTest.txt b/dev/tests/verification/Resources/MergedReferencesTest.txt index 2e42b6587..519db4314 100644 --- a/dev/tests/verification/Resources/MergedReferencesTest.txt +++ b/dev/tests/verification/Resources/MergedReferencesTest.txt @@ -46,7 +46,6 @@ class MergedReferencesTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt index 3946495ed..d9b32bfce 100644 --- a/dev/tests/verification/Resources/MultipleActionGroupsTest.txt +++ b/dev/tests/verification/Resources/MultipleActionGroupsTest.txt @@ -57,7 +57,6 @@ class MultipleActionGroupsTestCest public function _failed(AcceptanceTester $I) { $I->saveScreenshot(); - $this->_after($I); } /** diff --git a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php new file mode 100644 index 000000000..a518c9234 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Extension; + +use \Codeception\Events; + +/** + * Class TestContextExtension + * @SuppressWarnings(PHPMD.UnusedPrivateField) + */ +class TestContextExtension extends \Codeception\Extension +{ + const TEST_PHASE_AFTER = "_after"; + + /** + * Codeception Events Mapping to methods + * @var array + */ + public static $events = [ + Events::TEST_FAIL => 'testFail', + ]; + + /** + * Codeception event listener function, triggered on test failure. + * @param \Codeception\Event\FailEvent $e + * @return void + */ + public function testFail(\Codeception\Event\FailEvent $e) + { + $cest = $e->getTest(); + $context = $this->extractContext($e->getFail()->getTrace(), $cest->getTestMethod()); + // 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. + if ($context != TestContextExtension::TEST_PHASE_AFTER) { + try { + $actorClass = $e->getTest()->getMetadata()->getCurrent('actor'); + $I = new $actorClass($cest->getScenario()); + call_user_func(\Closure::bind( + function () use ($cest, $I) { + $cest->executeHook($I, 'after'); + }, + null, + $cest + )); + } catch (\Exception $e) { + // Do not rethrow Exception + } + } + } + + /** + * Extracts hook method from trace, looking specifically for the cest class given. + * @param array $trace + * @param string $class + * @return string + */ + public function extractContext($trace, $class) + { + foreach ($trace as $entry) { + $traceClass = $entry["class"] ?? null; + if (strpos($traceClass, $class) != 0) { + return $entry["function"]; + } + } + return null; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 6f4a0b643..c57d7e2c8 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1373,6 +1373,7 @@ private function stripAndSplitReference($reference, $delimiter) * @return string * @throws TestReferenceException * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function generateHooksPhp($hookObjects) { @@ -1414,12 +1415,6 @@ private function generateHooksPhp($hookObjects) throw new TestReferenceException($e->getMessage() . " in Element \"" . $type . "\""); } - if ($hookObject->getType() == TestObjectExtractor::TEST_FAILED_HOOK) { - $steps.="\t\t"; - $steps.='$this->_after($I);'; - $steps.="\n"; - } - $hooks .= sprintf("\tpublic function _{$type}(%s)\n", $dependencies); $hooks .= "\t{\n"; $hooks .= $steps; From dcd1eca241627b2fd23206d8c0dc22754908b149 Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Wed, 28 Mar 2018 14:19:59 -0500 Subject: [PATCH 23/77] MQE-783: Allure reporting Spike - semi working solution --- .../Allure/Adapter/MagentoAllureAdapter.php | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php new file mode 100644 index 000000000..912309b55 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Allure\Adapter; + +use Yandex\Allure\Adapter\AllureAdapter; +use Yandex\Allure\Adapter\Annotation; +use Yandex\Allure\Adapter\Event\TestSuiteEvent; +use Yandex\Allure\Adapter\Event\TestSuiteStartedEvent; +use Codeception\Event\SuiteEvent; + +/** + * Class MagentoAllureAdapter + * + * Extends AllureAdapter to provide further information for allure reports + * + * @package Magento\FunctionalTestingFramework\Allure + */ + +class MagentoAllureAdapter extends AllureAdapter +{ + /** + * Variable name used for extracting group argument to codecept run commaned + * + * @var string + */ + protected $groupKey = "groups"; + + /** + * Array of group values from test runner command to append to allure suitename + * + * @var array + */ + protected $groups; + + /** + * Initialize from parent with group value + * + * @params array $ignoredAnnotations + * @return AllureAdapter + */ + public function _initialize(array $ignoredAnnotations = []) + { + $this->groups = $this->getGroup($this->groupKey); + parent::_initialize($ignoredAnnotations); + } + + /** + * Array of group values passed to test runner command + * + * @param String $groupKey + * @return array + */ + private function getGroup($groupKey) + { + $groups = $this->options[$groupKey]; + return $groups; + } + + /** + * Override of parent method to set suitename as suitename and group name concatenated + * + * @param SuiteEvent $suiteEvent + * @return void + */ + public function suiteBefore(SuiteEvent $suiteEvent) + { + $suite = $suiteEvent->getSuite(); + $group = implode(".", $this->groups); + $suiteName = ($suite->getName())."-{$group}"; + //cant access protected property of suiteEvent + //$suiteEvent->suite['name'] = $suiteName; + + call_user_func(\Closure::bind( + function () use ($suite, $suiteName) { + $suite->name = $suiteName; + }, + null, + $suite + )); + + $event = new TestSuiteStartedEvent($suiteName); + if (class_exists($suiteName, false)) { + $annotationManager = new Annotation\AnnotationManager( + Annotation\AnnotationProvider::getClassAnnotations($suiteName) + ); + $annotationManager->updateTestSuiteEvent($event); + } + $this->uuid = $event->getUuid(); + $this->getLifecycle()->fire($event); + // unset($event); + } + + + public function suiteAfter() +} + From 78bc14425ad17a7dfb54ef58129933104bef1eab Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Wed, 28 Mar 2018 15:52:20 -0500 Subject: [PATCH 24/77] MQE-783 working prototype --- .../Allure/Adapter/MagentoAllureAdapter.php | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index 912309b55..c551cad84 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -21,6 +21,8 @@ class MagentoAllureAdapter extends AllureAdapter { + private $uuid; + /** * Variable name used for extracting group argument to codecept run commaned * @@ -81,19 +83,41 @@ function () use ($suite, $suiteName) { $suite )); - $event = new TestSuiteStartedEvent($suiteName); - if (class_exists($suiteName, false)) { - $annotationManager = new Annotation\AnnotationManager( - Annotation\AnnotationProvider::getClassAnnotations($suiteName) - ); - $annotationManager->updateTestSuiteEvent($event); - } - $this->uuid = $event->getUuid(); - $this->getLifecycle()->fire($event); - // unset($event); + //change suiteEvent + $changeSuiteEvent = new SuiteEvent( + $suiteEvent->getSuite(), + $suiteEvent->getResult(), + $suiteEvent->getSettings() + ); + + // call parent function + parent::suiteBefore($changeSuiteEvent); +// +// $event = new TestSuiteStartedEvent($suiteName); +// if (class_exists($suiteName, false)) { +// $annotationManager = new Annotation\AnnotationManager( +// Annotation\AnnotationProvider::getClassAnnotations($suiteName) +// ); +// $annotationManager->updateTestSuiteEvent($event); +// } +// +//// $uuid = ""; +// $uuid = call_user_func(\Closure::bind( +// function () use ($uuid, $event) { +// return $this->uuid; +// }, +// null, +// $this +// )); +//// $this->uuid = $uuid; +// +// $this->uuid = $event->getUuid(); +// +// +// $this->getLifecycle()->fire($event); +// // unset($event); } - public function suiteAfter() } From 856aa5eae10289008a8982c2e4fe87e31d2c187d Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Thu, 29 Mar 2018 09:49:10 -0500 Subject: [PATCH 25/77] MQE-783: removing comments and whitespace --- .../Allure/Adapter/MagentoAllureAdapter.php | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index c551cad84..f84e202ce 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -6,9 +6,6 @@ namespace Magento\FunctionalTestingFramework\Allure\Adapter; use Yandex\Allure\Adapter\AllureAdapter; -use Yandex\Allure\Adapter\Annotation; -use Yandex\Allure\Adapter\Event\TestSuiteEvent; -use Yandex\Allure\Adapter\Event\TestSuiteStartedEvent; use Codeception\Event\SuiteEvent; /** @@ -21,8 +18,6 @@ class MagentoAllureAdapter extends AllureAdapter { - private $uuid; - /** * Variable name used for extracting group argument to codecept run commaned * @@ -72,8 +67,6 @@ public function suiteBefore(SuiteEvent $suiteEvent) $suite = $suiteEvent->getSuite(); $group = implode(".", $this->groups); $suiteName = ($suite->getName())."-{$group}"; - //cant access protected property of suiteEvent - //$suiteEvent->suite['name'] = $suiteName; call_user_func(\Closure::bind( function () use ($suite, $suiteName) { @@ -92,30 +85,6 @@ function () use ($suite, $suiteName) { // call parent function parent::suiteBefore($changeSuiteEvent); -// -// $event = new TestSuiteStartedEvent($suiteName); -// if (class_exists($suiteName, false)) { -// $annotationManager = new Annotation\AnnotationManager( -// Annotation\AnnotationProvider::getClassAnnotations($suiteName) -// ); -// $annotationManager->updateTestSuiteEvent($event); -// } -// -//// $uuid = ""; -// $uuid = call_user_func(\Closure::bind( -// function () use ($uuid, $event) { -// return $this->uuid; -// }, -// null, -// $this -// )); -//// $this->uuid = $uuid; -// -// $this->uuid = $event->getUuid(); -// -// -// $this->getLifecycle()->fire($event); -// // unset($event); } From 07eae39b3e14ef01a6bc6b328ee5cbdab0b578e0 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 29 Mar 2018 10:55:19 -0500 Subject: [PATCH 26/77] MQE-778: robo generate:tests generates all tests and suites by default - add new generate all method to SuiteGenerator - modify TestManifest to only clear once - introduce argument to exclude tests during generation --- .../Tests/SuiteGenerationTest.php | 56 +++++++------ .../Suite/SuiteGenerator.php | 82 +++++++++++++++++-- .../Suite/Util/SuiteObjectExtractor.php | 6 ++ .../Util/Filesystem/DirSetupUtil.php | 13 +++ .../Util/Manifest/DefaultTestManifest.php | 33 +++++++- .../Util/Manifest/SingleRunTestManifest.php | 2 +- .../Util/TestGenerator.php | 9 +- 7 files changed, 165 insertions(+), 36 deletions(-) diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index fc9897f9c..10049484e 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -17,13 +17,6 @@ class SuiteGenerationTest extends MftfTestCase const RESOURCES_DIR = TESTS_BP . DIRECTORY_SEPARATOR . 'verification' . DIRECTORY_SEPARATOR . 'Resources'; const CONFIG_YML_FILE = FW_BP . DIRECTORY_SEPARATOR . SuiteGenerator::YAML_CODECEPTION_CONFIG_FILENAME; - const MANIFEST_RESULTS = [ - 'IncludeTest2Cest.php', - 'additionalTestCest.php', - 'IncludeTestCest.php', - 'additionalIncludeTest2Cest.php' - ]; - /** * Flag to track existence of config.yml file * @@ -48,6 +41,11 @@ public static function setUpBeforeClass() return; } + // destroy manifest file if it exists + if (file_exists(self::getManifestFilePath())) { + unlink(self::getManifestFilePath()); + } + $configYml = fopen(self::CONFIG_YML_FILE, "w"); fclose($configYml); } @@ -59,6 +57,13 @@ public function testSuiteGeneration1() { $groupName = 'functionalSuite1'; + $expectedContents = [ + 'additionalTestCest.php', + 'additionalIncludeTest2Cest.php', + 'IncludeTest2Cest.php', + 'IncludeTestCest.php' + ]; + // Generate the Suite SuiteGenerator::getInstance()->generateSuite($groupName); @@ -79,23 +84,10 @@ public function testSuiteGeneration1() $groupName . DIRECTORY_SEPARATOR; - // Validate test manifest contents - $actualManifest = dirname($suiteResultBaseDir). DIRECTORY_SEPARATOR . 'testManifest.txt'; - $actualTestReferences = explode(PHP_EOL, file_get_contents($actualManifest)); - - for ($i = 0; $i < count($actualTestReferences); $i++) { - if (empty($actualTestReferences[$i])) { - continue; - } - - $this->assertStringEndsWith(self::MANIFEST_RESULTS[$i], $actualTestReferences[$i]); - $this->assertNotFalse(strpos($actualTestReferences[$i], $groupName)); - } - - // Validate expected php files exist - foreach (self::MANIFEST_RESULTS as $expectedTestReference) { - $cestName = explode(":", $expectedTestReference, 2); - $this->assertFileExists($suiteResultBaseDir . $cestName[0]); + // Validate tests have been generated + $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + foreach ($expectedContents as $expectedFile) { + $this->assertTrue(in_array($expectedFile, $dirContents)); } } @@ -117,4 +109,20 @@ public static function tearDownAfterClass() unlink(self::CONFIG_YML_FILE); } + + /** + * Getter for manifest file path + * + * @return string + */ + private static function getManifestFilePath() + { + return TESTS_BP . + DIRECTORY_SEPARATOR . + "verification" . + DIRECTORY_SEPARATOR . + "_generated" . + DIRECTORY_SEPARATOR . + 'testManifest.txt'; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index 0d52bbf0a..a3c664fb0 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -32,6 +32,13 @@ class SuiteGenerator */ private static $SUITE_GENERATOR_INSTANCE; + /** + * Boolean to track whether we have already cleared the yaml file. + * + * @var bool + */ + private $ymlFileCleared = false; + /** * Group Class Generator initialized in constructor. * @@ -61,14 +68,35 @@ public static function getInstance() return self::$SUITE_GENERATOR_INSTANCE; } + /** + * Function which takes all suite configurations and generates to appropriate directory, updating yml configuration + * as needed. Returns an array of all tests generated keyed by test name. + * + * @param string $config + * @return array + */ + public function generateAllSuites($config) + { + $testsReferencedInSuites = []; + $suites = SuiteObjectHandler::getInstance()->getAllObjects(); + foreach ($suites as $suite) { + /** @var SuiteObject $suite */ + $testsReferencedInSuites = array_merge($testsReferencedInSuites, $suite->getTests()); + $this->generateSuite($suite->getName(), $config); + } + + return $testsReferencedInSuites; + } + /** * Function which takes a suite name and generates corresponding dir, test files, group class, and updates * yml configuration for group run. * * @param string $suiteName + * @param string $config * @return void */ - public function generateSuite($suiteName) + public function generateSuite($suiteName, $config = null) { /**@var SuiteObject $suite **/ $suite = SuiteObjectHandler::getInstance()->getObject($suiteName); @@ -77,7 +105,7 @@ public function generateSuite($suiteName) $groupNamespace = null; DirSetupUtil::createGroupDir($fullPath); - $this->generateRelevantGroupTests($suiteName, $suite->getTests()); + $this->generateRelevantGroupTests($suiteName, $suite->getTests(), $config); if ($suite->requiresGroupFile()) { // if the suite requires a group file, generate it and set the namespace @@ -117,17 +145,56 @@ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) $ymlArray[self::YAML_GROUPS_TAG]= []; } - $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; + $ymlArray = $this->clearPreviousSessionConfigEntries($ymlArray); - if ($groupNamespace && - !in_array($groupNamespace, $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG])) { + if ($groupNamespace) { $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace; } + $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10); file_put_contents($configYmlFile, $ymlText); } + /** + * Function which takes the current config.yml array and clears any previous configuration for suite group object + * files. + * + * @param array $ymlArray + * @return array + */ + private function clearPreviousSessionConfigEntries($ymlArray) + { + if ($this->ymlFileCleared) { + return $ymlArray; + } + + $newYmlArray = $ymlArray; + // if the yaml entries haven't already been cleared + + if (array_key_exists(self::YAML_EXTENSIONS_TAG, $ymlArray)) { + foreach ($ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] as $key => $entry) { + if (preg_match('/(Group\\\\.*)/', $entry)) { + unset($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][$key]); + } + + } + + // needed for proper yml file generation based on indices + $newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] = + array_values($newYmlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG]); + + } + + if (array_key_exists(self::YAML_GROUPS_TAG, $newYmlArray)) { + unset($newYmlArray[self::YAML_GROUPS_TAG]); + } + + $this->ymlFileCleared = true; + + return $newYmlArray; + } + /** * Function which takes a string which is the desired output directory (under _generated) and an array of tests * relevant to the suite to be generated. The function takes this information and creates a new instance of the test @@ -135,11 +202,12 @@ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) * * @param string $path * @param array $tests + * @param string $config * @return void */ - private function generateRelevantGroupTests($path, $tests) + private function generateRelevantGroupTests($path, $tests, $config) { $testGenerator = TestGenerator::getInstance($path, $tests); - $testGenerator->createAllTestFiles(); + $testGenerator->createAllTestFiles($config); } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 16d65630c..56e50982a 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -44,6 +44,12 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) { $suiteObjects = []; $testHookObjectExtractor = new TestHookObjectExtractor(); + + // make sure there are suites defined before trying to parse as objects. + if (!array_key_exists(self::SUITE_ROOT_TAG, $parsedSuiteData)) { + return $suiteObjects; + } + foreach ($parsedSuiteData[self::SUITE_ROOT_TAG] as $parsedSuite) { if (!is_array($parsedSuite)) { // skip non array items parsed from suite (suite objects will always be arrays) diff --git a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php index 32c03966e..4207a2c8c 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/Filesystem/DirSetupUtil.php @@ -11,6 +11,13 @@ class DirSetupUtil { + /** + * Array which will track any previously cleared directories, to prevent any unintended removal. + * + * @var array + */ + private static $DIR_CONTEXT = []; + /** * Method used to clean export dir if needed and create new empty export dir. * @@ -19,11 +26,17 @@ class DirSetupUtil */ public static function createGroupDir($fullPath) { + // make sure we haven't already cleaned up this directory at any point before deletion + if (in_array($fullPath, self::$DIR_CONTEXT)) { + return; + } + if (file_exists($fullPath)) { self::rmDirRecursive($fullPath); } mkdir($fullPath, 0777, true); + self::$DIR_CONTEXT[] = $fullPath; } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index bcfa877ca..369de08e6 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; class DefaultTestManifest extends BaseTestManifest { @@ -19,6 +20,13 @@ class DefaultTestManifest extends BaseTestManifest */ protected $manifestPath; + /** + * A static array to track which test manifests have been cleared to prevent overwriting during generation. + * + * @var array + */ + private static $CLEARED_MANIFESTS = []; + /** * An array containing all test names for output. * @@ -34,8 +42,9 @@ class DefaultTestManifest extends BaseTestManifest public function __construct($manifestPath, $testPath) { $this->manifestPath = $manifestPath . DIRECTORY_SEPARATOR . 'testManifest.txt'; + $this->cleanManifest($this->manifestPath); parent::__construct($testPath, self::DEFAULT_CONFIG); - $fileResource = fopen($this->manifestPath, 'w'); + $fileResource = fopen($this->manifestPath, 'a'); fclose($fileResource); } @@ -67,4 +76,26 @@ public function generate($nodes = null) fclose($fileResource); } + + /** + * Function which checks the path for an existing test manifest and clears if the file has not already been cleared + * during current runtime. + * + * @param string $path + * @return void + */ + private function cleanManifest($path) + { + // if we have already cleared the file then simply return + if (in_array($path, self::$CLEARED_MANIFESTS)) { + return; + } + + // if the file exists remove + if (file_exists($path)) { + unlink($path); + } + + self::$CLEARED_MANIFESTS[] = $path; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php index 2896f857f..5fc492e9e 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php @@ -19,7 +19,7 @@ public function __construct($manifestPath, $testPath) { parent::__construct($manifestPath, $testPath); $this->runTypeConfig = self::SINGLE_RUN_CONFIG; - $fileResource = fopen($this->manifestPath, 'w'); + $fileResource = fopen($this->manifestPath, 'a'); fclose($fileResource); } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index c57d7e2c8..eb23ddd42 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -135,11 +135,12 @@ private function createCestFile($testPhp, $filename) * * @param string $runConfig * @param int $nodes + * @param TestObject[] $testsToIgnore * @return void * @throws TestReferenceException * @throws \Exception */ - public function createAllTestFiles($runConfig = null, $nodes = null) + public function createAllTestFiles($runConfig = null, $nodes = null, $testsToIgnore = []) { DirSetupUtil::createGroupDir($this->exportDirectory); @@ -149,8 +150,8 @@ public function createAllTestFiles($runConfig = null, $nodes = null) $this->exportDirectory, $runConfig ); - $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes); + $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes, $testsToIgnore); foreach ($testPhpArray as $testPhpFile) { $this->createCestFile($testPhpFile[1], $testPhpFile[0]); } @@ -196,14 +197,16 @@ private function assembleTestPhp($testObject) * * @param BaseTestManifest $testManifest * @param int $nodes + * @param TestObject[] $testsToIgnore * @return array * @throws TestReferenceException * @throws \Exception */ - private function assembleAllTestPhp($testManifest, $nodes) + private function assembleAllTestPhp($testManifest, $nodes, $testsToIgnore) { /** @var TestObject[] $testObjects */ $testObjects = $this->loadAllTestObjects(); + $testObjects = array_diff_key($testObjects, $testsToIgnore); $cestPhpArray = []; foreach ($testObjects as $test) { From 12ca5e4effb3786f786b87084a1aa90dd91c557a Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Thu, 29 Mar 2018 19:29:06 +0300 Subject: [PATCH 27/77] MQE-904: Deliver changes from Magento Contribution Day --- RoboFile.php | 2 +- .../Util/TestGenerator.php | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/RoboFile.php b/RoboFile.php index 2f5c7988d..db3d4de40 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -54,7 +54,7 @@ function generateTests($opts = ['config' => null, 'force' => true, 'nodes' => nu require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance() - ->createAllTestFiles($opts['config'], $opts['nodes'], $opts['debug'], $this->getOutput()); + ->createAllTestFiles($opts['config'], $opts['nodes'], $opts['debug']); $this->say("Generate Tests Command Run"); } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 0fb54a992..0e73b8a4d 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -20,8 +20,6 @@ use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; -use Robo\Common\IO; -use Symfony\Component\Console\Output\OutputInterface; /** * Class TestGenerator @@ -29,8 +27,6 @@ */ class TestGenerator { - use IO; - const REQUIRED_ENTITY_REFERENCE = 'createDataKey'; const GENERATED_DIR = '_generated'; @@ -55,6 +51,13 @@ class TestGenerator */ private $tests; + /** + * Symfony console output interface. + * + * @var \Symfony\Component\Console\Output\ConsoleOutput + */ + private $consoleOutput; + /** * TestGenerator constructor. * @@ -70,6 +73,7 @@ private function __construct($exportDir, $tests) DIRECTORY_SEPARATOR ); $this->tests = $tests; + $this->consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput(); } /** @@ -137,20 +141,19 @@ private function createCestFile($testPhp, $filename) * @param string $runConfig * @param int $nodes * @param bool $debug - * @param OutputInterface|null $output * @return void * @throws TestReferenceException * @throws \Exception */ - public function createAllTestFiles($runConfig = null, $nodes = null, $debug = false, $output = null) + public function createAllTestFiles($runConfig = null, $nodes = null, $debug = false) { DirSetupUtil::createGroupDir($this->exportDirectory); - if ($output !== null) { - $this->setOutput($output); - } - // create our manifest file here - $testManifest = TestManifestFactory::makeManifest($this->exportDirectory, $runConfig); + $testManifest = TestManifestFactory::makeManifest( + dirname($this->exportDirectory), + $this->exportDirectory, + $runConfig + ); $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes, $debug); foreach ($testPhpArray as $testPhpFile) { @@ -219,7 +222,7 @@ private function assembleAllTestPhp($testManifest, $nodes, $debug = false) $debugInformation = $test->getDebugInformation(); $this->debug($debugInformation, $debug); - $this->debug('Finish creating test ' . $test->getCodeceptionName(), $debug); + $this->debug('Finish creating test ' . $test->getCodeceptionName() . PHP_EOL, $debug); } $testManifest->generate($nodes); @@ -239,7 +242,7 @@ private function debug($messages, $debug = false) if ($debug && $messages) { $messages = (array) $messages; foreach ($messages as $message) { - $this->say($message); + $this->consoleOutput->writeln($message); } } } From a34cbd1c9e48436e2ed672e39a998c34d0303e91 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Fri, 30 Mar 2018 19:19:25 +0300 Subject: [PATCH 28/77] MQE-904: Deliver changes from Contribution day --- .../Util/TestGenerator.php | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 92f4d7aef..6464e359f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -194,7 +194,7 @@ private function assembleTestPhp($testObject) $hookPhp = $this->generateHooksPhp($testObject->getHooks()); $testsPhp = $this->generateTestPhp($testObject); } catch (TestReferenceException $e) { - throw new TestReferenceException($e->getMessage(). " in Test \"" . $testObject->getName() . "\""); + throw new TestReferenceException($e->getMessage() . " in Test \"" . $testObject->getName() . "\""); } $cestPhp = "<?php\n"; @@ -252,7 +252,7 @@ private function assembleAllTestPhp($testManifest, $nodes, array $testsToIgnore) private function debug($messages) { if ($this->debug && $messages) { - $messages = (array) $messages; + $messages = (array)$messages; foreach ($messages as $message) { $this->consoleOutput->writeln($message); } @@ -316,9 +316,9 @@ private function generateAnnotationsPhp($annotationsObject, $isMethod = false) continue; } if (!$isMethod) { - $annotationsPhp.= $this->generateClassAnnotations($annotationType, $annotationName); + $annotationsPhp .= $this->generateClassAnnotations($annotationType, $annotationName); } else { - $annotationsPhp.= $this->generateMethodAnnotations($annotationType, $annotationName); + $annotationsPhp .= $this->generateMethodAnnotations($annotationType, $annotationName); } } @@ -544,10 +544,9 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " // validate the param array is in the correct format $this->validateParameterArray($customActionAttributes['parameterArray']); - $parameterArray = "[" . $this->addUniquenessToParamArray( - $customActionAttributes['parameterArray'] - ) - . "]"; + $parameterArray = "["; + $parameterArray .= $this->addUniquenessToParamArray($customActionAttributes['parameterArray']); + $parameterArray .= "]"; } if (isset($customActionAttributes['requiredAction'])) { @@ -1186,12 +1185,9 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; - $argRef= "\t\t\$" . str_replace( - ucfirst($fieldKey), - "", - $stepKey - ) . "Fields['{$fieldKey}'] = ${input};\n"; - $testSteps.= $this->resolveTestVariable($argRef, [$input], $actionObject->getActionOrigin()); + $argRef = "\t\t\$"; + $argRef .= str_replace(ucfirst($fieldKey), "", $stepKey) . "Fields['{$fieldKey}'] = ${input};\n"; + $testSteps .= $this->resolveTestVariable($argRef, [$input], $actionObject->getActionOrigin()); break; default: $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $input, $parameter); @@ -1804,10 +1800,11 @@ private function stripQuotes($inStr) private function validateXmlAttributesMutuallyExclusive($key, $tagName, $attributes) { $rules = [ - ['attributes' => [ - 'selector', - 'selectorArray', - ] + [ + 'attributes' => [ + 'selector', + 'selectorArray', + ] ], [ 'attributes' => [ From 2109b0d3f8d3de3143ecf629bc346ba5679e8363 Mon Sep 17 00:00:00 2001 From: Alex Kolesnyk <okolesnyk@magento.com> Date: Fri, 30 Mar 2018 19:44:54 +0300 Subject: [PATCH 29/77] MQE-904: Deliver changes from Contribution day --- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 6464e359f..07f069c10 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -23,7 +23,7 @@ /** * Class TestGenerator - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD) */ class TestGenerator { From 5c426c89bc19aaa737efefcf7a27afdd03686719 Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Mon, 2 Apr 2018 09:38:18 -0500 Subject: [PATCH 30/77] MQE-783: [SPIKE] Investigate allure reporting when tests run in multiple suites - Cleaned up static check failures --- .../Allure/Adapter/MagentoAllureAdapter.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index f84e202ce..343b1f6fc 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -35,14 +35,17 @@ class MagentoAllureAdapter extends AllureAdapter /** * Initialize from parent with group value * - * @params array $ignoredAnnotations - * @return AllureAdapter + * @param array $ignoredAnnotations + * @return void */ + + // @codingStandardsIgnoreStart public function _initialize(array $ignoredAnnotations = []) { $this->groups = $this->getGroup($this->groupKey); parent::_initialize($ignoredAnnotations); } + // @codingStandardsIgnoreEnd /** * Array of group values passed to test runner command @@ -86,7 +89,4 @@ function () use ($suite, $suiteName) { // call parent function parent::suiteBefore($changeSuiteEvent); } - - } - From 0794a741a58eb4388fa162560e89e7b70b13e7a8 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 2 Apr 2018 23:39:03 +0300 Subject: [PATCH 31/77] MQE-768: Framework cannot depend on relative file structure to execute tests - MFTF and MFTF Tests can now interact with one another properly even if installed via composer. - Pathing and anchoring is now done via composer autoload file checking. --- dev/tests/_bootstrap.php | 1 + .../FunctionalTestingFramework/Util/ModuleResolver.php | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index d7e72c8f3..981da5b8b 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -42,6 +42,7 @@ defined('TESTS_BP') || define('TESTS_BP', __DIR__); defined('TESTS_MODULE_PATH') || define('TESTS_MODULE_PATH', TESTS_BP . $RELATIVE_TESTS_MODULE_PATH); +defined('MAGENTO_BP') || define('MAGENTO_BP', __DIR__); $utilDir = DIRECTORY_SEPARATOR . 'Util'. DIRECTORY_SEPARATOR . '*.php'; diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index b6b83870d..324e206ce 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -223,9 +223,8 @@ private function aggregateTestModulePaths() { $allModulePaths = []; - // TODO update these paths when we switch a composer based pathing // Define the Module paths from app/code - $appCodePath = dirname(dirname(dirname(PROJECT_ROOT))) + $appCodePath = MAGENTO_BP . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . 'code' . DIRECTORY_SEPARATOR; @@ -234,7 +233,7 @@ private function aggregateTestModulePaths() $modulePath = defined('TESTS_MODULE_PATH') ? TESTS_MODULE_PATH : TESTS_BP; // Define the Module paths from vendor modules - $vendorCodePath = dirname(dirname(dirname(PROJECT_ROOT))) + $vendorCodePath = PROJECT_ROOT . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR; From 48c5585516a544138caaf2ff69251e6699e3e5e4 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Tue, 3 Apr 2018 16:35:54 +0300 Subject: [PATCH 32/77] MQE-818: Enable executeJS to save a variable - executeJS now assigns a variable for storage. --- dev/tests/verification/Resources/BasicFunctionalTest.txt | 2 +- .../FunctionalTestingFramework/Util/TestGenerator.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 289a62e3f..c500628b8 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -91,7 +91,7 @@ class BasicFunctionalTestCest $I->dontSeeOptionIsSelected(".functionalTestSelector", "someInput"); $I->doubleClick(".functionalTestSelector"); $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2"); - $I->executeJS("someJSFunction"); + $executeJSKey1 = $I->executeJS("someJSFunction"); $I->fillField(".functionalTestSelector", "someInput"); $I->fillField(".functionalTestSelector", "0"); $grabAttributeFromKey1 = $I->grabAttributeFrom(".functionalTestSelector", "someInput"); diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 07f069c10..4a51906c0 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -959,7 +959,12 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $function); break; case "executeJS": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $function); + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, + $actor, + $actionObject, + $function + ); break; case "performOn": case "waitForElementChange": From d48a35bd5e781b8dee8d6df2e326c182702db113 Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Tue, 3 Apr 2018 09:52:40 -0500 Subject: [PATCH 33/77] MQE-783: [SPIKE] Investigate allure reporting when tests run in multiple suites - Added null checking for test runs without group argument - Refactored suiteBefore in light of the above --- .../Allure/Adapter/MagentoAllureAdapter.php | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index 343b1f6fc..8ecfcb3e3 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -5,6 +5,7 @@ */ namespace Magento\FunctionalTestingFramework\Allure\Adapter; +use Magento\FunctionalTestingFramework\Data\Argument\Interpreter\NullType; use Yandex\Allure\Adapter\AllureAdapter; use Codeception\Event\SuiteEvent; @@ -30,7 +31,7 @@ class MagentoAllureAdapter extends AllureAdapter * * @var array */ - protected $groups; + protected $group; /** * Initialize from parent with group value @@ -42,7 +43,7 @@ class MagentoAllureAdapter extends AllureAdapter // @codingStandardsIgnoreStart public function _initialize(array $ignoredAnnotations = []) { - $this->groups = $this->getGroup($this->groupKey); + $this->group = $this->getGroup($this->groupKey); parent::_initialize($ignoredAnnotations); } // @codingStandardsIgnoreEnd @@ -51,12 +52,14 @@ public function _initialize(array $ignoredAnnotations = []) * Array of group values passed to test runner command * * @param String $groupKey - * @return array + * @return String */ private function getGroup($groupKey) { - $groups = $this->options[$groupKey]; - return $groups; + if(!($this->options[$groupKey] == Null)){ + return $this->options[$groupKey][0]; + } + return null; } /** @@ -67,25 +70,27 @@ private function getGroup($groupKey) */ public function suiteBefore(SuiteEvent $suiteEvent) { - $suite = $suiteEvent->getSuite(); - $group = implode(".", $this->groups); - $suiteName = ($suite->getName())."-{$group}"; + $changeSuiteEvent = $suiteEvent; - call_user_func(\Closure::bind( - function () use ($suite, $suiteName) { - $suite->name = $suiteName; - }, - null, - $suite - )); + if ($this->group != null) { + $suite = $suiteEvent->getSuite(); + $suiteName = ($suite->getName()) . "-{$this->group}"; - //change suiteEvent - $changeSuiteEvent = new SuiteEvent( - $suiteEvent->getSuite(), - $suiteEvent->getResult(), - $suiteEvent->getSettings() - ); + call_user_func(\Closure::bind( + function () use ($suite, $suiteName) { + $suite->name = $suiteName; + }, + null, + $suite + )); + //change suiteEvent + $changeSuiteEvent = new SuiteEvent( + $suiteEvent->getSuite(), + $suiteEvent->getResult(), + $suiteEvent->getSettings() + ); + } // call parent function parent::suiteBefore($changeSuiteEvent); } From df9dfda6ae507cbfead43db4e525aa29745d14b6 Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Wed, 4 Apr 2018 10:22:12 -0500 Subject: [PATCH 34/77] MQE-783: [SPIKE] Investigate allure reporting when tests run in multiple suites - Updated MagentoAllureAdapter to remove instance varaibles and hardcode the groupKey to 'groups' as this will not change --- .../Allure/Adapter/MagentoAllureAdapter.php | 40 +++---------------- 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index 8ecfcb3e3..bdc57e4f0 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -19,45 +19,15 @@ class MagentoAllureAdapter extends AllureAdapter { - /** - * Variable name used for extracting group argument to codecept run commaned - * - * @var string - */ - protected $groupKey = "groups"; - - /** - * Array of group values from test runner command to append to allure suitename - * - * @var array - */ - protected $group; - - /** - * Initialize from parent with group value - * - * @param array $ignoredAnnotations - * @return void - */ - - // @codingStandardsIgnoreStart - public function _initialize(array $ignoredAnnotations = []) - { - $this->group = $this->getGroup($this->groupKey); - parent::_initialize($ignoredAnnotations); - } - // @codingStandardsIgnoreEnd - /** * Array of group values passed to test runner command * - * @param String $groupKey * @return String */ - private function getGroup($groupKey) + private function getGroup() { - if(!($this->options[$groupKey] == Null)){ - return $this->options[$groupKey][0]; + if ($this->options['groups'] != null) { + return $this->options['groups'][0]; } return null; } @@ -72,9 +42,9 @@ public function suiteBefore(SuiteEvent $suiteEvent) { $changeSuiteEvent = $suiteEvent; - if ($this->group != null) { + if ($this->getGroup() != null) { $suite = $suiteEvent->getSuite(); - $suiteName = ($suite->getName()) . "-{$this->group}"; + $suiteName = ($suite->getName()) . "-{$this->getGroup()}"; call_user_func(\Closure::bind( function () use ($suite, $suiteName) { From c9cbeb60d7237c55228ac571e3afc836b8018df2 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 4 Apr 2018 10:40:42 -0500 Subject: [PATCH 35/77] MQE-878: Modify parallel grouping algorithm to work with suites - add new parallel group sorter - update suite object handler to work with new parallel groupings - alter manifest files to use new -g notation for groups - add new unit tests for new classes --- .../Suite/Handlers/SuiteObjectHandlerTest.php | 109 +++++++ .../Util/Sorter/ParallelGroupSorterTest.php | 122 ++++++++ .../Validation/NameValidationUtilTest.php} | 6 +- dev/tests/unit/Util/SuiteDataArrayBuilder.php | 234 +++++++++++++++ .../Tests/SuiteGenerationTest.php | 1 + .../Suite/Generators/GroupClassGenerator.php | 16 +- .../Suite/Handlers/SuiteObjectHandler.php | 4 - .../Suite/Objects/SuiteObject.php | 14 +- .../Suite/SuiteGenerator.php | 140 ++++++--- .../Suite/Util/SuiteObjectExtractor.php | 4 + .../Test/Util/TestNameValidationUtil.php | 61 ---- .../Test/Util/TestObjectExtractor.php | 3 +- .../Util/Manifest/BaseTestManifest.php | 3 +- .../Util/Manifest/DefaultTestManifest.php | 26 +- .../Util/Manifest/ParallelTestManifest.php | 75 +++-- .../Util/Manifest/SingleRunTestManifest.php | 4 +- .../Util/Manifest/TestManifestFactory.php | 4 + .../Util/Sorter/ParallelGroupSorter.php | 270 ++++++++++++++++++ .../Util/TestGenerator.php | 16 +- .../Util/Validation/NameValidationUtil.php | 53 ++++ 20 files changed, 1014 insertions(+), 151 deletions(-) create mode 100644 dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php create mode 100644 dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php rename dev/tests/unit/Magento/FunctionalTestFramework/{Test/Util/TestNameValidationUtilTest.php => Util/Validation/NameValidationUtilTest.php} (90%) create mode 100644 dev/tests/unit/Util/SuiteDataArrayBuilder.php delete mode 100644 src/Magento/FunctionalTestingFramework/Test/Util/TestNameValidationUtil.php create mode 100644 src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php create mode 100644 src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php new file mode 100644 index 000000000..ede99bf34 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Suite/Handlers/SuiteObjectHandlerTest.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Tests\unit\Magento\FunctionalTestFramework\Suite\Handlers; + +use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\ObjectManager\ObjectManager; +use Magento\FunctionalTestingFramework\ObjectManagerFactory; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Parsers\TestDataParser; +use PHPUnit\Framework\TestCase; +use tests\unit\Util\SuiteDataArrayBuilder; +use tests\unit\Util\TestDataArrayBuilder; + +class SuiteObjectHandlerTest extends TestCase +{ + /** + * Tests basic parsing and accesors of suite object and suite object supporting classes + */ + public function testGetSuiteObject() + { + $suiteDataArrayBuilder = new SuiteDataArrayBuilder(); + $mockData = $suiteDataArrayBuilder + ->withName('basicTestSuite') + ->withAfterHook() + ->withBeforeHook() + ->includeTests(['simpleTest']) + ->includeGroups(['group1']) + ->excludeTests(['group1Test2']) + ->excludeGroups(['group2']) + ->build(); + + $testDataArrayBuilder = new TestDataArrayBuilder(); + $mockSimpleTest = $testDataArrayBuilder + ->withName('simpleTest') + ->withTestActions() + ->build(); + + $mockGroup1Test1 = $testDataArrayBuilder + ->withName('group1Test1') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + + $mockGroup1Test2 = $testDataArrayBuilder + ->withName('group1Test2') + ->withAnnotations(['group' => [['value' => 'group1']]]) + ->withTestActions() + ->build(); + + $mockGroup2Test1 = $testDataArrayBuilder + ->withName('group2Test1') + ->withAnnotations(['group' => [['value' => 'group2']]]) + ->withTestActions() + ->build(); + + $mockTestData = ['tests' => array_merge($mockSimpleTest, $mockGroup1Test1, $mockGroup1Test2, $mockGroup2Test1)]; + $this->setMockTestAndSuiteParserOutput($mockTestData, $mockData); + + // parse and retrieve suite object with mocked data + $basicTestSuiteObj = SuiteObjectHandler::getInstance()->getObject('basicTestSuite'); + + // assert on created suite object + $this->assertEquals($basicTestSuiteObj->getName(), 'basicTestSuite'); + $this->assertCount(2, $basicTestSuiteObj->getTests()); + $this->assertNotEmpty($basicTestSuiteObj->getBeforeHook()); + $this->assertNotEmpty($basicTestSuiteObj->getAfterHook()); + } + + /** + * Function used to set mock for parser return and force init method to run between tests. + * + * @param array $testData + * @throws \Exception + */ + private function setMockTestAndSuiteParserOutput($testData, $suiteData) + { + // clear test object handler value to inject parsed content + $property = new \ReflectionProperty(TestObjectHandler::class, 'testObjectHandler'); + $property->setAccessible(true); + $property->setValue(null); + + // clear suite object handler value to inject parsed content + $property = new \ReflectionProperty(SuiteObjectHandler::class, 'SUITE_OBJECT_HANLDER_INSTANCE'); + $property->setAccessible(true); + $property->setValue(null); + + $mockDataParser = AspectMock::double(TestDataParser::class, ['readTestData' => $testData])->make(); + $mockSuiteDataParser = AspectMock::double(SuiteDataParser::class, ['readSuiteData' => $suiteData])->make(); + $instance = AspectMock::double( + ObjectManager::class, + ['create' => function ($clazz) use ($mockDataParser, $mockSuiteDataParser) { + if ($clazz == TestDataParser::class) { + return $mockDataParser; + } + + if ($clazz == SuiteDataParser::class) { + return $mockSuiteDataParser; + } + }] + )->make(); + // bypass the private constructor + AspectMock::double(ObjectManagerFactory::class, ['getObjectManager' => $instance]); + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php new file mode 100644 index 000000000..444749ed2 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php @@ -0,0 +1,122 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Tests\unit\Magento\FunctionalTestFramework\Util\Sorter; + +use AspectMock\Test as AspectMock; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; +use PHPUnit\Framework\TestCase; + +class ParallelGroupSorterTest extends TestCase +{ + /** + * Test a basic sort of available tests based on size + */ + public function testBasicTestGroupSort() + { + $sampleTestArray = [ + 'test1' => 100, + 'test2' => 300, + 'test3' => 50, + 'test4' => 60, + 'test5' => 25, + 'test6' => 125, + 'test7' => 250, + 'test8' => 1, + 'test9' => 80, + 'test10' => 25 + ]; + + $expectedResult = [ + 1 => ['test2'], + 2 => ['test7'], + 3 => ['test6', 'test9'], + 4 => ['test1', 'test4', 'test3'], + 5 => ['test5', 'test10', 'test8'] + ]; + + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedBySize([], $sampleTestArray, 200); + + $this->assertCount(5, $actualResult); + + foreach ($actualResult as $gropuNumber => $actualTests) { + $expectedTests = $expectedResult[$gropuNumber]; + $this->assertEquals($expectedTests, array_keys($actualTests)); + } + } + + /** + * Test a sort of both tests and a suite which is larger than the given line limitation + */ + public function testSortWithSuites() + { + // mock tests for test object handler. + $numberOfCalls = 0; + $mockTest1 = AspectMock::double(TestObject::class, ['getTestActionCount' => function () use (&$numberOfCalls) { + $actionCount = [200, 275]; + $result = $actionCount[$numberOfCalls]; + $numberOfCalls++; + + return $result; + }])->make(); + + $mockHandler = AspectMock::double( + TestObjectHandler::class, + ['getObject' => function () use ($mockTest1) { + return $mockTest1; + }] + )->make(); + + AspectMock::double(TestObjectHandler::class, ['getInstance' => $mockHandler])->make(); + + // mock a suite object + $mockSuite = AspectMock::double(SuiteObject::class, [ + 'getBeforeHook' => null, + 'getAfterHook' => null, + 'getName' => 'mockSuite1' + ])->make(); + $mockSuiteHandler = AspectMock::double(SuiteObjectHandler::class, ['getObject' => $mockSuite])->make(); + AspectMock::double(SuiteObjectHandler::class, ['getInstance' => $mockSuiteHandler]); + + // create test to size array + $sampleTestArray = [ + 'test1' => 100, + 'test2' => 300, + 'test3' => 500, + 'test4' => 60, + 'test5' => 125 + ]; + + // create mock suite references + $sampleSuiteArray = [ + 'mockTest1' => ['mockSuite1'], + 'mockTest2' => ['mockSuite1'] + ]; + + // perform sort + $testSorter = new ParallelGroupSorter(); + $actualResult = $testSorter->getTestsGroupedBySize($sampleSuiteArray, $sampleTestArray, 200); + + // verify the resulting groups + $this->assertCount(5, $actualResult); + + $expectedResults = [ + 1 => ['test3'], + 2 => ['test2'], + 3 => ['mockSuite1_0'], + 4 => ['mockSuite1_1'], + 5 => ['test5', 'test4', 'test1'] + ]; + + foreach ($actualResult as $groupNum => $group) { + $this->assertEquals($expectedResults[$groupNum], array_keys($group)); + } + } +} diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/TestNameValidationUtilTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php similarity index 90% rename from dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/TestNameValidationUtilTest.php rename to dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php index 8b50062ed..595033012 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/TestNameValidationUtilTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Validation/NameValidationUtilTest.php @@ -7,10 +7,10 @@ namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; use Magento\FunctionalTestingFramework\Exceptions\XmlException; -use Magento\FunctionalTestingFramework\Test\Util\TestNameValidationUtil; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; use PHPUnit\Framework\TestCase; -class TestNameValidationUtilTest extends TestCase +class NameValidationUtilTest extends TestCase { /** * Validate name with curly braces throws exception @@ -69,6 +69,6 @@ public function testSpacesInTestName() private function validateBlacklistedTestName($testName) { $this->expectException(XmlException::class); - TestNameValidationUtil::validateName($testName); + NameValidationUtil::validateName($testName, "Test"); } } diff --git a/dev/tests/unit/Util/SuiteDataArrayBuilder.php b/dev/tests/unit/Util/SuiteDataArrayBuilder.php new file mode 100644 index 000000000..85411b447 --- /dev/null +++ b/dev/tests/unit/Util/SuiteDataArrayBuilder.php @@ -0,0 +1,234 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace tests\unit\Util; + +use Magento\FunctionalTestingFramework\Suite\Util\SuiteObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; +use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; + +class SuiteDataArrayBuilder +{ + /** + * Mock test name + * + * @var string + */ + private $name; + + /** + * Mock before action name + * + * @var string + */ + private $testActionBeforeName = 'testActionBefore'; + + /** + * Mock after action name + * + * @var string + */ + private $testActionAfterName = 'testActionAfter'; + + /** + * Array containing before hook actions + * + * @var array + */ + private $beforeHook = []; + + /** + * Arrat containing after hook actions + * + * @var array + */ + private $afterHook = []; + + /** + * Array which contains tests, groups, modules included as part of suite + * + * @var array + */ + private $includes = []; + + /** + * Array which contains, tests, groups, module excluded from suite + * + * @var array + */ + private $excludes = []; + + /** + * Mock test action type + * + * @var string + */ + public $testActionType = 'testAction'; + + /** + * Function which sets the name of the mock suite array + * + * @param string $name + * @return $this + */ + public function withName($name) + { + $this->name = $name; + return $this; + } + + /** + * Function which takes an array of test names and formats them as included raw suite data + * + * @param array $tests + * @return $this + */ + public function includeTests($tests) + { + $this->includes = $this->appendEntriesToSuiteContents($this->includes, 'test', $tests); + return $this; + } + + /** + * Function which takes an array of test names and formats them as excluded raw suite data + * + * @param array $tests + * @return $this + */ + public function excludeTests($tests) + { + $this->excludes = $this->appendEntriesToSuiteContents($this->excludes, 'test', $tests); + return $this; + } + + /** + * Function which takes an array of group names and formats them as included raw suite data + * + * @param array $groups + * @return $this + */ + public function includeGroups($groups) + { + $this->includes = $this->appendEntriesToSuiteContents($this->includes, 'group', $groups); + return $this; + } + + /** + * Function which takes an array of group names and formats them as excluded raw suite data + * + * @param array $groups + * @return $this + */ + public function excludeGroups($groups) + { + $this->excludes = $this->appendEntriesToSuiteContents($this->excludes, 'groups', $groups); + return $this; + } + + /** + * Function which takes an array of module names and formats them as included raw suite data + * + * @param array $modules + * @return $this + */ + public function includeModules($modules) + { + $this->includes = $this->appendEntriesToSuiteContents($this->includes, 'module', $modules); + return $this; + } + + /** + * Function which takes an array of group names and formats them as excluded raw suite data + * + * @param array $modules + * @return $this + */ + public function excludeModules($modules) + { + $this->excludes = $this->appendEntriesToSuiteContents($this->excludes, 'module', $modules); + return $this; + } + + /** + * Function which takes an array of current include/exclude contents, a type (group, module, or test) and contents + * to be appended to the array and returns a propelry formatted array representative of parsed suite data. + * + * @param array $currentContents + * @param string $type + * @param array $contents + * @return array + */ + private function appendEntriesToSuiteContents($currentContents, $type, $contents) + { + $newContents = $currentContents; + foreach ($contents as $entry) { + $newContents[$entry] = [ + SuiteObjectExtractor::NODE_NAME => $type, + SuiteObjectExtractor::NAME => $entry + ]; + } + + return $newContents; + } + + /** + * Add an after hook passed in by arg (or default if no arg) + * + * @param null $afterHook + * @return $this + */ + public function withAfterHook($afterHook = null) + { + if ($afterHook == null) { + $this->afterHook = [$this->testActionAfterName => [ + ActionObjectExtractor::NODE_NAME => $this->testActionType, + ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionAfterName + + ]]; + } else { + $this->afterHook = $afterHook; + } + + return $this; + } + + /** + * Add a before hook passed in by arg (or default if no arg) + * + * @param null $beforeHook + * @return $this + */ + public function withBeforeHook($beforeHook = null) + { + if ($beforeHook == null) { + $this->beforeHook = [$this->testActionBeforeName => [ + ActionObjectExtractor::NODE_NAME => $this->testActionType, + ActionObjectExtractor::TEST_STEP_MERGE_KEY => $this->testActionBeforeName + ]]; + } else { + $this->beforeHook = $beforeHook; + } + + return $this; + } + + /** + * Function which takes all class properties set and generates an array representing suite data as parsed from xml. + * + * @return array + */ + public function build() + { + return ['suites' => [ + $this->name => [ + SuiteObjectExtractor::NAME => $this->name, + TestObjectExtractor::TEST_BEFORE_HOOK => $this->beforeHook, + TestObjectExtractor::TEST_AFTER_HOOK => $this->afterHook, + SuiteObjectExtractor::INCLUDE_TAG_NAME => $this->includes, + SuiteObjectExtractor::EXCLUDE_TAG_NAME => $this->excludes + ] + ]]; + } +} diff --git a/dev/tests/verification/Tests/SuiteGenerationTest.php b/dev/tests/verification/Tests/SuiteGenerationTest.php index 10049484e..b1790ac81 100644 --- a/dev/tests/verification/Tests/SuiteGenerationTest.php +++ b/dev/tests/verification/Tests/SuiteGenerationTest.php @@ -86,6 +86,7 @@ public function testSuiteGeneration1() // Validate tests have been generated $dirContents = array_diff(scandir($suiteResultBaseDir), ['..', '.']); + foreach ($expectedContents as $expectedFile) { $this->assertTrue(in_array($expectedFile, $dirContents)); } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php index 46c2caaed..5ba1c3cac 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -40,6 +40,16 @@ class GroupClassGenerator */ private $mustacheEngine; + /** + * Static function to return group directory path for precondition files. + * + * @return string + */ + public static function getGroupDirPath() + { + return dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . self::GROUP_DIR_NAME . DIRECTORY_SEPARATOR; + } + /** * GroupClassGenerator constructor */ @@ -64,10 +74,7 @@ public function generateGroupClass($suiteObject) { $classContent = $this->createClassContent($suiteObject); $configEntry = self::GROUP_DIR_NAME . DIRECTORY_SEPARATOR . $suiteObject->getName(); - $filePath = dirname(dirname(__DIR__)) . - DIRECTORY_SEPARATOR . - $configEntry . - '.php'; + $filePath = self::getGroupDirPath() . $suiteObject->getName() . '.php'; file_put_contents($filePath, $classContent); return str_replace(DIRECTORY_SEPARATOR, "\\", $configEntry); @@ -122,6 +129,7 @@ private function buildHookMustacheArray($hookObj) $mustacheHookArray = []; $actions = []; $hasWebDriverActions = false; + foreach ($hookObj->getActions() as $action) { /** @var ActionObject $action */ $index = count($actions); diff --git a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php index 6795bef5b..3e4e0a1b5 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -5,16 +5,12 @@ */ namespace Magento\FunctionalTestingFramework\Suite\Handlers; -use Exception; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\ObjectManagerFactory; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Suite\Parsers\SuiteDataParser; use Magento\FunctionalTestingFramework\Suite\Util\SuiteObjectExtractor; -use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; -use Magento\FunctionalTestingFramework\Test\Util\TestHookObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\ObjectExtractor; -use Magento\Ui\Test\Unit\Component\PagingTest; /** * Class SuiteObjectHandler diff --git a/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php index c0a2be9e9..bf560eac3 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Objects/SuiteObject.php @@ -117,6 +117,16 @@ public function requiresGroupFile() return !empty($this->hooks); } + /** + * Getter for the Hook Array which contains the before/after objects. + * + * @return TestHookObject[] + */ + public function getHooks() + { + return $this->hooks; + } + /** * Getter for before hooks. * @@ -124,7 +134,7 @@ public function requiresGroupFile() */ public function getBeforeHook() { - return $this->hooks['before']; + return $this->hooks['before'] ?? null; } /** @@ -134,6 +144,6 @@ public function getBeforeHook() */ public function getAfterHook() { - return $this->hooks['after']; + return $this->hooks['after'] ?? null; } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index a3c664fb0..f87ab5c4c 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -12,6 +12,8 @@ use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest; +use Magento\FunctionalTestingFramework\Util\Manifest\ParallelTestManifest; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Yaml\Yaml; @@ -32,13 +34,6 @@ class SuiteGenerator */ private static $SUITE_GENERATOR_INSTANCE; - /** - * Boolean to track whether we have already cleared the yaml file. - * - * @var bool - */ - private $ymlFileCleared = false; - /** * Group Class Generator initialized in constructor. * @@ -62,6 +57,9 @@ private function __construct() public static function getInstance() { if (!self::$SUITE_GENERATOR_INSTANCE) { + // clear any previous configurations before any generation occurs. + self::clearPreviousGroupPreconditions(); + self::clearPreviousSessionConfigEntries(); self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator(); } @@ -72,17 +70,38 @@ public static function getInstance() * Function which takes all suite configurations and generates to appropriate directory, updating yml configuration * as needed. Returns an array of all tests generated keyed by test name. * - * @param string $config + * @param BaseTestManifest $testManifest + * @return void + */ + public function generateAllSuites($testManifest) + { + $suites = SuiteObjectHandler::getInstance()->getAllObjects(); + if (get_class($testManifest) == ParallelTestManifest::class) { + /** @var ParallelTestManifest $testManifest */ + $suites = $testManifest->getSorter()->getResultingSuites(); + } + + foreach ($suites as $suite) { + // during a parallel config run we must generate only after we have data around how a suite will be split + $this->generateSuiteFromObject($suite); + } + } + + /** + * Returns an array of tests contained within suites as keys pointed at the name of their corresponding suite. + * * @return array */ - public function generateAllSuites($config) + public function getTestsReferencedInSuites() { $testsReferencedInSuites = []; $suites = SuiteObjectHandler::getInstance()->getAllObjects(); foreach ($suites as $suite) { /** @var SuiteObject $suite */ - $testsReferencedInSuites = array_merge($testsReferencedInSuites, $suite->getTests()); - $this->generateSuite($suite->getName(), $config); + $test_keys = array_keys($suite->getTests()); + $testToSuiteName = array_fill_keys($test_keys, [$suite->getName()]); + + $testsReferencedInSuites = array_merge_recursive($testsReferencedInSuites, $testToSuiteName); } return $testsReferencedInSuites; @@ -93,23 +112,34 @@ public function generateAllSuites($config) * yml configuration for group run. * * @param string $suiteName - * @param string $config * @return void */ - public function generateSuite($suiteName, $config = null) + public function generateSuite($suiteName) { /**@var SuiteObject $suite **/ $suite = SuiteObjectHandler::getInstance()->getObject($suiteName); + $this->generateSuiteFromObject($suite); + } + + /** + * Function which takes a suite object and generates all relevant supporting files and classes. + * + * @param SuiteObject $suiteObject + * @return void + */ + public function generateSuiteFromObject($suiteObject) + { + $suiteName = $suiteObject->getName(); $relativePath = TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $suiteName; $fullPath = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . $relativePath; $groupNamespace = null; DirSetupUtil::createGroupDir($fullPath); - $this->generateRelevantGroupTests($suiteName, $suite->getTests(), $config); + $this->generateRelevantGroupTests($suiteName, $suiteObject->getTests()); - if ($suite->requiresGroupFile()) { + if ($suiteObject->requiresGroupFile()) { // if the suite requires a group file, generate it and set the namespace - $groupNamespace = $this->groupClassGenerator->generateGroupClass($suite); + $groupNamespace = $this->groupClassGenerator->generateGroupClass($suiteObject); } $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); @@ -128,50 +158,33 @@ public function generateSuite($suiteName, $config = null) */ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace) { - $configYmlPath = dirname(dirname(TESTS_BP)) . DIRECTORY_SEPARATOR; - $configYmlFile = $configYmlPath . self::YAML_CODECEPTION_CONFIG_FILENAME; - $defaultConfigYmlFile = $configYmlPath . self::YAML_CODECEPTION_DIST_FILENAME; $relativeSuitePath = substr($suitePath, strlen(dirname(dirname(TESTS_BP))) + 1); - $ymlContents = null; - if (file_exists($configYmlFile)) { - $ymlContents = file_get_contents($configYmlFile); - } else { - $ymlContents = file_get_contents($defaultConfigYmlFile); - } - - $ymlArray = Yaml::parse($ymlContents) ?? []; + $ymlArray = self::getYamlFileContents(); if (!array_key_exists(self::YAML_GROUPS_TAG, $ymlArray)) { $ymlArray[self::YAML_GROUPS_TAG]= []; } - $ymlArray = $this->clearPreviousSessionConfigEntries($ymlArray); - if ($groupNamespace) { $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace; } $ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath]; $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($ymlArray, 10); - file_put_contents($configYmlFile, $ymlText); + file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); } /** * Function which takes the current config.yml array and clears any previous configuration for suite group object * files. * - * @param array $ymlArray - * @return array + * @return void */ - private function clearPreviousSessionConfigEntries($ymlArray) + private static function clearPreviousSessionConfigEntries() { - if ($this->ymlFileCleared) { - return $ymlArray; - } - + $ymlArray = self::getYamlFileContents(); $newYmlArray = $ymlArray; // if the yaml entries haven't already been cleared - if (array_key_exists(self::YAML_EXTENSIONS_TAG, $ymlArray)) { foreach ($ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG] as $key => $entry) { if (preg_match('/(Group\\\\.*)/', $entry)) { @@ -190,9 +203,8 @@ private function clearPreviousSessionConfigEntries($ymlArray) unset($newYmlArray[self::YAML_GROUPS_TAG]); } - $this->ymlFileCleared = true; - - return $newYmlArray; + $ymlText = self::YAML_COPYRIGHT_TEXT . Yaml::dump($newYmlArray, 10); + file_put_contents(self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME, $ymlText); } /** @@ -202,12 +214,52 @@ private function clearPreviousSessionConfigEntries($ymlArray) * * @param string $path * @param array $tests - * @param string $config * @return void */ - private function generateRelevantGroupTests($path, $tests, $config) + private function generateRelevantGroupTests($path, $tests) { $testGenerator = TestGenerator::getInstance($path, $tests); - $testGenerator->createAllTestFiles($config); + $testGenerator->createAllTestFiles('suite'); + } + + /** + * Function which on first execution deletes all generate php in the MFTF Group directory + * + * @return void + */ + private static function clearPreviousGroupPreconditions() + { + $groupFilePath = GroupClassGenerator::getGroupDirPath(); + array_map('unlink', glob("$groupFilePath*.php")); + } + + /** + * Function to return contents of codeception.yml file for config changes. + * + * @return array + */ + private static function getYamlFileContents() + { + $configYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_CONFIG_FILENAME; + $defaultConfigYmlFile = self::getYamlConfigFilePath() . self::YAML_CODECEPTION_DIST_FILENAME; + + $ymlContents = null; + if (file_exists($configYmlFile)) { + $ymlContents = file_get_contents($configYmlFile); + } else { + $ymlContents = file_get_contents($defaultConfigYmlFile); + } + + return Yaml::parse($ymlContents) ?? []; + } + + /** + * Static getter for the Config yml filepath (as path cannot be stored in a const) + * + * @return string + */ + private static function getYamlConfigFilePath() + { + return dirname(dirname(TESTS_BP)) . DIRECTORY_SEPARATOR; } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 56e50982a..4ff0b25a3 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -13,6 +13,7 @@ use Magento\FunctionalTestingFramework\Test\Util\BaseObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestHookObjectExtractor; use Magento\FunctionalTestingFramework\Test\Util\TestObjectExtractor; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; class SuiteObjectExtractor extends BaseObjectExtractor { @@ -55,6 +56,9 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) // skip non array items parsed from suite (suite objects will always be arrays) continue; } + + // validate the name used isn't using special char or the "default" reserved name + NameValidationUtil::validateName($parsedSuite[self::NAME], 'Suite'); if ($parsedSuite[self::NAME] == 'default') { throw new XmlException("A Suite can not have the name \"default\""); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestNameValidationUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestNameValidationUtil.php deleted file mode 100644 index 61124a3b3..000000000 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestNameValidationUtil.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\FunctionalTestingFramework\Test\Util; - -use Magento\FunctionalTestingFramework\Exceptions\XmlException; - -class TestNameValidationUtil -{ - const BLACKLISTED_CHAR = [ - " " => "spaces", - "," => "commas", - "'" => "single quotes", - "\"" => "double quotes", - "{" => "curly braces", - "}" => "curly braces", - "$" => "dollar signs", - "(" => "parenthesis", - ")" => "parenthesis" - ]; - - /** - * Function which runs a validation against the blacklisted char defined in this class. Validation occurs to insure - * allure report does not error/future devOps builds do not error against illegal char. - * - * @param string $testName - * @return void - * @throws XmlException - */ - public static function validateName($testName) - { - $testChars = str_split($testName); - - $diff = array_intersect($testChars, array_keys(self::BLACKLISTED_CHAR)); - if (count($diff) > 0) { - $errorMessage = "Test name \"${testName}\" contains illegal characters, please fix and re-run."; - $uniqueDiff = array_unique(array_map(['self', 'nameMapper'], $diff)); - - foreach ($uniqueDiff as $diffChar) { - $errorMessage .= "\nTest names cannot contain " . $diffChar; - } - - throw new XmlException($errorMessage); - } - } - - /** - * Function which maps the blacklisted char to its name, function is used by the array map above. - * - * @param string $val - * @return string - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) - */ - private static function nameMapper($val) - { - return self::BLACKLISTED_CHAR[$val]; - } -} diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index d9044a0a1..48b7d5552 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -8,6 +8,7 @@ use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; +use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; /** * Class TestObjectExtractor @@ -61,7 +62,7 @@ public function __construct() public function extractTestData($testData) { // validate the test name for blacklisted char (will cause allure report issues) MQE-483 - TestNameValidationUtil::validateName($testData[self::NAME]); + NameValidationUtil::validateName($testData[self::NAME], "Test"); $testAnnotations = []; $testHooks = []; diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php index f31271a8d..d8c7c397b 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php @@ -57,8 +57,9 @@ abstract public function addTest($testObject); /** * Function which generates the actual manifest(s) once the relevant tests have been added to the array. * + * @param array $testsReferencedInSuites * @param int|null $nodes * @return void */ - abstract public function generate($nodes = null); + abstract public function generate($testsReferencedInSuites, $nodes = null); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index 369de08e6..d577be92f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -62,10 +62,11 @@ public function addTest($testObject) /** * Function which outputs a list of all test files to the defined testManifest.txt file. * + * @param array $testsReferencedInSuites * @param int|null $nodes * @return void */ - public function generate($nodes = null) + public function generate($testsReferencedInSuites, $nodes = null) { $fileResource = fopen($this->manifestPath, 'a'); @@ -74,9 +75,32 @@ public function generate($nodes = null) fwrite($fileResource, $line . PHP_EOL); } + $this->generateSuiteEntries($testsReferencedInSuites, $fileResource); + fclose($fileResource); } + /** + * Function which takes the test suites passed to the manifest and generates corresponding entries in the manifest. + * + * @param array $testsReferencedInSuites + * @param resource $fileResource + * @return void + */ + protected function generateSuiteEntries($testsReferencedInSuites, $fileResource) + { + // get the names of available suites + $suiteNames = []; + array_walk($testsReferencedInSuites, function ($value) use (&$suiteNames) { + $suiteNames = array_unique(array_merge($value, $suiteNames)); + }); + + foreach ($suiteNames as $suiteName) { + $line = "-g {$suiteName}"; + fwrite($fileResource, $line . PHP_EOL); + } + } + /** * Function which checks the path for an existing test manifest and clears if the file has not already been cleared * during current runtime. diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php index f130ecff7..9c26e270c 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php @@ -7,8 +7,12 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; use Magento\Framework\Exception\RuntimeException; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; +use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; class ParallelTestManifest extends BaseTestManifest { @@ -21,6 +25,13 @@ class ParallelTestManifest extends BaseTestManifest */ private $testNameToSize = []; + /** + * An instance of the group sorter which will take suites and tests organizing them to be run together. + * + * @var ParallelGroupSorter + */ + private $parallelGroupSorter; + /** * Path to the directory that will contain all test group files * @@ -37,6 +48,7 @@ class ParallelTestManifest extends BaseTestManifest public function __construct($manifestPath, $testPath) { $this->dirPath = $manifestPath . DIRECTORY_SEPARATOR . 'groups'; + $this->parallelGroupSorter = new ParallelGroupSorter(); parent::__construct($testPath, self::PARALLEL_CONFIG); } @@ -54,43 +66,56 @@ public function addTest($testObject) /** * Function which generates the actual manifest once the relevant tests have been added to the array. * - * @param int $nodes + * @param array $testsReferencedInSuites + * @param int $lines * @return void */ - public function generate($nodes = null) + public function generate($testsReferencedInSuites, $lines = null) { - if ($nodes == null) { - $nodes = 2; - } - DirSetupUtil::createGroupDir($this->dirPath); - arsort($this->testNameToSize); - $node = $nodes; + $testGroups = $this->parallelGroupSorter->getTestsGroupedBySize( + $testsReferencedInSuites, + $this->testNameToSize, + $lines + ); - foreach (array_keys($this->testNameToSize) as $testName) { - $node = $this->getNodeOrder($node, $nodes); - $nodeString = strval($node); - $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "group{$nodeString}.txt", 'a'); - $line = $this->relativeDirPath . DIRECTORY_SEPARATOR . $testName . '.php'; - fwrite($fileResource, $line . PHP_EOL); - fclose($fileResource); + foreach ($testGroups as $groupNumber => $groupContents) { + $this->generateGroupFile($groupContents, $groupNumber); } } /** - * Function which independently iterates node position based on number of desired nodes. + * Function which simply returns the private sorter used by the manifest. * - * @param int $currentNode - * @param int $nodes - * @return int + * @return ParallelGroupSorter */ - private function getNodeOrder($currentNode, $nodes) + public function getSorter() { - $adjustedRef = $currentNode + 1; - if ($adjustedRef <= $nodes) { - return $currentNode + 1; - } + return $this->parallelGroupSorter; + } - return 1; + /** + * Function which takes an array containing entries representing the test execution as well as the associated group + * for the entry in order to generate a txt file used by devops for parllel execution in Jenkins. + * + * @param array $testGroup + * @param int $nodeNumber + * @return void + */ + private function generateGroupFile($testGroup, $nodeNumber) + { + $suites = $this->parallelGroupSorter->getResultingSuites(); + foreach ($testGroup as $entryName => $testValue) { + $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "group{$nodeNumber}.txt", 'a'); + + $line = null; + if (array_key_exists($entryName, $suites)) { + $line = "-g {$entryName}"; + } else { + $line = $this->relativeDirPath . DIRECTORY_SEPARATOR . $entryName . '.php'; + } + fwrite($fileResource, $line . PHP_EOL); + fclose($fileResource); + } } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php index 5fc492e9e..f4fb33b5e 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php @@ -26,14 +26,16 @@ public function __construct($manifestPath, $testPath) /** * Function which generates the actual manifest once the relevant tests have been added to the array. * + * @param array $testsReferencedInSuites * @param int|null $nodes * @return void */ - public function generate($nodes = null) + public function generate($testsReferencedInSuites, $nodes = null) { $fileResource = fopen($this->manifestPath, 'a'); $line = $this->relativeDirPath . DIRECTORY_SEPARATOR; fwrite($fileResource, $line . PHP_EOL); + $this->generateSuiteEntries($testsReferencedInSuites, $fileResource); fclose($fileResource); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php index bc54a47ea..4344a96a1 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php @@ -33,6 +33,10 @@ public static function makeManifest($manifestPath, $testPath, $runConfig) case 'parallel': return new ParallelTestManifest($manifestPath, $testPath); + case 'suite': + // the suite does not have its own manifest but instead is handled by the other suite types. + return null; + default: return new DefaultTestManifest($manifestPath, $testPath); diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php new file mode 100644 index 000000000..9d51bf2e1 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -0,0 +1,270 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Util\Sorter; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; + +class ParallelGroupSorter +{ + /** + * An array of newly split suite object names mapped to their corresponding objects. + * + * @var array + */ + private $compositeSuiteObjects = []; + + /** + * ParallelGroupSorter constructor. + */ + public function __construct() + { + // empty constructor + } + + /** + * Function which returns tests and suites split according to desired number of lines divded into groups. + * + * @param array $testNameToSuiteName + * @param array $testNameToSize + * @param integer $lines + * @return array + * @throws TestFrameworkException + */ + public function getTestsGroupedBySize($testNameToSuiteName, $testNameToSize, $lines) + { + // we must have the lines argument in order to create the test groups + if ($lines == 0) { + throw new TestFrameworkException( + "Please provide the argument '--lines' to the robo command in order to". + " generate grouped tests manifests for a parallel execution" + ); + } + + $testGroups = []; + $splitSuiteNamesToTests = $this->createGroupsWithinSuites($testNameToSuiteName, $lines); + $splitSuiteNamesToSize = $this->getSuiteToSize($splitSuiteNamesToTests); + $entriesForGeneration = array_merge($testNameToSize, $splitSuiteNamesToSize); + arsort($entriesForGeneration); + + $testNameToSizeForUse = $entriesForGeneration; + $nodeNumber = 1; + foreach ($entriesForGeneration as $testName => $testSize) { + if (!array_key_exists($testName, $testNameToSizeForUse)) { + // skip tests which have already been added to a group + continue; + } + + $testGroup = $this->createTestGroup($lines, $testName, $testSize, $testNameToSizeForUse); + $testGroups[$nodeNumber] = $testGroup; + + // unset the test which have been used. + $testNameToSizeForUse = array_diff($testNameToSizeForUse, $testGroup); + $nodeNumber++; + } + + return $testGroups; + } + + /** + * Function which returns the newly formed suite objects created as a part of the sort + * + * @return array + */ + public function getResultingSuites() + { + return $this->compositeSuiteObjects; + } + + /** + * Function which constructs a group of tests to be run together based on the desired number of lines per group, + * a test to be used as a starting point, the size of a starting test, an array of tests available to be added to + * the group. + * + * @param integer $lineMaximum + * @param string $testName + * @param integer $testSize + * @param array $testNameToSizeForUse + * @return array + */ + private function createTestGroup($lineMaximum, $testName, $testSize, $testNameToSizeForUse) + { + $group[$testName] = $testSize; + + if ($testSize < $lineMaximum) { + while (array_sum($group) < $lineMaximum && !empty($testNameToSizeForUse)) { + $groupSize = array_sum($group); + $lineGoal = $lineMaximum - $groupSize; + + $testNameForUse = $this->getClosestLineCount($testNameToSizeForUse, $lineGoal); + $testSizeForUse = $testNameToSizeForUse[$testNameForUse]; + unset($testNameToSizeForUse[$testNameForUse]); + + $group[$testNameForUse] = $testSizeForUse; + } + } + + return $group; + } + + /** + * Function which takes a group of available tests mapped to size and a desired number of lines matching with the + * test of closest size and returning. + * + * @param array $testGroup + * @param integer $desiredValue + * @return string + */ + private function getClosestLineCount($testGroup, $desiredValue) + { + $winner = key($testGroup); + $closestThreshold = $desiredValue; + foreach ($testGroup as $testName => $testValue) { + $testThreshold = abs($desiredValue - $testValue); + if ($closestThreshold > $testThreshold) { + $closestThreshold = $testThreshold; + $winner = $testName; + } + } + + return $winner; + } + + /** + * Function which takes an array of test names mapped to suite name and a size limitation for each group of tests. + * The function divides suites that are over the specified limit and returns the resulting suites in an array. + * + * @param array $testNameToSuiteName + * @param integer $lineLimit + * @return array + */ + private function createGroupsWithinSuites($testNameToSuiteName, $lineLimit) + { + $suiteNameToTestNames = []; + $suiteNameToSize = []; + array_walk($testNameToSuiteName, function ($value, $key) use (&$suiteNameToTestNames, &$suiteNameToSize) { + $testActionCount = TestObjectHandler::getInstance()->getObject($key)->getTestActionCount(); + foreach ($value as $suite) { + $suiteNameToTestNames[$suite][$key] = $testActionCount; + $currentSize = $suiteNameToSize[$suite] ?? 0; + $suiteNameToSize[$suite] = $currentSize + $testActionCount; + } + }); + + // divide the suites up within the array + $suitesForResize = array_filter($suiteNameToSize, function ($val) use ($lineLimit) { + return $val > $lineLimit; + }); + + // remove the suites for resize from the original list + $remainingSuites = array_diff_key($suiteNameToTestNames, $suitesForResize); + + foreach ($remainingSuites as $remainingSuite => $tests) { + $this->addSuiteAsObject($remainingSuite, null, null); + } + + $resultingGroups = []; + foreach ($suitesForResize as $suiteName => $suiteSize) { + $resultingGroups = array_merge( + $resultingGroups, + $this->splitTestSuite($suiteName, $suiteNameToTestNames[$suiteName], $lineLimit) + ); + } + + // merge the resulting divisions with the appropriately sized suites + return array_merge($remainingSuites, $resultingGroups); + } + + /** + * Function which takes a multidimensional array containing a suite name mapped to an array of tests names as keys + * with their sizes as values. The function returns an array of suite name to size of the corresponding mapped + * tests. + * + * @param array $suiteNamesToTests + * @return array + */ + private function getSuiteToSize($suiteNamesToTests) + { + $suiteNamesToSize = []; + foreach ($suiteNamesToTests as $name => $tests) { + $size = array_sum($tests); + $suiteNamesToSize[$name] = $size; + } + + return $suiteNamesToSize; + } + + /** + * Function which takes a suite name, an array of tests affiliated with that suite, and a maximum number of lines. + * The function uses the limit to split up the oversized suite and returns an array of suites representative of the + * previously oversized suite. + * + * E.g. + * Input {suitename = 'sample', tests = ['test1' => 100,'test2' => 150, 'test3' => 300], linelimit = 275} + * Result { ['sample_01' => ['test3' => 300], 'sample_02' => ['test2' => 150, 'test1' => 100]] } + * + * @param string $suiteName + * @param array $tests + * @param integer $lineLimit + * @return array + */ + private function splitTestSuite($suiteName, $tests, $lineLimit) + { + arsort($tests); + $split_suites = []; + $availableTests = $tests; + $split_count = 0; + + foreach ($tests as $test => $size) { + if (!array_key_exists($test, $availableTests)) { + continue; + } + + $group = $this->createTestGroup($lineLimit, $test, $size, $availableTests); + $split_suites["{$suiteName}_${split_count}"] = $group; + $this->addSuiteAsObject($suiteName, "{$suiteName}_${split_count}", $group); + + $availableTests = array_diff($availableTests, $group); + $split_count++; + } + + return $split_suites; + } + + /** + * Function which takes a new suite, the original suite from which it was sourced, and an array of tests now + * associated with thew new suite. The function takes this information and creates a new suite object stored in + * the sorter for later retrieval, copying the pre/post conditions from the original suite. + * + * @param string $originalSuiteName + * @param string $newSuiteName + * @param array $tests + * @return void + */ + private function addSuiteAsObject($originalSuiteName, $newSuiteName, $tests) + { + /** @var SuiteObject $originalSuite */ + $originalSuite = SuiteObjectHandler::getInstance()->getObject($originalSuiteName); + if ($newSuiteName == null && $tests == null) { + $this->compositeSuiteObjects[$originalSuiteName] = $originalSuite; + return; + } + + $newSuiteTests = []; + foreach ($tests as $test => $lines) { + $newSuiteTests[$test] = TestObjectHandler::getInstance()->getObject($test); + } + + $this->compositeSuiteObjects[$newSuiteName] = new SuiteObject( + $newSuiteName, + $newSuiteTests, + [], + $originalSuite->getHooks() + ); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 4a51906c0..3a3483ea4 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -154,7 +154,7 @@ private function createCestFile($testPhp, $filename) * @param string $runConfig * @param int $nodes * @param TestObject[] $testsToIgnore - * @return void + * @return BaseTestManifest * @throws TestReferenceException * @throws \Exception */ @@ -173,6 +173,8 @@ public function createAllTestFiles($runConfig = null, $nodes = null, $testsToIgn foreach ($testPhpArray as $testPhpFile) { $this->createCestFile($testPhpFile[1], $testPhpFile[0]); } + + return $testManifest; } /** @@ -230,15 +232,21 @@ private function assembleAllTestPhp($testManifest, $nodes, array $testsToIgnore) $php = $this->assembleTestPhp($test); $cestPhpArray[] = [$test->getCodeceptionName(), $php]; - //write to manifest here if config is not single run - $testManifest->addTest($test); + $debugInformation = $test->getDebugInformation(); $this->debug($debugInformation); $this->debug("<comment>Finish creating test: " . $test->getCodeceptionName() . "</comment>" . PHP_EOL); + + //write to manifest here if manifest is not null + if ($testManifest != null) { + $testManifest->addTest($test); + } } - $testManifest->generate($nodes); + if ($testManifest != null) { + $testManifest->generate($testsToIgnore, intval($nodes)); + } return $cestPhpArray; } diff --git a/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php new file mode 100644 index 000000000..60b4660fb --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Util/Validation/NameValidationUtil.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Util\Validation; + +use Magento\FunctionalTestingFramework\Exceptions\XmlException; + +class NameValidationUtil +{ + const PHP_CLASS_REGEX_PATTERN = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/'; + + /** + * Function which runs a validation against the blacklisted char defined in this class. Validation occurs to insure + * allure report does not error/future devOps builds do not error against illegal char. + * + * @param string $name + * @param string $type + * @return void + * @throws XmlException + */ + public static function validateName($name, $type) + { + $startingPos = 0; + $illegalCharArray = []; + $nameToEvaluate = $name; + + while ($startingPos < strlen($nameToEvaluate)) { + $startingPos++; + $partialName = substr($nameToEvaluate, 0, $startingPos); + $valid = boolval(preg_match(self::PHP_CLASS_REGEX_PATTERN, $partialName)); + + if (!$valid) { + $illegalChar = str_split($partialName)[$startingPos -1]; + $illegalCharArray[] = $illegalChar; + $nameToEvaluate = str_replace($illegalChar, "", $nameToEvaluate); + $startingPos--; + } + } + + if (!empty($illegalCharArray)) { + $errorMessage = "{$type} name \"${name}\" contains illegal characters, please fix and re-run."; + + foreach ($illegalCharArray as $diffChar) { + $errorMessage .= "\nTest names cannot contain '{$diffChar}'"; + } + + throw new XmlException($errorMessage); + } + } +} From f2ed017d6e1f60ec42efd2fbcf06d3b55cc6f5f9 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 4 Apr 2018 16:04:54 -0500 Subject: [PATCH 36/77] MQE-878: Modify parallel grouping algorithm to work with suites - fix for static check post merge --- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 3a3483ea4..a18d1e086 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -232,9 +232,7 @@ private function assembleAllTestPhp($testManifest, $nodes, array $testsToIgnore) $php = $this->assembleTestPhp($test); $cestPhpArray[] = [$test->getCodeceptionName(), $php]; - $debugInformation = $test->getDebugInformation(); - $this->debug($debugInformation); $this->debug("<comment>Finish creating test: " . $test->getCodeceptionName() . "</comment>" . PHP_EOL); From 6eaaa9e3ffe243e22da9c168472bea6742b81695 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 5 Apr 2018 19:16:26 +0300 Subject: [PATCH 37/77] MQE-759: Call Operation tags without tying them to an entity - Test action deleteData now allows you to pass in a url, in lieu of an entity. --- .../Resources/BasicFunctionalTest.txt | 3 ++ .../TestModule/Test/BasicFunctionalTest.xml | 2 + .../Module/MagentoWebDriver.php | 17 +++++++- .../Test/Objects/ActionObject.php | 27 +++++++++++++ .../Test/etc/Actions/dataActions.xsd | 3 +- .../Util/TestGenerator.php | 40 ++++++++++++++----- 6 files changed, 80 insertions(+), 12 deletions(-) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index c500628b8..3452b8883 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -75,6 +75,9 @@ class BasicFunctionalTestCest $I->clickWithRightButton("#element .4123#element", 200, 300); $I->closeTab(); $I->conditionalClick(".functionalTestSelector", ".functionalDependentTestSelector", true); + $I->amGoingTo("delete entity that has the createDataKey: createKey1"); + $createKey1->deleteEntity(); + $I->deleteEntityByUrl("/V1/categories{$grabbedData}"); $I->dontSee("someInput", ".functionalTestSelector"); $I->dontSeeCheckboxIsChecked(".functionalTestSelector"); $I->dontSeeCookie("someInput"); diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index 77122d42f..70ba6299f 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -38,6 +38,8 @@ <clickWithRightButton selector="{{SampleSection.simpleElementOneParam('4123')}}{{SampleSection.simpleElement}}" x="{{offset.x}}" y="{{offset.y}}" stepKey="clickWithRightButtonKeyXY1" /> <closeTab stepKey="closeTabKey1"/> <conditionalClick selector=".functionalTestSelector" dependentSelector=".functionalDependentTestSelector" visible="true" stepKey="conditionalClickKey1"/> + <deleteData stepKey="deleteKey1" createDataKey="createKey1"/> + <deleteData stepKey="deleteKey2" url="/V1/categories{$grabbedData}"/> <dontSee userInput="someInput" selector=".functionalTestSelector" stepKey="dontSeeKey1" /> <dontSeeCheckboxIsChecked selector=".functionalTestSelector" stepKey="dontSeeCheckboxIsCheckedKey1"/> <dontSeeCookie userInput="someInput" stepKey="dontSeeCookieKey1"/> diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 606437cd6..25bb65e07 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -18,6 +18,7 @@ use Codeception\Exception\ModuleException; use Codeception\Util\Uri; use Codeception\Util\ActionSequence; +use Magento\FunctionalTestingFramework\DataGenerator\Persist\Curl\WebapiExecutor; use Magento\FunctionalTestingFramework\Util\Protocol\CurlTransport; use Magento\FunctionalTestingFramework\Util\Protocol\CurlInterface; use Magento\Setup\Exception; @@ -437,7 +438,6 @@ public function scrollToTopOfPage() */ public function magentoCLI($command) { - $apiURL = $this->config['url'] . getenv('MAGENTO_CLI_COMMAND_PATH'); $executor = new CurlTransport(); $executor->write($apiURL, [getenv('MAGENTO_CLI_COMMAND_PARAMETER') => $command], CurlInterface::POST, []); @@ -446,6 +446,21 @@ public function magentoCLI($command) return $response; } + /** + * Runs DELETE request to delete a Magento entity against the url given. + * @param string $url + * @param int $storeCode + * @return string + */ + public function deleteEntityByUrl($url, $storeCode = null) + { + $executor = new WebapiExecutor($storeCode); + $executor->write($url, [], CurlInterface::DELETE, []); + $response = $executor->read(); + $executor->close(); + return $response; + } + /** * Conditional click for an area that should be visible * diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 10cd77937..84f073c04 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -7,6 +7,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\ObjectManager\ObjectHandlerInterface; use Magento\FunctionalTestingFramework\Page\Objects\PageObject; use Magento\FunctionalTestingFramework\Page\Objects\SectionObject; @@ -34,6 +35,7 @@ class ActionObject const ASSERTION_ATTRIBUTES = ["expectedResult" => "expected", "actualResult" => "actual"]; const ASSERTION_TYPE_ATTRIBUTE = "type"; const ASSERTION_VALUE_ATTRIBUTE = "value"; + const DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES = ["url", "createDataKey"]; const EXTERNAL_URL_AREA_INVALID_ACTIONS = ['amOnPage']; const MERGE_ACTION_ORDER_AFTER = 'after'; const MERGE_ACTION_ORDER_BEFORE = 'before'; @@ -229,6 +231,9 @@ public function resolveReferences() $this->resolveSelectorReferenceAndTimeout(); $this->resolveUrlReference(); $this->resolveDataInputReferences(); + if ($this->getType() == "deleteData") { + $this->validateMutuallyExclusiveAttributes(self::DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES); + } } } @@ -496,6 +501,28 @@ private function findAndReplaceReferences($objectHandler, $inputString) return $outputString; } + /** + * Validates that the mutually exclusive attributes passed in don't all occur. + * @param array $attributes + * @return void + * @throws TestReferenceException + */ + private function validateMutuallyExclusiveAttributes(array $attributes) + { + $matches = array_intersect($attributes, array_keys($this->getCustomActionAttributes())); + if (count($matches) > 1) { + throw new TestReferenceException( + "Actions of type '{$this->getType()}' must only contain one attribute of types '" + . implode("', '", $attributes) . "'" + ); + } elseif (count($matches) == 0) { + throw new TestReferenceException( + "Actions of type '{$this->getType()}' must contain at least one attribute of types '" + . implode("', '", $attributes) . "'" + ); + } + } + /** * Validates the page objects area 'external' against a list of known incompatible types * diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd index ad8ea9414..93c07b991 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd @@ -76,7 +76,8 @@ </xs:annotation> <xs:simpleContent> <xs:extension base="xs:string"> - <xs:attribute ref="createDataKey" use="required"/> + <xs:attribute ref="url"/> + <xs:attribute ref="createDataKey"/> <xs:attributeGroup ref="commonActionAttributes"/> <xs:attribute ref="storeCode"/> </xs:extension> diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index a18d1e086..0d30717a2 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -510,6 +510,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $input = $this->addUniquenessFunctionCall($customActionAttributes['userInput']); } elseif (isset($customActionAttributes['url'])) { $input = $this->addUniquenessFunctionCall($customActionAttributes['url']); + $url = $this->addUniquenessFunctionCall($customActionAttributes['url']); } elseif (isset($customActionAttributes['expectedValue'])) { //For old Assert backwards Compatibility, remove when deprecating $assertExpected = $this->addUniquenessFunctionCall($customActionAttributes['expectedValue']); @@ -712,18 +713,37 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $testSteps .= $createEntityFunctionCall; break; case "deleteData": - $key = $customActionAttributes['createDataKey']; - //Add an informative statement to help the user debug test runs - $testSteps .= sprintf( - "\t\t$%s->amGoingTo(\"delete entity that has the createDataKey: %s\");\n", - $actor, - $key - ); + if (isset($customActionAttributes['createDataKey'])) { + $key = $customActionAttributes['createDataKey']; + //Add an informative statement to help the user debug test runs + $testSteps .= sprintf( + "\t\t$%s->amGoingTo(\"delete entity that has the createDataKey: %s\");\n", + $actor, + $key + ); - if ($hookObject) { - $testSteps .= sprintf("\t\t\$this->%s->deleteEntity();\n", $key); + if ($hookObject) { + $testSteps .= sprintf("\t\t\$this->%s->deleteEntity();\n", $key); + } else { + $testSteps .= sprintf("\t\t$%s->deleteEntity();\n", $key); + } } else { - $testSteps .= sprintf("\t\t$%s->deleteEntity();\n", $key); + $output = sprintf( + "\t\t$%s->deleteEntityByUrl(%s", + $actor, + $url + ); + $storeCode = null; + if (isset($customActionAttributes["storeCode"])) { + $storeCode = $customActionAttributes["storeCode"]; + $output .= sprintf( + ", %s", + $storeCode + ); + } + $output .= ");\n"; + $output = $this->resolveEnvReferences($output, [$url, $storeCode]); + $testSteps .= $this->resolveTestVariable($output, [$url, $storeCode], null); } break; case "updateData": From d3db491ba0410d2814609d72aa4de435356c77a3 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 5 Apr 2018 11:42:54 -0500 Subject: [PATCH 38/77] MQE-783: [SPIKE] Investigate allure reporting when tests run in multiple suites - change suiteName to be of format <default>\<groupName> --- .../Allure/Adapter/MagentoAllureAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index bdc57e4f0..c10a04506 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -44,7 +44,7 @@ public function suiteBefore(SuiteEvent $suiteEvent) if ($this->getGroup() != null) { $suite = $suiteEvent->getSuite(); - $suiteName = ($suite->getName()) . "-{$this->getGroup()}"; + $suiteName = ($suite->getName()) . "\\" . $this->getGroup(); call_user_func(\Closure::bind( function () use ($suite, $suiteName) { From 2e466f2eead242108eb627a17621c0556e154584 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 5 Apr 2018 21:45:08 +0300 Subject: [PATCH 39/77] MQE-743: Can't run tests without a store having "default" store code - Persistence mechanisms now do not append 'default' where no store code is given (rely on magento web api to redirect to the default) - update/delete data actions no longer allow for a storeCode, relies on handler's storeCode for overrides - create/get data actions take a storeCode, and overwrite the handler's storeCode if given --- .../Persist/Curl/WebapiExecutor.php | 22 ++++++++-- .../DataGenerator/Persist/CurlHandler.php | 2 +- .../Persist/DataPersistenceHandler.php | 15 ++----- .../Module/MagentoWebDriver.php | 5 +-- .../Test/etc/Actions/dataActions.xsd | 2 - .../Util/TestGenerator.php | 43 +++++++++---------- 6 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php index e0a58d470..9a93cded3 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/Curl/WebapiExecutor.php @@ -55,11 +55,12 @@ class WebapiExecutor extends AbstractExecutor implements CurlInterface * * @param string $storeCode */ - public function __construct($storeCode = 'default') + public function __construct($storeCode = null) { if (!isset(parent::$baseUrl)) { parent::resolveBaseUrl(); } + $this->storeCode = $storeCode; $this->transport = new CurlTransport(); $this->authorize(); @@ -72,7 +73,7 @@ public function __construct($storeCode = 'default') */ protected function authorize() { - $authUrl = parent::$baseUrl . 'rest/' . $this->storeCode . self::ADMIN_AUTH_URL; + $authUrl = $this->getFormattedUrl(self::ADMIN_AUTH_URL); $authCreds = [ 'username' => getenv('MAGENTO_ADMIN_USERNAME'), 'password' => getenv('MAGENTO_ADMIN_PASSWORD') @@ -97,7 +98,7 @@ protected function authorize() public function write($url, $data = [], $method = CurlInterface::POST, $headers = []) { $this->transport->write( - parent::$baseUrl . 'rest/' . $this->storeCode . '/' . trim($url, '/'), + $this->getFormattedUrl($url), json_encode($data, JSON_PRETTY_PRINT), $method, array_unique(array_merge($headers, $this->headers)) @@ -138,4 +139,19 @@ public function close() { $this->transport->close(); } + + /** + * Builds and returns URL for request, appending storeCode if needed. + * @param string $resource + * @return string + */ + public function getFormattedUrl($resource) + { + $urlResult = parent::$baseUrl . 'rest/'; + if ($this->storeCode != null) { + $urlResult .= $this->storeCode . "/"; + } + $urlResult.= trim($resource, "/"); + return $urlResult; + } } diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php index c72cafc25..63144a9ff 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/CurlHandler.php @@ -81,7 +81,7 @@ class CurlHandler * @param EntityDataObject $entityObject * @param string $storeCode */ - public function __construct($operation, $entityObject, $storeCode = 'default') + public function __construct($operation, $entityObject, $storeCode = null) { $this->operation = $operation; $this->entityObject = $entityObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php index 6e2f8d2b7..290a14c4e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Persist/DataPersistenceHandler.php @@ -63,7 +63,7 @@ public function __construct($entityObject, $dependentObjects = [], $customFields } else { $this->entityObject = clone $entityObject; } - $this->storeCode = 'default'; + $this->storeCode = null; foreach ($dependentObjects as $dependentObject) { $this->dependentObjects[] = $dependentObject->getCreatedObject(); @@ -96,16 +96,11 @@ public function createEntity($storeCode = null) * * @param string $updateDataName * @param array $updateDependentObjects - * @param string $storeCode * @return void */ - public function updateEntity($updateDataName, $updateDependentObjects = [], $storeCode = null) + public function updateEntity($updateDataName, $updateDependentObjects = []) { - if (!empty($storeCode)) { - $this->storeCode = $storeCode; - } - foreach ($updateDependentObjects as $dependentObject) { $this->dependentObjects[] = $dependentObject->getCreatedObject(); } @@ -146,14 +141,10 @@ public function getEntity($index = null, $storeCode = null) /** * Function which executes a delete request based on specific operation metadata * - * @param string $storeCode * @return void */ - public function deleteEntity($storeCode = null) + public function deleteEntity() { - if (!empty($storeCode)) { - $this->storeCode = $storeCode; - } $curlHandler = new CurlHandler('delete', $this->createdObject, $this->storeCode); $curlHandler->executeRequest($this->dependentObjects); } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 25bb65e07..cfffac9bc 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -449,12 +449,11 @@ public function magentoCLI($command) /** * Runs DELETE request to delete a Magento entity against the url given. * @param string $url - * @param int $storeCode * @return string */ - public function deleteEntityByUrl($url, $storeCode = null) + public function deleteEntityByUrl($url) { - $executor = new WebapiExecutor($storeCode); + $executor = new WebapiExecutor(null); $executor->write($url, [], CurlInterface::DELETE, []); $response = $executor->read(); $executor->close(); diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd index 93c07b991..1a7ea2d78 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/dataActions.xsd @@ -67,7 +67,6 @@ <xs:attribute ref="entity" use="required"/> <xs:attribute ref="createDataKey" use="required"/> <xs:attributeGroup ref="commonActionAttributes"/> - <xs:attribute ref="storeCode"/> </xs:complexType> <xs:complexType name="deleteDataType"> @@ -79,7 +78,6 @@ <xs:attribute ref="url"/> <xs:attribute ref="createDataKey"/> <xs:attributeGroup ref="commonActionAttributes"/> - <xs:attribute ref="storeCode"/> </xs:extension> </xs:simpleContent> </xs:complexType> diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 0d30717a2..73be6295a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -481,6 +481,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $visible = null; $command = null; $sortOrder = null; + $storeCode = null; $assertExpected = null; $assertActual = null; @@ -635,6 +636,9 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $visible = $customActionAttributes['visible']; } + if (isset($customActionAttributes['storeCode'])) { + $storeCode = $customActionAttributes['storeCode']; + } switch ($actionObject->getType()) { case "createData": $entity = $customActionAttributes['entity']; @@ -691,8 +695,8 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " ); } - if (isset($customActionAttributes['storeCode'])) { - $createEntityFunctionCall .= sprintf("\"%s\");\n", $customActionAttributes['storeCode']); + if (isset($storeCode)) { + $createEntityFunctionCall .= sprintf("\"%s\");\n", $storeCode); } else { $createEntityFunctionCall .= ");\n"; } @@ -716,34 +720,29 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " if (isset($customActionAttributes['createDataKey'])) { $key = $customActionAttributes['createDataKey']; //Add an informative statement to help the user debug test runs - $testSteps .= sprintf( + $contextSetter = sprintf( "\t\t$%s->amGoingTo(\"delete entity that has the createDataKey: %s\");\n", $actor, $key ); + $deleteEntityFunctionCall = ""; if ($hookObject) { - $testSteps .= sprintf("\t\t\$this->%s->deleteEntity();\n", $key); + $deleteEntityFunctionCall .= sprintf("\t\t\$this->%s->deleteEntity();\n", $key); } else { - $testSteps .= sprintf("\t\t$%s->deleteEntity();\n", $key); + $deleteEntityFunctionCall .= sprintf("\t\t$%s->deleteEntity();\n", $key); } + + $testSteps .= $contextSetter; + $testSteps .= $deleteEntityFunctionCall; } else { $output = sprintf( - "\t\t$%s->deleteEntityByUrl(%s", + "\t\t$%s->deleteEntityByUrl(%s);\n", $actor, $url ); - $storeCode = null; - if (isset($customActionAttributes["storeCode"])) { - $storeCode = $customActionAttributes["storeCode"]; - $output .= sprintf( - ", %s", - $storeCode - ); - } - $output .= ");\n"; - $output = $this->resolveEnvReferences($output, [$url, $storeCode]); - $testSteps .= $this->resolveTestVariable($output, [$url, $storeCode], null); + $output = $this->resolveEnvReferences($output, [$url]); + $testSteps .= $this->resolveTestVariable($output, [$url], null); } break; case "updateData": @@ -792,8 +791,8 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " ); } - if (isset($customActionAttributes['storeCode'])) { - $updateEntityFunctionCall .= sprintf("\"%s\");\n", $customActionAttributes['storeCode']); + if (isset($storeCode)) { + $updateEntityFunctionCall .= sprintf(", \"%s\");\n", $storeCode); } else { $updateEntityFunctionCall .= ");\n"; } @@ -854,13 +853,13 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " } if (isset($customActionAttributes['index'])) { - $getEntityFunctionCall .= sprintf("%s", (int)$customActionAttributes['index']); + $getEntityFunctionCall .= sprintf(", %s", (int)$customActionAttributes['index']); } else { $getEntityFunctionCall .= 'null'; } - if (isset($customActionAttributes['storeCode'])) { - $getEntityFunctionCall .= sprintf(", \"%s\");\n", $customActionAttributes['storeCode']); + if (isset($storeCode)) { + $getEntityFunctionCall .= sprintf(", \"%s\");\n", $storeCode); } else { $getEntityFunctionCall .= ");\n"; } From 3550ecd4efda5632ebb10f118c0c714f2235abd5 Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Wed, 21 Mar 2018 17:16:28 -0500 Subject: [PATCH 40/77] MQE-782: Fatal error is thrown when a group name conflicts with an existing suite name - Added getter for $filename to TestObject - Added check for matching suite and groups names to SuiteObjectExtractor MQE-782: Fatal error is thrown when a group name conflicts with an existing suite name - Fixed static check failures on line length MQE-782: Fatal error is thrown when a group name conflicts with an existing suite name - Modified exception message formatting for more readable output MQE-782: Fatal error is thrown when a group name conflicts with an existing suite name - Suppress Warnings for Cyclomatic and NPath SuiteObjectExtractor::parseSuiteDataIntoObjects --- .../Suite/Util/SuiteObjectExtractor.php | 15 +++++++++++++ .../Test/Objects/TestObject.php | 21 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php index 4ff0b25a3..4db1abfc7 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php @@ -40,6 +40,8 @@ public function __construct() * @param array $parsedSuiteData * @return array * @throws XmlException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function parseSuiteDataIntoObjects($parsedSuiteData) { @@ -65,6 +67,19 @@ public function parseSuiteDataIntoObjects($parsedSuiteData) $suiteHooks = []; + //Check for collisions between suite name and existing group name + $suiteName = $parsedSuite[self::NAME]; + $testGroupConflicts = TestObjectHandler::getInstance()->getTestsByGroup($suiteName); + if (!empty($testGroupConflicts)) { + $testGroupConflictsFileNames = ""; + foreach ($testGroupConflicts as $test) { + $testGroupConflictsFileNames .= $test->getFilename() . "\n"; + } + $exceptionmessage = "\"Suite names and Group names can not have the same value. \t\n" . + "Suite: \"{$suiteName}\" also exists as a group annotation in: \n{$testGroupConflictsFileNames}"; + throw new XmlException($exceptionmessage); + } + //extract include and exclude references $groupTestsToInclude = $parsedSuite[self::INCLUDE_TAG_NAME] ?? []; $groupTestsToExclude = $parsedSuite[self::EXCLUDE_TAG_NAME] ?? []; diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 70a138d35..818e87924 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -43,6 +43,13 @@ class TestObject */ private $hooks = []; + /** + * String of filename of test + * + * @var String + */ + private $filename; + /** * TestObject constructor. * @@ -50,13 +57,15 @@ class TestObject * @param ActionObject[] $parsedSteps * @param array $annotations * @param TestHookObject[] $hooks + * @param String $filename */ - public function __construct($name, $parsedSteps, $annotations, $hooks) + public function __construct($name, $parsedSteps, $annotations, $hooks, $filename = null) { $this->name = $name; $this->parsedSteps = $parsedSteps; $this->annotations = $annotations; $this->hooks = $hooks; + $this->filename = $filename; } /** @@ -69,6 +78,16 @@ public function getName() return $this->name; } + /** + * Getter for the Test Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for Codeception format name * From 2fe0d2a7aa6b6d032f1fdfb53ba129515996bd70 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Fri, 6 Apr 2018 18:33:27 +0300 Subject: [PATCH 41/77] MQE-743: Can't run tests without a store having "default" store code] - Fixed bad merge on getData function call. --- src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 73be6295a..cae931611 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -853,7 +853,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " } if (isset($customActionAttributes['index'])) { - $getEntityFunctionCall .= sprintf(", %s", (int)$customActionAttributes['index']); + $getEntityFunctionCall .= sprintf("%s", (int)$customActionAttributes['index']); } else { $getEntityFunctionCall .= 'null'; } From 00b21e38fd22d617eac2ff63cc5a930c32d4b361 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathanjosiah@gmail.com> Date: Fri, 6 Apr 2018 12:12:39 -0500 Subject: [PATCH 42/77] Added ability to use array entities as arguments. --- .../Test/Objects/ActionObjectTest.php | 29 +++++++++++++++++++ .../Test/Objects/ActionObject.php | 4 +++ 2 files changed, 33 insertions(+) diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php index 98646d0c4..aafc32fcf 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php @@ -255,6 +255,35 @@ public function testResolveDataInUserInput() $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } + /** + * {{EntityDataObject.values}} should be replaced with ["value1","value2"] + */ + public function testResolveArrayData() + { + // Set up mocks + $actionObject = new ActionObject('merge123', 'fillField', [ + 'selector' => '#selector', + 'userInput' => '{{EntityDataObject.values}}' + ]); + $entityDataObject = new EntityDataObject('EntityDataObject', 'test', [ + 'values' => [ + 'value1', + 'value2' + ] + ], [], '', ''); + $this->mockDataHandlerWithData($entityDataObject); + + // Call the method under test + $actionObject->resolveReferences(); + + // Verify + $expected = [ + 'selector' => '#selector', + 'userInput' => '["value1","value2"]' + ]; + $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); + } + /** * Action object should throw an exception if a reference to a parameterized selector has too few given args. */ diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 84f073c04..8841f6471 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -496,6 +496,10 @@ private function findAndReplaceReferences($objectHandler, $inputString) $replacement = $this->resolveParameterization($parameterized, $replacement, $match, $obj); + if (is_array($replacement)) { + $replacement = '["' . implode('","', $replacement) . '"]'; + } + $outputString = str_replace($match, $replacement, $outputString); } return $outputString; From 106201840261d4421f8aa9331eadc38fdcf7d73e Mon Sep 17 00:00:00 2001 From: Tom Erskine <terskine@magento.com> Date: Mon, 2 Apr 2018 16:24:40 -0500 Subject: [PATCH 43/77] MQE-876: Add Skipped Tests in Allure Report - TestObject now has getter for boolean for check if group annotation is skip - Test Generator generates skip code if the above getter returns true MQE-876: Add Skipped Tests in Allure Report - Fixed Static checks MQE-876: Add Skipped Tests in Allure Report - Refactored based on review MQE-876: Add Skipped Tests in Allure Report - Added newline to skip test generation to meet coding standards --- .../Test/Objects/TestObject.php | 13 +++++++++++++ .../Util/TestGenerator.php | 13 +++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index 818e87924..b2e4ee907 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -88,6 +88,19 @@ public function getFilename() return $this->filename; } + /** + * Getter for the skip_test boolean + * + * @return string + */ + public function isSkipped() + { + if (array_key_exists('group', $this->annotations) && (in_array("skip", $this->annotations['group']))) { + return true; + } + return false; + } + /** * Getter for Codeception format name * diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index cae931611..e1b15791f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1507,10 +1507,15 @@ private function generateTestPhp($test) $testName = str_replace(' ', '', $testName); $testAnnotations = $this->generateAnnotationsPhp($test->getAnnotations(), true); $dependencies = 'AcceptanceTester $I'; - try { - $steps = $this->generateStepsPhp($test->getOrderedActions()); - } catch (TestReferenceException $e) { - throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\""); + if ($test->isSkipped()) { + $steps = "\t\t" . '$scenario->skip("This test is skipped");' . "\n"; + $dependencies .= ', \Codeception\Scenario $scenario'; + } else { + try { + $steps = $this->generateStepsPhp($test->getOrderedActions()); + } catch (TestReferenceException $e) { + throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\""); + } } $testPhp .= $testAnnotations; From 274b2b0bd6c82d5b6e7ae42ec10d6bcd8e92cb7b Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Tue, 10 Apr 2018 22:25:46 +0300 Subject: [PATCH 44/77] =?UTF-8?q?MQE-913:=20Nested=20Element=20Assertions?= =?UTF-8?q?=20Don=E2=80=99t=20Support=20Action=20Group=20Replacement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed above bug. Attribute/argument resolution is now recursive. --- .../ActionGroupUsingNestedArgument.txt | 33 +++++++++ .../ActionGroup/BasicActionGroup.xml | 11 +++ .../TestModule/Test/ActionGroupTest.xml | 6 ++ .../Tests/ActionGroupGenerationTest.php | 11 +++ .../Test/Objects/ActionGroupObject.php | 72 +++++++++++++------ .../Test/Objects/ActionObject.php | 11 ++- 6 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt diff --git a/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt new file mode 100644 index 000000000..b978a3188 --- /dev/null +++ b/dev/tests/verification/Resources/ActionGroupUsingNestedArgument.txt @@ -0,0 +1,33 @@ +<?php +namespace Magento\AcceptanceTest\_default\Backend; + +use Magento\FunctionalTestingFramework\AcceptanceTester; +use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Persist\DataPersistenceHandler; +use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; +use \Codeception\Util\Locator; +use Yandex\Allure\Adapter\Annotation\Features; +use Yandex\Allure\Adapter\Annotation\Stories; +use Yandex\Allure\Adapter\Annotation\Title; +use Yandex\Allure\Adapter\Annotation\Description; +use Yandex\Allure\Adapter\Annotation\Parameter; +use Yandex\Allure\Adapter\Annotation\Severity; +use Yandex\Allure\Adapter\Model\SeverityLevel; +use Yandex\Allure\Adapter\Annotation\TestCaseId; + +/** + */ +class ActionGroupUsingNestedArgumentCest +{ + /** + * @Parameter(name = "AcceptanceTester", value="$I") + * @param AcceptanceTester $I + * @return void + * @throws \Exception + */ + public function ActionGroupUsingNestedArgument(AcceptanceTester $I) + { + $grabProductsActionGroup = $I->grabMultiple("selector"); + $I->assertCount(99, $grabProductsActionGroup); + } +} diff --git a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml index 1fec39772..94e7a6b60 100644 --- a/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml +++ b/dev/tests/verification/TestModule/ActionGroup/BasicActionGroup.xml @@ -48,4 +48,15 @@ </arguments> <see selector="{{SampleSection.oneParamElement(someArgument)}}" userInput="{{someArgument}}" stepKey="see1" /> </actionGroup> + + <actionGroup name="actionGroupWithNestedArgument"> + <arguments> + <argument name="count" defaultValue="10" type="string"/> + </arguments> + <grabMultiple selector="selector" stepKey="grabProducts"/> + <assertCount stepKey="assertCount"> + <expectedResult type="int">{{count}}</expectedResult> + <actualResult type="variable">grabProducts</actualResult> + </assertCount> + </actionGroup> </actionGroups> diff --git a/dev/tests/verification/TestModule/Test/ActionGroupTest.xml b/dev/tests/verification/TestModule/Test/ActionGroupTest.xml index 72617c858..94b9e74dd 100644 --- a/dev/tests/verification/TestModule/Test/ActionGroupTest.xml +++ b/dev/tests/verification/TestModule/Test/ActionGroupTest.xml @@ -114,4 +114,10 @@ <test name="ActionGroupWithStepKeyReferences"> <actionGroup ref="FunctionActionGroupWithStepKeyReferences" stepKey="actionGroup"/> </test> + + <test name="ActionGroupUsingNestedArgument"> + <actionGroup ref="actionGroupWithNestedArgument" stepKey="actionGroup"> + <argument name="count" value="99"/> + </actionGroup> + </test> </tests> diff --git a/dev/tests/verification/Tests/ActionGroupGenerationTest.php b/dev/tests/verification/Tests/ActionGroupGenerationTest.php index e62ea24a5..a26e9ad87 100644 --- a/dev/tests/verification/Tests/ActionGroupGenerationTest.php +++ b/dev/tests/verification/Tests/ActionGroupGenerationTest.php @@ -107,4 +107,15 @@ public function testActionGroupWithStepKeyReferences() { $this->generateAndCompareTest('ActionGroupWithStepKeyReferences'); } + + /** + * Test generation of a test referencing an action group that uses stepKey references (grabFrom/CreateData) + * + * @throws \Exception + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + */ + public function testActionGroupWithNestedArgument() + { + $this->generateAndCompareTest('ActionGroupUsingNestedArgument'); + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index 90be0a643..fdef1107e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -125,33 +125,15 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) { $resolvedActions = []; - // $regexPattern match on: $matches[0] {{section.element(arg.field)}} - // $matches[1] = section.element - // $matches[2] = arg.field - $regexPattern = '/{{([\w.\[\]]+)\(*([\w.$\',\s\[\]]+)*\)*}}/'; - foreach ($this->parsedActions as $action) { $varAttributes = array_intersect($this->varAttributes, array_keys($action->getCustomActionAttributes())); $newActionAttributes = []; if (!empty($varAttributes)) { - // 1 check to see if we have pertinent var - foreach ($varAttributes as $varAttribute) { - $attributeValue = $action->getCustomActionAttributes()[$varAttribute]; - preg_match_all($regexPattern, $attributeValue, $matches); - if (empty($matches[0])) { - continue; - } - - //get rid of full match {{arg.field(arg.field)}} - array_shift($matches); - - $newActionAttributes[$varAttribute] = $this->replaceAttributeArguments( - $arguments, - $attributeValue, - $matches - ); - } + $newActionAttributes = $this->resolveAttributesWithArguments( + $arguments, + $action->getCustomActionAttributes() + ); } // we append the action reference key to any linked action and the action's merge key as the user might @@ -159,7 +141,7 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) $resolvedActions[$action->getStepKey() . ucfirst($actionReferenceKey)] = new ActionObject( $action->getStepKey() . ucfirst($actionReferenceKey), $action->getType(), - array_merge($action->getCustomActionAttributes(), $newActionAttributes), + array_replace_recursive($action->getCustomActionAttributes(), $newActionAttributes), $action->getLinkedAction() == null ? null : $action->getLinkedAction() . ucfirst($actionReferenceKey), $action->getOrderOffset(), [self::ACTION_GROUP_ORIGIN_NAME => $this->name, @@ -170,6 +152,50 @@ private function getResolvedActionsWithArgs($arguments, $actionReferenceKey) return $resolvedActions; } + /** + * Resolves all references to arguments in attributes, and subAttributes. + * @param array $arguments + * @param array $attributes + * @return array + */ + private function resolveAttributesWithArguments($arguments, $attributes) + { + // $regexPattern match on: $matches[0] {{section.element(arg.field)}} + // $matches[1] = section.element + // $matches[2] = arg.field + $regexPattern = '/{{([\w.\[\]]+)\(*([\w.$\',\s\[\]]+)*\)*}}/'; + + $newActionAttributes = []; + foreach ($attributes as $attributeKey => $attributeValue) { + + if (is_array($attributeValue)) { + // attributes with child elements are parsed as an array, need make recursive call to resolve children + $newActionAttributes[$attributeKey] = $this->resolveAttributesWithArguments( + $arguments, + $attributeValue + ); + continue; + } + + preg_match_all($regexPattern, $attributeValue, $matches); + + if (empty($matches[0])) { + continue; + } + + //get rid of full match {{arg.field(arg.field)}} + array_shift($matches); + + $newActionAttributes[$attributeKey] = $this->replaceAttributeArguments( + $arguments, + $attributeValue, + $matches + ); + } + return $newActionAttributes; + + } + /** * Function that takes an array of replacement arguments, and matches them with args in an actionGroup's attribute. * Determines if the replacement arguments are persisted data, and replaces them accordingly. diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 84f073c04..9a55755cc 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -21,7 +21,16 @@ class ActionObject { const __ENV = "_ENV"; - const DATA_ENABLED_ATTRIBUTES = ["userInput", "parameterArray", "expected", "actual", "x", "y"]; + const DATA_ENABLED_ATTRIBUTES = [ + "userInput", + "parameterArray", + "expected", + "actual", + "x", + "y", + "expectedResult", + "actualResult" + ]; const SELECTOR_ENABLED_ATTRIBUTES = [ 'selector', 'dependentSelector', From eb6c9434a96fe881699049f83c7ca4b9804aa6db Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 9 Apr 2018 17:05:42 -0500 Subject: [PATCH 45/77] MQE-784: Update parser to read suites in from extensions - alter Root based file resolver to extend module fileresolver - refactor Module based fileresolver to expose utility method --- etc/di.xml | 2 +- .../Config/FileResolver/Module.php | 19 ++++++++++++++--- .../Config/FileResolver/Root.php | 21 +++++++++++++++---- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/etc/di.xml b/etc/di.xml index dfcfeafe7..e52c58b31 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -366,7 +366,7 @@ <item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item> </argument> <argument name="fileName" xsi:type="string">*.xml</argument> - <argument name="defaultScope" xsi:type="string">_suite</argument> + <argument name="defaultScope" xsi:type="string">Suite</argument> </arguments> </virtualType> diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php index c949aa961..7cff19c87 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Module.php @@ -20,7 +20,7 @@ class Module implements FileResolverInterface * * @var ModuleResolver */ - private $moduleResolver; + protected $moduleResolver; /** * Module constructor. @@ -40,6 +40,20 @@ public function __construct(ModuleResolver $moduleResolver = null) * @return array|\Iterator,\Countable */ public function get($filename, $scope) + { + $iterator = new File($this->getPaths($filename, $scope)); + return $iterator; + } + + /** + * Function which takes a string representing filename and a scope represnting directory scope to glob for matched + * patterns against. Returns the file matching the patterns given by the module resolver. + * + * @param string $filename + * @param string $scope + * @return array + */ + protected function getPaths($filename, $scope) { $modulesPath = $this->moduleResolver->getModulesPath(); $paths = []; @@ -48,7 +62,6 @@ public function get($filename, $scope) $paths = array_merge($paths, glob($path)); } - $iterator = new File($paths); - return $iterator; + return $paths; } } diff --git a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php index e728fa418..93e74a82c 100644 --- a/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php +++ b/src/Magento/FunctionalTestingFramework/Config/FileResolver/Root.php @@ -9,11 +9,13 @@ use Magento\FunctionalTestingFramework\Config\FileResolverInterface; use Magento\FunctionalTestingFramework\Util\Iterator\File; -class Root implements FileResolverInterface +class Root extends Module { + const ROOT_SUITE_DIR = "_suite"; /** - * Retrieve the list of configuration files with given name that relate to specified scope at the tests level + * Retrieve the list of configuration files with given name that relate to specified scope at the root level as well + * as any extension based suite configuration. * * @param string $filename * @param string $scope @@ -21,8 +23,19 @@ class Root implements FileResolverInterface */ public function get($filename, $scope) { - $paths = glob(dirname(TESTS_BP) . DIRECTORY_SEPARATOR . $scope . DIRECTORY_SEPARATOR . $filename); + // first pick up the root level test suite dir + $paths = glob( + dirname(TESTS_BP) . DIRECTORY_SEPARATOR . self::ROOT_SUITE_DIR + . DIRECTORY_SEPARATOR . $filename + ); - return new File($paths); + // then merge this path into the module based paths + // Since we are sharing this code with Module based resolution we will unncessarily glob against modules in the + // dev/tests dir tree, however as we plan to migrate to app/code this will be a temporary unneeded check. + $paths = array_merge($paths, $this->getPaths($filename, $scope)); + + // create and return the iterator for these file paths + $iterator = new File($paths); + return $iterator; } } From c204f13510a61dce289c5d4d2e7851ddd0bf068f Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 4 Apr 2018 17:18:33 -0500 Subject: [PATCH 46/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - alter SuiteGenerator to account for custom suite configuration --- .../Suite/SuiteGenerator.php | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index f87ab5c4c..31a2033e4 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -41,26 +41,37 @@ class SuiteGenerator */ private $groupClassGenerator; + /** + * Multidimensional array which represents a custom suite configuration (e.g. certain tests run within a suite etc.) + * + * @var array + */ + private $suiteReferences; + /** * SuiteGenerator constructor. + * + * @param array $suiteReferences */ - private function __construct() + private function __construct($suiteReferences) { $this->groupClassGenerator = new GroupClassGenerator(); + $this->suiteReferences = $suiteReferences; } /** * Singleton method which is used to retrieve the instance of the suite generator. * + * @param array $suiteReferences * @return SuiteGenerator */ - public static function getInstance() + public static function getInstance($suiteReferences = []) { if (!self::$SUITE_GENERATOR_INSTANCE) { // clear any previous configurations before any generation occurs. self::clearPreviousGroupPreconditions(); self::clearPreviousSessionConfigEntries(); - self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator(); + self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator($suiteReferences); } return self::$SUITE_GENERATOR_INSTANCE; @@ -79,6 +90,8 @@ public function generateAllSuites($testManifest) if (get_class($testManifest) == ParallelTestManifest::class) { /** @var ParallelTestManifest $testManifest */ $suites = $testManifest->getSorter()->getResultingSuites(); + } elseif (!empty($this->suiteReferences)) { + $suites = array_intersect_key($suites, $this->suiteReferences); } foreach ($suites as $suite) { @@ -96,11 +109,22 @@ public function getTestsReferencedInSuites() { $testsReferencedInSuites = []; $suites = SuiteObjectHandler::getInstance()->getAllObjects(); + + // see if we have a specific suite configuration. + if (!empty($this->suiteReferences)) { + $suites = array_intersect_key($suites, $this->suiteReferences); + } + foreach ($suites as $suite) { /** @var SuiteObject $suite */ $test_keys = array_keys($suite->getTests()); - $testToSuiteName = array_fill_keys($test_keys, [$suite->getName()]); + // see if we need to filter which tests we'll be generating. + if (array_key_exists($suite->getName(), $this->suiteReferences)) { + $test_keys = $this->suiteReferences[$suite->getName()] ?? $test_keys; + } + + $testToSuiteName = array_fill_keys($test_keys, [$suite->getName()]); $testsReferencedInSuites = array_merge_recursive($testsReferencedInSuites, $testToSuiteName); } @@ -135,7 +159,19 @@ public function generateSuiteFromObject($suiteObject) $groupNamespace = null; DirSetupUtil::createGroupDir($fullPath); - $this->generateRelevantGroupTests($suiteName, $suiteObject->getTests()); + + $relevantTests = $suiteObject->getTests(); + if (array_key_exists($suiteName, $this->suiteReferences)) { + $testReferences = $this->suiteReferences[$suiteName] ?? []; + $tmpRelevantTests = null; + array_walk($testReferences, function ($value) use (&$tmpRelevantTests, $relevantTests) { + $tmpRelevantTests[$value] = $relevantTests[$value]; + }); + + $relevantTests = $tmpRelevantTests ?? $relevantTests; + } + + $this->generateRelevantGroupTests($suiteName, $relevantTests); if ($suiteObject->requiresGroupFile()) { // if the suite requires a group file, generate it and set the namespace From dd7ff1487aacaf50333682d81f258c039f12f362 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Fri, 6 Apr 2018 16:32:32 -0500 Subject: [PATCH 47/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - refactor the custom test configuration to use tests/suites in the global scope --- .../Util/Sorter/ParallelGroupSorterTest.php | 12 +- .../Suite/Handlers/SuiteObjectHandler.php | 20 +++ .../Suite/SuiteGenerator.php | 140 ++++++++++++------ .../Util/Manifest/BaseTestManifest.php | 48 +++++- .../Util/Manifest/DefaultTestManifest.php | 30 ++-- .../Util/Manifest/ParallelTestManifest.php | 43 ++++-- .../Util/Manifest/SingleRunTestManifest.php | 15 +- .../Util/Manifest/TestManifestFactory.php | 25 ++-- .../Util/Sorter/ParallelGroupSorter.php | 78 +++++----- .../Util/TestGenerator.php | 31 ++-- 10 files changed, 267 insertions(+), 175 deletions(-) diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php index 444749ed2..1acd31522 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Util/Sorter/ParallelGroupSorterTest.php @@ -76,15 +76,6 @@ public function testSortWithSuites() AspectMock::double(TestObjectHandler::class, ['getInstance' => $mockHandler])->make(); - // mock a suite object - $mockSuite = AspectMock::double(SuiteObject::class, [ - 'getBeforeHook' => null, - 'getAfterHook' => null, - 'getName' => 'mockSuite1' - ])->make(); - $mockSuiteHandler = AspectMock::double(SuiteObjectHandler::class, ['getObject' => $mockSuite])->make(); - AspectMock::double(SuiteObjectHandler::class, ['getInstance' => $mockSuiteHandler]); - // create test to size array $sampleTestArray = [ 'test1' => 100, @@ -96,8 +87,7 @@ public function testSortWithSuites() // create mock suite references $sampleSuiteArray = [ - 'mockTest1' => ['mockSuite1'], - 'mockTest2' => ['mockSuite1'] + 'mockSuite1' => ['mockTest1', 'mockTest2'] ]; // perform sort diff --git a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php index 3e4e0a1b5..8930de98c 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Handlers/SuiteObjectHandler.php @@ -78,6 +78,26 @@ public function getAllObjects() return $this->suiteObjects; } + /** + * Function which return all tests referenced by suites. + * + * @return array + */ + public function getAllTestReferences() + { + $testsReferencedInSuites = []; + $suites = $this->getAllObjects(); + + foreach ($suites as $suite) { + /** @var SuiteObject $suite */ + $test_keys = array_keys($suite->getTests()); + $testToSuiteName = array_fill_keys($test_keys, [$suite->getName()]); + $testsReferencedInSuites = array_merge_recursive($testsReferencedInSuites, $testToSuiteName); + } + + return $testsReferencedInSuites; + } + /** * Method to parse all suite data xml into objects. * diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index 31a2033e4..959d4e8d6 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -6,14 +6,12 @@ namespace Magento\FunctionalTestingFramework\Suite; -use Magento\Framework\Phrase; -use Magento\Framework\Validator\Exception; use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\Manifest\BaseTestManifest; -use Magento\FunctionalTestingFramework\Util\Manifest\ParallelTestManifest; use Magento\FunctionalTestingFramework\Util\TestGenerator; use Symfony\Component\Yaml\Yaml; @@ -41,37 +39,26 @@ class SuiteGenerator */ private $groupClassGenerator; - /** - * Multidimensional array which represents a custom suite configuration (e.g. certain tests run within a suite etc.) - * - * @var array - */ - private $suiteReferences; - /** * SuiteGenerator constructor. - * - * @param array $suiteReferences */ - private function __construct($suiteReferences) + private function __construct() { $this->groupClassGenerator = new GroupClassGenerator(); - $this->suiteReferences = $suiteReferences; } /** * Singleton method which is used to retrieve the instance of the suite generator. * - * @param array $suiteReferences * @return SuiteGenerator */ - public static function getInstance($suiteReferences = []) + public static function getInstance() { if (!self::$SUITE_GENERATOR_INSTANCE) { // clear any previous configurations before any generation occurs. self::clearPreviousGroupPreconditions(); self::clearPreviousSessionConfigEntries(); - self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator($suiteReferences); + self::$SUITE_GENERATOR_INSTANCE = new SuiteGenerator(); } return self::$SUITE_GENERATOR_INSTANCE; @@ -86,17 +73,23 @@ public static function getInstance($suiteReferences = []) */ public function generateAllSuites($testManifest) { - $suites = SuiteObjectHandler::getInstance()->getAllObjects(); - if (get_class($testManifest) == ParallelTestManifest::class) { - /** @var ParallelTestManifest $testManifest */ - $suites = $testManifest->getSorter()->getResultingSuites(); - } elseif (!empty($this->suiteReferences)) { - $suites = array_intersect_key($suites, $this->suiteReferences); + $suites = array_keys(SuiteObjectHandler::getInstance()->getAllObjects()); + if ($testManifest != null) { + $suites = $testManifest->getSuiteConfig(); } - foreach ($suites as $suite) { - // during a parallel config run we must generate only after we have data around how a suite will be split - $this->generateSuiteFromObject($suite); + foreach ($suites as $suiteName => $suiteContent) { + $firstElement = array_values($suiteContent)[0]; + + // if the first element is a string we know that we simply have an array of tests + if (is_string($firstElement)) { + $this->generateSuiteFromTest($suiteName, $suiteContent); + } + + // if our first element is an array we know that we have split the suites + if (is_array($firstElement)) { + $this->generateSplitSuiteFromTest($suiteName, $suiteContent); + } } } @@ -141,47 +134,96 @@ public function getTestsReferencedInSuites() public function generateSuite($suiteName) { /**@var SuiteObject $suite **/ - $suite = SuiteObjectHandler::getInstance()->getObject($suiteName); - $this->generateSuiteFromObject($suite); + $this->generateSuiteFromTest($suiteName, []); } /** - * Function which takes a suite object and generates all relevant supporting files and classes. + * Function which takes a suite name and a set of test names. The function then generates all relevant supporting + * files and classes for the suite. The function takes an optional argument for suites which are split by a parallel + * run so that any pre/post conditions can be duplicated. * - * @param SuiteObject $suiteObject + * @param string $suiteName + * @param array $tests + * @param string $originalSuiteName * @return void */ - public function generateSuiteFromObject($suiteObject) + private function generateSuiteFromTest($suiteName, $tests = [], $originalSuiteName = null) { - $suiteName = $suiteObject->getName(); $relativePath = TestGenerator::GENERATED_DIR . DIRECTORY_SEPARATOR . $suiteName; - $fullPath = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . $relativePath; - $groupNamespace = null; + $fullPath = TESTS_MODULE_PATH . DIRECTORY_SEPARATOR . $relativePath . DIRECTORY_SEPARATOR; DirSetupUtil::createGroupDir($fullPath); - $relevantTests = $suiteObject->getTests(); - if (array_key_exists($suiteName, $this->suiteReferences)) { - $testReferences = $this->suiteReferences[$suiteName] ?? []; - $tmpRelevantTests = null; - array_walk($testReferences, function ($value) use (&$tmpRelevantTests, $relevantTests) { - $tmpRelevantTests[$value] = $relevantTests[$value]; - }); - - $relevantTests = $tmpRelevantTests ?? $relevantTests; + $relevantTests = []; + if (!empty($tests)) { + foreach ($tests as $testName) { + $relevantTests[$testName] = TestObjectHandler::getInstance()->getObject($testName); + } + } else { + $relevantTests = SuiteObjectHandler::getInstance()->getObject($suiteName)->getTests(); } $this->generateRelevantGroupTests($suiteName, $relevantTests); - - if ($suiteObject->requiresGroupFile()) { - // if the suite requires a group file, generate it and set the namespace - $groupNamespace = $this->groupClassGenerator->generateGroupClass($suiteObject); - } + $groupNamespace = $this->generateGroupFile($suiteName, $relevantTests, $originalSuiteName); $this->appendEntriesToConfig($suiteName, $fullPath, $groupNamespace); print "Suite ${suiteName} generated to ${relativePath}.\n"; } + /** + * Function for generating split groups of tests (following a parallel execution). Takes a paralle suite config + * and generates applicable suites. + * + * @param string $suiteName + * @param array $suiteContent + * @return void + */ + private function generateSplitSuiteFromTest($suiteName, $suiteContent) + { + foreach ($suiteContent as $suiteSplitName => $tests) { + $this->generateSuiteFromTest($suiteSplitName, $tests, $suiteName); + } + } + + /** + * Function which takes a suite name, array of tests, and an original suite name. The function takes these args + * and generates a group file which captures suite level preconditions. + * + * @param string $suiteName + * @param array $tests + * @param string $originalSuiteName + * @return null|string + */ + private function generateGroupFile($suiteName, $tests, $originalSuiteName) + { + // if there's an original suite name we know that this test came from a split group. + if ($originalSuiteName) { + // create the new suite object + /** @var SuiteObject $originalSuite */ + $originalSuite = SuiteObjectHandler::getInstance()->getObject($originalSuiteName); + $suiteObject = new SuiteObject( + $suiteName, + $tests, + [], + $originalSuite->getHooks() + ); + } else { + $suiteObject = SuiteObjectHandler::getInstance()->getObject($suiteName); + // we have to handle the case when there is a custom configuration for an existing suite. + if (count($suiteObject->getTests()) != count($tests)) { + return $this->generateGroupFile($suiteName, $tests, $suiteName); + } + } + + if (!$suiteObject->requiresGroupFile()) { + // if we do not require a group file we don't need a namespace + return null; + } + + // if the suite requires a group file, generate it and set the namespace + return $this->groupClassGenerator->generateGroupClass($suiteObject); + } + /** * Function which accepts a suite name and suite path and appends a new group entry to the codeception.yml.dist * file in order to register the set of tests as a new group. Also appends group object location if required @@ -255,7 +297,7 @@ private static function clearPreviousSessionConfigEntries() private function generateRelevantGroupTests($path, $tests) { $testGenerator = TestGenerator::getInstance($path, $tests); - $testGenerator->createAllTestFiles('suite'); + $testGenerator->createAllTestFiles(null, []); } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php index d8c7c397b..c3c0ec805 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; +use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; abstract class BaseTestManifest @@ -24,16 +26,25 @@ abstract class BaseTestManifest */ protected $relativeDirPath; + /** + * Suite configuration in the format suite name to test name. Overwritten during a custom configuration. + * + * @var array + */ + protected $suiteConfiguration; + /** * TestManifest constructor. * * @param string $path * @param string $runConfig + * @param array $suiteConfiguration */ - public function __construct($path, $runConfig) + public function __construct($path, $runConfig, $suiteConfiguration) { $this->runTypeConfig = $runConfig; $this->relativeDirPath = substr($path, strlen(dirname(dirname(TESTS_BP))) + 1); + $this->suiteConfiguration = $suiteConfiguration; } /** @@ -57,9 +68,38 @@ abstract public function addTest($testObject); /** * Function which generates the actual manifest(s) once the relevant tests have been added to the array. * - * @param array $testsReferencedInSuites - * @param int|null $nodes * @return void */ - abstract public function generate($testsReferencedInSuites, $nodes = null); + abstract public function generate(); + + /** + * Getter for the suite configuration. + * + * @return array + */ + public function getSuiteConfig() + { + $suiteToTestNames = []; + + if (empty($this->suiteConfiguration)) { + // if there is no configuration passed we can assume the user wants all suites generated as specified. + foreach (SuiteObjectHandler::getInstance()->getAllObjects() as $suite => $suiteObj) { + /** @var SuiteObject $suitObj */ + $suiteToTestNames[$suite] = array_keys($suiteObj->getTests()); + } + } else { + // we need to loop through the configuration to make sure we capture suites with no specific config + foreach ($this->suiteConfiguration as $suiteName => $test) { + if (empty($test)) { + $suiteToTestNames[$suiteName] = + array_keys(SuiteObjectHandler::getInstance()->getObject($suiteName)->getTests()); + continue; + } + + $suiteToTestNames[$suiteName] = $test; + } + } + + return $suiteToTestNames; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index d577be92f..2417fec3d 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -36,16 +36,17 @@ class DefaultTestManifest extends BaseTestManifest /** * DefaultTestManifest constructor. - * @param string $manifestPath + * + * @param array $suiteConfiguration * @param string $testPath */ - public function __construct($manifestPath, $testPath) + public function __construct($suiteConfiguration, $testPath) { - $this->manifestPath = $manifestPath . DIRECTORY_SEPARATOR . 'testManifest.txt'; + $this->manifestPath = dirname($testPath) . DIRECTORY_SEPARATOR . 'testManifest.txt'; $this->cleanManifest($this->manifestPath); - parent::__construct($testPath, self::DEFAULT_CONFIG); - $fileResource = fopen($this->manifestPath, 'a'); - fclose($fileResource); + parent::__construct($testPath, self::DEFAULT_CONFIG, $suiteConfiguration); +// $fileResource = fopen($this->manifestPath, 'a'); +// fclose($fileResource); } /** @@ -62,11 +63,9 @@ public function addTest($testObject) /** * Function which outputs a list of all test files to the defined testManifest.txt file. * - * @param array $testsReferencedInSuites - * @param int|null $nodes * @return void */ - public function generate($testsReferencedInSuites, $nodes = null) + public function generate() { $fileResource = fopen($this->manifestPath, 'a'); @@ -75,7 +74,7 @@ public function generate($testsReferencedInSuites, $nodes = null) fwrite($fileResource, $line . PHP_EOL); } - $this->generateSuiteEntries($testsReferencedInSuites, $fileResource); + $this->generateSuiteEntries($fileResource); fclose($fileResource); } @@ -83,19 +82,12 @@ public function generate($testsReferencedInSuites, $nodes = null) /** * Function which takes the test suites passed to the manifest and generates corresponding entries in the manifest. * - * @param array $testsReferencedInSuites * @param resource $fileResource * @return void */ - protected function generateSuiteEntries($testsReferencedInSuites, $fileResource) + protected function generateSuiteEntries($fileResource) { - // get the names of available suites - $suiteNames = []; - array_walk($testsReferencedInSuites, function ($value) use (&$suiteNames) { - $suiteNames = array_unique(array_merge($value, $suiteNames)); - }); - - foreach ($suiteNames as $suiteName) { + foreach ($this->getSuiteConfig() as $suiteName => $tests) { $line = "-g {$suiteName}"; fwrite($fileResource, $line . PHP_EOL); } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php index 9c26e270c..b5087202f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; +use Codeception\Suite; use Magento\Framework\Exception\RuntimeException; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; @@ -25,6 +26,13 @@ class ParallelTestManifest extends BaseTestManifest */ private $testNameToSize = []; + /** + * Class variable to store resulting group config. + * + * @var array + */ + private $testGroups; + /** * An instance of the group sorter which will take suites and tests organizing them to be run together. * @@ -42,14 +50,14 @@ class ParallelTestManifest extends BaseTestManifest /** * TestManifest constructor. * - * @param string $manifestPath + * @param array $suiteConfiguration * @param string $testPath */ - public function __construct($manifestPath, $testPath) + public function __construct($suiteConfiguration, $testPath) { - $this->dirPath = $manifestPath . DIRECTORY_SEPARATOR . 'groups'; + $this->dirPath = dirname($testPath) . DIRECTORY_SEPARATOR . 'groups'; $this->parallelGroupSorter = new ParallelGroupSorter(); - parent::__construct($testPath, self::PARALLEL_CONFIG); + parent::__construct($testPath, self::PARALLEL_CONFIG, $suiteConfiguration); } /** @@ -64,22 +72,33 @@ public function addTest($testObject) } /** - * Function which generates the actual manifest once the relevant tests have been added to the array. + * Function which generates test groups based on arg passed. The function builds groups using the args as an upper + * limit. * - * @param array $testsReferencedInSuites * @param int $lines * @return void */ - public function generate($testsReferencedInSuites, $lines = null) + public function createTestGroups($lines) { - DirSetupUtil::createGroupDir($this->dirPath); - $testGroups = $this->parallelGroupSorter->getTestsGroupedBySize( - $testsReferencedInSuites, + $this->testGroups = $this->parallelGroupSorter->getTestsGroupedBySize( + $this->getSuiteConfig(), $this->testNameToSize, $lines ); - foreach ($testGroups as $groupNumber => $groupContents) { + $this->suiteConfiguration = $this->parallelGroupSorter->getResultingSuiteConfig(); + } + + /** + * Function which generates the actual manifest once the relevant tests have been added to the array. + * + * @return void + */ + public function generate() + { + DirSetupUtil::createGroupDir($this->dirPath); + + foreach ($this->testGroups as $groupNumber => $groupContents) { $this->generateGroupFile($groupContents, $groupNumber); } } @@ -104,7 +123,7 @@ public function getSorter() */ private function generateGroupFile($testGroup, $nodeNumber) { - $suites = $this->parallelGroupSorter->getResultingSuites(); + $suites = $this->parallelGroupSorter->getResultingSuiteConfig(); foreach ($testGroup as $entryName => $testValue) { $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "group{$nodeNumber}.txt", 'a'); diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php index f4fb33b5e..94004e3be 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/SingleRunTestManifest.php @@ -12,30 +12,27 @@ class SingleRunTestManifest extends DefaultTestManifest /** * SingleRunTestManifest constructor. - * @param string $manifestPath + * + * @param array $suiteConfiguration * @param string $testPath */ - public function __construct($manifestPath, $testPath) + public function __construct($suiteConfiguration, $testPath) { - parent::__construct($manifestPath, $testPath); + parent::__construct($suiteConfiguration, $testPath); $this->runTypeConfig = self::SINGLE_RUN_CONFIG; - $fileResource = fopen($this->manifestPath, 'a'); - fclose($fileResource); } /** * Function which generates the actual manifest once the relevant tests have been added to the array. * - * @param array $testsReferencedInSuites - * @param int|null $nodes * @return void */ - public function generate($testsReferencedInSuites, $nodes = null) + public function generate() { $fileResource = fopen($this->manifestPath, 'a'); $line = $this->relativeDirPath . DIRECTORY_SEPARATOR; fwrite($fileResource, $line . PHP_EOL); - $this->generateSuiteEntries($testsReferencedInSuites, $fileResource); + $this->generateSuiteEntries($fileResource); fclose($fileResource); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php index 4344a96a1..51b34bc80 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/TestManifestFactory.php @@ -6,6 +6,9 @@ namespace Magento\FunctionalTestingFramework\Util\Manifest; +use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; +use Magento\FunctionalTestingFramework\Util\TestGenerator; + class TestManifestFactory { /** @@ -19,26 +22,28 @@ private function __construct() /** * Static function which takes path and config to return the appropriate manifest output type. * - * @param String $manifestPath - * @param String $testPath * @param String $runConfig + * @param array $suiteConfiguration + * @param String $testPath * @return BaseTestManifest */ - public static function makeManifest($manifestPath, $testPath, $runConfig) + public static function makeManifest($runConfig, $suiteConfiguration, $testPath = TestGenerator::DEFAULT_DIR) { + $testDirFullPath = TESTS_MODULE_PATH + . DIRECTORY_SEPARATOR + . TestGenerator::GENERATED_DIR + . DIRECTORY_SEPARATOR + . $testPath; + switch ($runConfig) { case 'singleRun': - return new SingleRunTestManifest($manifestPath, $testPath); + return new SingleRunTestManifest($suiteConfiguration, $testDirFullPath); case 'parallel': - return new ParallelTestManifest($manifestPath, $testPath); - - case 'suite': - // the suite does not have its own manifest but instead is handled by the other suite types. - return null; + return new ParallelTestManifest($suiteConfiguration, $testDirFullPath); default: - return new DefaultTestManifest($manifestPath, $testPath); + return new DefaultTestManifest($suiteConfiguration, $testDirFullPath); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index 9d51bf2e1..735a36447 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -6,8 +6,6 @@ namespace Magento\FunctionalTestingFramework\Util\Sorter; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; -use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; -use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; class ParallelGroupSorter @@ -17,7 +15,7 @@ class ParallelGroupSorter * * @var array */ - private $compositeSuiteObjects = []; + private $suiteConfig = []; /** * ParallelGroupSorter constructor. @@ -30,13 +28,13 @@ public function __construct() /** * Function which returns tests and suites split according to desired number of lines divded into groups. * - * @param array $testNameToSuiteName + * @param array $suiteConfiguration * @param array $testNameToSize * @param integer $lines * @return array * @throws TestFrameworkException */ - public function getTestsGroupedBySize($testNameToSuiteName, $testNameToSize, $lines) + public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $lines) { // we must have the lines argument in order to create the test groups if ($lines == 0) { @@ -47,7 +45,7 @@ public function getTestsGroupedBySize($testNameToSuiteName, $testNameToSize, $li } $testGroups = []; - $splitSuiteNamesToTests = $this->createGroupsWithinSuites($testNameToSuiteName, $lines); + $splitSuiteNamesToTests = $this->createGroupsWithinSuites($suiteConfiguration, $lines); $splitSuiteNamesToSize = $this->getSuiteToSize($splitSuiteNamesToTests); $entriesForGeneration = array_merge($testNameToSize, $splitSuiteNamesToSize); arsort($entriesForGeneration); @@ -76,9 +74,9 @@ public function getTestsGroupedBySize($testNameToSuiteName, $testNameToSize, $li * * @return array */ - public function getResultingSuites() + public function getResultingSuiteConfig() { - return $this->compositeSuiteObjects; + return $this->suiteConfig; } /** @@ -139,22 +137,14 @@ private function getClosestLineCount($testGroup, $desiredValue) * Function which takes an array of test names mapped to suite name and a size limitation for each group of tests. * The function divides suites that are over the specified limit and returns the resulting suites in an array. * - * @param array $testNameToSuiteName + * @param array $suiteConfiguration * @param integer $lineLimit * @return array */ - private function createGroupsWithinSuites($testNameToSuiteName, $lineLimit) + private function createGroupsWithinSuites($suiteConfiguration, $lineLimit) { - $suiteNameToTestNames = []; - $suiteNameToSize = []; - array_walk($testNameToSuiteName, function ($value, $key) use (&$suiteNameToTestNames, &$suiteNameToSize) { - $testActionCount = TestObjectHandler::getInstance()->getObject($key)->getTestActionCount(); - foreach ($value as $suite) { - $suiteNameToTestNames[$suite][$key] = $testActionCount; - $currentSize = $suiteNameToSize[$suite] ?? 0; - $suiteNameToSize[$suite] = $currentSize + $testActionCount; - } - }); + $suiteNameToTestSize = $this->getSuiteNameToTestSize($suiteConfiguration); + $suiteNameToSize = $this->getSuiteToSize($suiteNameToTestSize); // divide the suites up within the array $suitesForResize = array_filter($suiteNameToSize, function ($val) use ($lineLimit) { @@ -162,17 +152,17 @@ private function createGroupsWithinSuites($testNameToSuiteName, $lineLimit) }); // remove the suites for resize from the original list - $remainingSuites = array_diff_key($suiteNameToTestNames, $suitesForResize); + $remainingSuites = array_diff_key($suiteNameToTestSize, $suitesForResize); foreach ($remainingSuites as $remainingSuite => $tests) { - $this->addSuiteAsObject($remainingSuite, null, null); + $this->addSuiteToConfig($remainingSuite, null, $tests); } $resultingGroups = []; foreach ($suitesForResize as $suiteName => $suiteSize) { $resultingGroups = array_merge( $resultingGroups, - $this->splitTestSuite($suiteName, $suiteNameToTestNames[$suiteName], $lineLimit) + $this->splitTestSuite($suiteName, $suiteNameToTestSize[$suiteName], $lineLimit) ); } @@ -180,6 +170,26 @@ private function createGroupsWithinSuites($testNameToSuiteName, $lineLimit) return array_merge($remainingSuites, $resultingGroups); } + /** + * Function which takes the given suite configuration and returns an array of suite to test size. + * + * @param array $suiteConfiguration + * @return array + */ + private function getSuiteNameToTestSize($suiteConfiguration) + { + $suiteNameToTestSize = []; + foreach ($suiteConfiguration as $suite => $test) { + foreach ($test as $testName) { + $suiteNameToTestSize[$suite][$testName] = TestObjectHandler::getInstance() + ->getObject($testName) + ->getTestActionCount(); + } + } + + return $suiteNameToTestSize; + } + /** * Function which takes a multidimensional array containing a suite name mapped to an array of tests names as keys * with their sizes as values. The function returns an array of suite name to size of the corresponding mapped @@ -227,7 +237,7 @@ private function splitTestSuite($suiteName, $tests, $lineLimit) $group = $this->createTestGroup($lineLimit, $test, $size, $availableTests); $split_suites["{$suiteName}_${split_count}"] = $group; - $this->addSuiteAsObject($suiteName, "{$suiteName}_${split_count}", $group); + $this->addSuiteToConfig($suiteName, "{$suiteName}_${split_count}", $group); $availableTests = array_diff($availableTests, $group); $split_count++; @@ -246,25 +256,13 @@ private function splitTestSuite($suiteName, $tests, $lineLimit) * @param array $tests * @return void */ - private function addSuiteAsObject($originalSuiteName, $newSuiteName, $tests) + private function addSuiteToConfig($originalSuiteName, $newSuiteName, $tests) { - /** @var SuiteObject $originalSuite */ - $originalSuite = SuiteObjectHandler::getInstance()->getObject($originalSuiteName); - if ($newSuiteName == null && $tests == null) { - $this->compositeSuiteObjects[$originalSuiteName] = $originalSuite; + if ($newSuiteName == null) { + $this->suiteConfig[$originalSuiteName] = array_keys($tests); return; } - $newSuiteTests = []; - foreach ($tests as $test => $lines) { - $newSuiteTests[$test] = TestObjectHandler::getInstance()->getObject($test); - } - - $this->compositeSuiteObjects[$newSuiteName] = new SuiteObject( - $newSuiteName, - $newSuiteTests, - [], - $originalSuite->getHooks() - ); + $this->suiteConfig[$originalSuiteName][$newSuiteName] = array_keys($tests); } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index e1b15791f..d34e41083 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -8,6 +8,7 @@ use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\ActionGroupObjectHandler; use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler; use Magento\FunctionalTestingFramework\Test\Objects\ActionGroupObject; @@ -151,30 +152,23 @@ private function createCestFile($testPhp, $filename) * Assemble ALL PHP strings using the assembleAllTestPhp function. Loop over and pass each array item * to the createCestFile function. * - * @param string $runConfig - * @param int $nodes - * @param TestObject[] $testsToIgnore - * @return BaseTestManifest + * @param BaseTestManifest $testManifest + * @param array $testsToIgnore + * @return void * @throws TestReferenceException * @throws \Exception */ - public function createAllTestFiles($runConfig = null, $nodes = null, $testsToIgnore = []) + public function createAllTestFiles($testManifest = null, $testsToIgnore = null) { DirSetupUtil::createGroupDir($this->exportDirectory); + if ($testsToIgnore === null) { + $testsToIgnore = SuiteObjectHandler::getInstance()->getAllTestReferences(); + } - // create our manifest file here - $testManifest = TestManifestFactory::makeManifest( - dirname($this->exportDirectory), - $this->exportDirectory, - $runConfig - ); - - $testPhpArray = $this->assembleAllTestPhp($testManifest, $nodes, $testsToIgnore); + $testPhpArray = $this->assembleAllTestPhp($testManifest, $testsToIgnore); foreach ($testPhpArray as $testPhpFile) { $this->createCestFile($testPhpFile[1], $testPhpFile[0]); } - - return $testManifest; } /** @@ -216,11 +210,10 @@ private function assembleTestPhp($testObject) * Load ALL Test objects. Loop over and pass each to the assembleTestPhp function. * * @param BaseTestManifest $testManifest - * @param int $nodes * @param array $testsToIgnore * @return array */ - private function assembleAllTestPhp($testManifest, $nodes, array $testsToIgnore) + private function assembleAllTestPhp($testManifest, array $testsToIgnore) { /** @var TestObject[] $testObjects */ $testObjects = $this->loadAllTestObjects(); @@ -242,10 +235,6 @@ private function assembleAllTestPhp($testManifest, $nodes, array $testsToIgnore) } } - if ($testManifest != null) { - $testManifest->generate($testsToIgnore, intval($nodes)); - } - return $cestPhpArray; } From 1e8fbd31d84c9933020dd126dc884d05bfce4c56 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 9 Apr 2018 10:52:46 -0500 Subject: [PATCH 48/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - add validation around custom test configuration --- .../Suite/SuiteGenerator.php | 30 +++++++++++++++++++ .../Util/Manifest/DefaultTestManifest.php | 2 -- .../Util/TestGenerator.php | 11 +++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php index 959d4e8d6..9c96742b4 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Suite; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Suite\Generators\GroupClassGenerator; use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject; @@ -156,6 +157,7 @@ private function generateSuiteFromTest($suiteName, $tests = [], $originalSuiteNa $relevantTests = []; if (!empty($tests)) { + $this->validateTestsReferencedInSuite($suiteName, $tests, $originalSuiteName); foreach ($tests as $testName) { $relevantTests[$testName] = TestObjectHandler::getInstance()->getObject($testName); } @@ -170,6 +172,34 @@ private function generateSuiteFromTest($suiteName, $tests = [], $originalSuiteNa print "Suite ${suiteName} generated to ${relativePath}.\n"; } + /** + * Function which validates tests passed in as custom configuration against the configuration defined by the user to + * prevent possible invalid test configurations from executing. + * + * @param string $suiteName + * @param array $testsReferenced + * @param string $originalSuiteName + * @return void + * @throws TestReferenceException + */ + private function validateTestsReferencedInSuite($suiteName, $testsReferenced, $originalSuiteName) + { + $suiteRef = $originalSuiteName ?? $suiteName; + $possibleTestRef = SuiteObjectHandler::getInstance()->getObject($suiteRef)->getTests(); + $invalidTestRef = null; + $errorMsg = "Cannot reference tests not declared as part of {$suiteRef}:\n "; + + array_walk($testsReferenced, function ($value) use (&$invalidTestRef, $possibleTestRef, &$errorMsg) { + if (!array_key_exists($value, $possibleTestRef)) { + $invalidTestRef.= "\t{$value}\n"; + } + }); + + if ($invalidTestRef != null) { + throw new TestReferenceException($errorMsg . $invalidTestRef); + } + } + /** * Function for generating split groups of tests (following a parallel execution). Takes a paralle suite config * and generates applicable suites. diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php index 2417fec3d..f6af0fe88 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/DefaultTestManifest.php @@ -45,8 +45,6 @@ public function __construct($suiteConfiguration, $testPath) $this->manifestPath = dirname($testPath) . DIRECTORY_SEPARATOR . 'testManifest.txt'; $this->cleanManifest($this->manifestPath); parent::__construct($testPath, self::DEFAULT_CONFIG, $suiteConfiguration); -// $fileResource = fopen($this->manifestPath, 'a'); -// fclose($fileResource); } /** diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index d34e41083..e0ff13aba 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -217,6 +217,17 @@ private function assembleAllTestPhp($testManifest, array $testsToIgnore) { /** @var TestObject[] $testObjects */ $testObjects = $this->loadAllTestObjects(); + + // We need to check the tests passed in to insure that we can generate them in the current context. + $invalidTestObjects = array_intersect_key($testObjects, $testsToIgnore); + if (!empty($invalidTestObjects)) { + $errorMsg = "Cannot reference the following tests for generation without accompanying suite:\n"; + array_walk($invalidTestObjects, function ($value, $key) use (&$errorMsg) { + $errorMsg.= "\t{$key}\n"; + }); + throw new TestReferenceException($errorMsg); + } + $testObjects = array_diff_key($testObjects, $testsToIgnore); $cestPhpArray = []; From 4f847ddd2bb9bb20e9c0733e44909694464ad609 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 9 Apr 2018 15:26:55 -0500 Subject: [PATCH 49/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - fix generation error during non custom generation - fix multidimensional references issue --- .../Util/Manifest/ParallelTestManifest.php | 35 ++++++++++++++++--- .../Util/TestGenerator.php | 34 +++++++++--------- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php index b5087202f..485c506dd 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php @@ -14,6 +14,8 @@ use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Filesystem\DirSetupUtil; use Magento\FunctionalTestingFramework\Util\Sorter\ParallelGroupSorter; +use RecursiveArrayIterator; +use RecursiveIteratorIterator; class ParallelTestManifest extends BaseTestManifest { @@ -97,9 +99,10 @@ public function createTestGroups($lines) public function generate() { DirSetupUtil::createGroupDir($this->dirPath); + $suites = $this->getFlattenedSuiteConfiguration($this->parallelGroupSorter->getResultingSuiteConfig()); foreach ($this->testGroups as $groupNumber => $groupContents) { - $this->generateGroupFile($groupContents, $groupNumber); + $this->generateGroupFile($groupContents, $groupNumber, $suites); } } @@ -115,15 +118,16 @@ public function getSorter() /** * Function which takes an array containing entries representing the test execution as well as the associated group - * for the entry in order to generate a txt file used by devops for parllel execution in Jenkins. + * for the entry in order to generate a txt file used by devops for parllel execution in Jenkins. The results + * are checked against a flattened list of suites in order to generate proper entries. * * @param array $testGroup * @param int $nodeNumber + * @param array $suites * @return void */ - private function generateGroupFile($testGroup, $nodeNumber) + private function generateGroupFile($testGroup, $nodeNumber, $suites) { - $suites = $this->parallelGroupSorter->getResultingSuiteConfig(); foreach ($testGroup as $entryName => $testValue) { $fileResource = fopen($this->dirPath . DIRECTORY_SEPARATOR . "group{$nodeNumber}.txt", 'a'); @@ -137,4 +141,27 @@ private function generateGroupFile($testGroup, $nodeNumber) fclose($fileResource); } } + + /** + * Function which recusrively parses a given potentially multidimensional array of suites containing their split + * groups. The result is a flattened array of suite names to relevant tests for generation of the manifest. + * + * @param array $multiDimensionalSuites + * @return array + */ + private function getFlattenedSuiteConfiguration($multiDimensionalSuites) + { + $suites = []; + foreach ($multiDimensionalSuites as $suiteName => $suiteContent) { + $value = array_values($suiteContent)[0]; + if (is_array($value)) { + $suites = array_merge($suites, $this->getFlattenedSuiteConfiguration($suiteContent)); + continue; + } + + $suites[$suiteName] = $suiteContent; + } + + return $suites; + } } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index e0ff13aba..4590daefe 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -113,14 +113,28 @@ public function getExportDir() } /** - * Load all Test files as Objects using the Test Object Handler. + * Load all Test files as Objects using the Test Object Handler, additionally validates test references being loaded + * for validity. * + * @param array $testsToIgnore * @return array */ - private function loadAllTestObjects() + private function loadAllTestObjects($testsToIgnore) { if ($this->tests === null || empty($this->tests)) { - return TestObjectHandler::getInstance()->getAllObjects(); + $testObjects = TestObjectHandler::getInstance()->getAllObjects(); + return array_diff_key($testObjects, $testsToIgnore); + } + + // If we have a custom configuration, we need to check the tests passed in to insure that we can generate + // them in the current context. + $invalidTestObjects = array_intersect_key($this->tests, $testsToIgnore); + if (!empty($invalidTestObjects)) { + $errorMsg = "Cannot reference the following tests for generation without accompanying suite:\n"; + array_walk($invalidTestObjects, function ($value, $key) use (&$errorMsg) { + $errorMsg.= "\t{$key}\n"; + }); + throw new TestReferenceException($errorMsg); } return $this->tests; @@ -216,19 +230,7 @@ private function assembleTestPhp($testObject) private function assembleAllTestPhp($testManifest, array $testsToIgnore) { /** @var TestObject[] $testObjects */ - $testObjects = $this->loadAllTestObjects(); - - // We need to check the tests passed in to insure that we can generate them in the current context. - $invalidTestObjects = array_intersect_key($testObjects, $testsToIgnore); - if (!empty($invalidTestObjects)) { - $errorMsg = "Cannot reference the following tests for generation without accompanying suite:\n"; - array_walk($invalidTestObjects, function ($value, $key) use (&$errorMsg) { - $errorMsg.= "\t{$key}\n"; - }); - throw new TestReferenceException($errorMsg); - } - - $testObjects = array_diff_key($testObjects, $testsToIgnore); + $testObjects = $this->loadAllTestObjects($testsToIgnore); $cestPhpArray = []; foreach ($testObjects as $test) { From 5b7eb65998ee1790c5378b9ac2d2bde468fc96bf Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Tue, 10 Apr 2018 11:07:07 -0500 Subject: [PATCH 50/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - add support for excluded test and suite generation based on config --- bin/static-checks | 4 ++-- bin/static-checks.bat | 4 ++-- .../Util/Manifest/BaseTestManifest.php | 5 ++++- .../Util/Manifest/ParallelTestManifest.php | 2 +- .../Util/Sorter/ParallelGroupSorter.php | 4 ++++ .../FunctionalTestingFramework/Util/TestGenerator.php | 7 ++++++- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bin/static-checks b/bin/static-checks index f1f58016f..c711726e4 100755 --- a/bin/static-checks +++ b/bin/static-checks @@ -8,7 +8,7 @@ set -e echo "===============================" echo " CODE SNIFFER" echo "===============================" -vendor/bin/phpcs ./src --standard=./dev/tests/static/Magento +vendor/bin/phpcs ./src --standard=./dev/tests/static/Magento --ignore=src/Magento/FunctionalTestingFramework/Group,src/Magento/FunctionalTestingFramework/AcceptanceTester.php vendor/bin/phpcs ./dev/tests/unit --standard=./dev/tests/static/Magento vendor/bin/phpcs ./dev/tests/verification --standard=./dev/tests/static/Magento --ignore=dev/tests/verification/_generated echo "" @@ -22,7 +22,7 @@ echo "" echo "===============================" echo " MESS DETECTOR" echo "===============================" -vendor/bin/phpmd ./src text /dev/tests/static/Magento/CodeMessDetector/ruleset.xml --exclude _generated +vendor/bin/phpmd ./src text /dev/tests/static/Magento/CodeMessDetector/ruleset.xml --exclude _generated,src/Magento/FunctionalTestingFramework/Group,src/Magento/FunctionalTestingFramework/AcceptanceTester.php echo "" echo "===============================" diff --git a/bin/static-checks.bat b/bin/static-checks.bat index 70cd44ceb..0b9402094 100644 --- a/bin/static-checks.bat +++ b/bin/static-checks.bat @@ -5,7 +5,7 @@ @echo off @echo ===============================PHP CODE SNIFFER REPORT=============================== -call vendor\bin\phpcs .\src --standard=.\dev\tests\static\Magento +call vendor\bin\phpcs .\src --standard=.\dev\tests\static\Magento --ignore=src\Magento\FunctionalTestingFramework\Group,src\Magento\FunctionalTestingFramework\AcceptanceTester.php call vendor\bin\phpcs .\dev\tests\unit --standard=.\dev\tests\static\Magento call vendor\bin\phpcs .\dev\tests\verification --standard=.\dev\tests\static\Magento --ignore=dev\tests\verification\_generated @@ -13,7 +13,7 @@ call vendor\bin\phpcs .\dev\tests\verification --standard=.\dev\tests\static\Mag call vendor\bin\phpcpd .\src @echo "===============================PHP MESS DETECTOR REPORT=============================== -vendor\bin\phpmd .\src text \dev\tests\static\Magento\CodeMessDetector\ruleset.xml --exclude _generated +vendor\bin\phpmd .\src text \dev\tests\static\Magento\CodeMessDetector\ruleset.xml --exclude _generated,src\Magento\FunctionalTestingFramework\Group,src\Magento\FunctionalTestingFramework\AcceptanceTester.php @echo ===============================MAGENTO COPYRIGHT REPORT=============================== echo msgbox "INFO:Copyright check currently not run as part of .bat implementation" > "%temp%\popup.vbs" diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php index c3c0ec805..3c3eb6de9 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/BaseTestManifest.php @@ -79,8 +79,11 @@ abstract public function generate(); */ public function getSuiteConfig() { - $suiteToTestNames = []; + if ($this->suiteConfiguration === null) { + return []; + } + $suiteToTestNames = []; if (empty($this->suiteConfiguration)) { // if there is no configuration passed we can assume the user wants all suites generated as specified. foreach (SuiteObjectHandler::getInstance()->getAllObjects() as $suite => $suiteObj) { diff --git a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php index 485c506dd..12ce5d701 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php +++ b/src/Magento/FunctionalTestingFramework/Util/Manifest/ParallelTestManifest.php @@ -99,7 +99,7 @@ public function createTestGroups($lines) public function generate() { DirSetupUtil::createGroupDir($this->dirPath); - $suites = $this->getFlattenedSuiteConfiguration($this->parallelGroupSorter->getResultingSuiteConfig()); + $suites = $this->getFlattenedSuiteConfiguration($this->suiteConfiguration ?? []); foreach ($this->testGroups as $groupNumber => $groupContents) { $this->generateGroupFile($groupContents, $groupNumber, $suites); diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index 735a36447..4eb825add 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -76,6 +76,10 @@ public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $lin */ public function getResultingSuiteConfig() { + if (empty($this->suiteConfig)) { + return null; + } + return $this->suiteConfig; } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 4590daefe..dfbf20dac 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -97,7 +97,7 @@ private function __construct($exportDir, $tests, $debug = false) * @param bool $debug * @return TestGenerator */ - public static function getInstance($dir = null, $tests = null, $debug = false) + public static function getInstance($dir = null, $tests = [], $debug = false) { return new TestGenerator($dir, $tests, $debug); } @@ -174,6 +174,11 @@ private function createCestFile($testPhp, $filename) */ public function createAllTestFiles($testManifest = null, $testsToIgnore = null) { + if ($this->tests === null) { + // no-op if the test configuration is null + return; + } + DirSetupUtil::createGroupDir($this->exportDirectory); if ($testsToIgnore === null) { $testsToIgnore = SuiteObjectHandler::getInstance()->getAllTestReferences(); From 81e078d2a22fd7bd0e0df25f4a4f187920ac1cbe Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Tue, 10 Apr 2018 16:21:16 -0500 Subject: [PATCH 51/77] MQE-893: Add flag to robo generate: tests which accepts a specific set of tests to execute - fix array_diff mistake, need to check key --- .../Util/Sorter/ParallelGroupSorter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index 4eb825add..dfe5d3915 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -62,7 +62,7 @@ public function getTestsGroupedBySize($suiteConfiguration, $testNameToSize, $lin $testGroups[$nodeNumber] = $testGroup; // unset the test which have been used. - $testNameToSizeForUse = array_diff($testNameToSizeForUse, $testGroup); + $testNameToSizeForUse = array_diff_key($testNameToSizeForUse, $testGroup); $nodeNumber++; } From 5fe37964012948ac8046215eb6fbb05336b4d645 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 12 Apr 2018 16:19:05 +0300 Subject: [PATCH 52/77] MQE-905: Persistent entity variable not interpolated with page urls with type "admin" - resolveEnvReferences now alters the passed in set of $args, allowing further use of the new $args. - Refactored resolve* functions to consume and transform $args instead of the whole string. --- .../Resources/PersistedReplacementTest.txt | 1 + .../Test/PersistedReplacementTest.xml | 1 + .../Util/TestGenerator.php | 81 +++++++++---------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/dev/tests/verification/Resources/PersistedReplacementTest.txt b/dev/tests/verification/Resources/PersistedReplacementTest.txt index ca09c767e..2c335c7f0 100644 --- a/dev/tests/verification/Resources/PersistedReplacementTest.txt +++ b/dev/tests/verification/Resources/PersistedReplacementTest.txt @@ -50,6 +50,7 @@ class PersistedReplacementTestCest $createdData->createEntity(); $I->fillField("#selector", "StringBefore " . $createdData->getCreatedDataByName('firstname') . " StringAfter"); $I->fillField("#" . $createdData->getCreatedDataByName('firstname'), "input"); + $I->fillField("#" . getenv("MAGENTO_BASE_URL") . "#" . $createdData->getCreatedDataByName('firstname'), "input"); $I->dragAndDrop("#" . $createdData->getCreatedDataByName('firstname'), $createdData->getCreatedDataByName('lastname')); $I->conditionalClick($createdData->getCreatedDataByName('lastname'), "#" . $createdData->getCreatedDataByName('firstname'), true); $I->amOnUrl($createdData->getCreatedDataByName('firstname') . ".html"); diff --git a/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml b/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml index d46381748..26885fda4 100644 --- a/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/PersistedReplacementTest.xml @@ -16,6 +16,7 @@ <fillField stepKey="inputReplace" selector="#selector" userInput="StringBefore $createdData.firstname$ StringAfter"/> <fillField stepKey="selectorReplace" selector="#$createdData.firstname$" userInput="input"/> + <fillField stepKey="selectorReplace2" selector="#{{_ENV.MAGENTO_BASE_URL}}#$createdData.firstname$" userInput="input"/> <dragAndDrop stepKey="selector12Replace" selector1="#$createdData.firstname$" selector2="$createdData.lastname$"/> <conditionalClick stepKey="dependentSelectorReplace" dependentSelector="#$createdData.firstname$" selector="$createdData.lastname$" visible="true"/> <amOnUrl stepKey="urlReplace" url="$createdData.firstname$.html"/> diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index dfbf20dac..e10626f64 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -702,7 +702,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " ); } - if (isset($storeCode)) { + if ($storeCode) { $createEntityFunctionCall .= sprintf("\"%s\");\n", $storeCode); } else { $createEntityFunctionCall .= ");\n"; @@ -743,13 +743,14 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $testSteps .= $contextSetter; $testSteps .= $deleteEntityFunctionCall; } else { + $url = $this->resolveEnvReferences([$url])[0]; + $url = $this->resolveTestVariable([$url], null)[0]; $output = sprintf( "\t\t$%s->deleteEntityByUrl(%s);\n", $actor, $url ); - $output = $this->resolveEnvReferences($output, [$url]); - $testSteps .= $this->resolveTestVariable($output, [$url], null); + $testSteps .= $output; } break; case "updateData": @@ -798,7 +799,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " ); } - if (isset($storeCode)) { + if ($storeCode) { $updateEntityFunctionCall .= sprintf(", \"%s\");\n", $storeCode); } else { $updateEntityFunctionCall .= ");\n"; @@ -865,7 +866,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $getEntityFunctionCall .= 'null'; } - if (isset($storeCode)) { + if ($storeCode) { $getEntityFunctionCall .= sprintf(", \"%s\");\n", $storeCode); } else { $getEntityFunctionCall .= ");\n"; @@ -1222,9 +1223,10 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; + $input = $this->resolveTestVariable([$input], $actionObject->getActionOrigin())[0]; $argRef = "\t\t\$"; $argRef .= str_replace(ucfirst($fieldKey), "", $stepKey) . "Fields['{$fieldKey}'] = ${input};\n"; - $testSteps .= $this->resolveTestVariable($argRef, [$input], $actionObject->getActionOrigin()); + $testSteps .= $argRef; break; default: $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $input, $parameter); @@ -1253,19 +1255,16 @@ private function resolveLocatorFunctionInAttribute($attribute) * Resolves replacement of $input$ and $$input$$ in given function, recursing and replacing individual arguments * Also determines if each argument requires any quote replacement. * - * @param string $inputString * @param array $args * @param array $actionOrigin - * @return string + * @return array * @throws \Exception */ - private function resolveTestVariable($inputString, $args, $actionOrigin) + private function resolveTestVariable($args, $actionOrigin) { - $outputString = $inputString; - - //Loop through each argument, replace and then replace - foreach ($args as $arg) { - if ($arg == null) { + $newArgs = []; + foreach ($args as $key => $arg) { + if ($arg === null) { continue; } $outputArg = $arg; @@ -1282,10 +1281,10 @@ private function resolveTestVariable($inputString, $args, $actionOrigin) $outputArg = $this->resolveStepKeyReferences($outputArg, $actionOrigin); - $outputString = str_replace($arg, $outputArg, $outputString); + $newArgs[$key] = $outputArg; } - return $outputString; + return $newArgs; } /** @@ -1665,20 +1664,17 @@ private function wrapFunctionCall($actor, $action, ...$args) if (null === $args[$i]) { continue; } - if (!$isFirst) { - $output .= ', '; - } if ($args[$i] === "") { $args[$i] = '"' . $args[$i] . '"'; } - $output .= $args[$i]; - $isFirst = false; } - $output .= ");\n"; - - $output = $this->resolveEnvReferences($output, $args); - - return $this->resolveTestVariable($output, $args, $action->getActionOrigin()); + if (!is_array($args)) { + $args = [$args]; + } + $args = $this->resolveEnvReferences($args); + $args = $this->resolveTestVariable($args, $action->getActionOrigin()); + $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n"; + return $output; } /** @@ -1699,36 +1695,32 @@ private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $actio if (null === $args[$i]) { continue; } - if (!$isFirst) { - $output .= ', '; - } if ($args[$i] === "") { $args[$i] = '"' . $args[$i] . '"'; } - $output .= $args[$i]; - $isFirst = false; } - $output .= ");\n"; - - $output = $this->resolveEnvReferences($output, $args); - - return $this->resolveTestVariable($output, $args, $action->getActionOrigin()); + if (!is_array($args)) { + $args = [$args]; + } + $args = $this->resolveEnvReferences($args); + $args = $this->resolveTestVariable($args, $action->getActionOrigin()); + $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n"; + return $output; } // @codingStandardsIgnoreEnd /** * Resolves {{_ENV.variable}} into getenv("variable") for test-runtime ENV referencing. - * @param string $inputString * @param array $args - * @return string + * @return array */ - private function resolveEnvReferences($inputString, $args) + private function resolveEnvReferences($args) { $envRegex = "/{{_ENV\.([\w]+)}}/"; - $outputString = $inputString; + $newArgs = []; - foreach ($args as $arg) { + foreach ($args as $key => $arg) { preg_match_all($envRegex, $arg, $matches); if (!empty($matches[0])) { $fullMatch = $matches[0][0]; @@ -1737,11 +1729,14 @@ private function resolveEnvReferences($inputString, $args) $replacement = "getenv(\"{$envVariable}\")"; $outputArg = $this->processQuoteBreaks($fullMatch, $arg, $replacement); - $outputString = str_replace($arg, $outputArg, $outputString); + $newArgs[$key] = $outputArg; + continue; } + $newArgs[$key] = $arg; } - return $outputString; + // override passed in args for use later. + return $newArgs; } /** From a3bb056e9e7e999c840412de6f925a616b051d47 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 11 Apr 2018 11:41:05 -0500 Subject: [PATCH 53/77] MQE-790: Error for duplicate step keys in a single action group definition - add new ActionGroup DOM for stepKey validation - update ActionGroup di.xml config to use new DOM - add unit test -fix verification test --- .../Test/Config/ActionGroupDomTest.php | 32 +++++++++++++++++ .../ActionGroup/XmlDuplicateActionGroup.xml | 8 ++--- etc/di.xml | 3 +- .../Test/Config/ActionGroupDom.php | 35 +++++++++++++++++++ .../Test/Config/Dom.php | 11 +++--- .../Test/Util/ActionGroupObjectExtractor.php | 5 ++- .../Test/etc/actionGroupSchema.xsd | 1 + 7 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php create mode 100644 src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php new file mode 100644 index 000000000..50b23c045 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Tests\unit\Magento\FunctionalTestFramework\Test\Config; + +use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom; +use PHPUnit\Framework\TestCase; + +class ActionGroupDomTest extends TestCase +{ + /** + * Test Action Group duplicate step key validation + */ + public function testActionGroupDomStepKeyValidation() + { + $sampleXml = "<actionGroups> + <actionGroup name=\"actionGroupWithoutArguments\"> + <wait time=\"1\" stepKey=\"waitForNothing\" /> + <wait time=\"2\" stepKey=\"waitForNothing\" /> + </actionGroup> + </actionGroups>"; + + $actionDom = new ActionGroupDom($sampleXml, 'test.xml'); + $this->expectException(XmlException::class); + + // an exception is only thrown for Action Group files. + $actionDom->initDom($sampleXml, 'dupeStepKeyActionGroup.xml'); + } +} diff --git a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml index 8c5ba7402..fcc22acca 100644 --- a/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml +++ b/dev/tests/verification/TestModule/ActionGroup/XmlDuplicateActionGroup.xml @@ -16,8 +16,8 @@ <amOnSubdomain stepKey="aosd2" url="1"/> <amOnUrl stepKey="aou1" url="1"/> <amOnUrl stepKey="aou2" url="1"/> - <appendField selector="1" stepKey="ap1"/> - <appendField selector="1" stepKey="ap2"/> + <appendField selector="1" stepKey="apf1"/> + <appendField selector="1" stepKey="apf2"/> <attachFile selector="1" stepKey="atf1"/> <attachFile selector="1" stepKey="atf2"/> <cancelPopup stepKey="cp1"/> @@ -164,8 +164,8 @@ <seeInField stepKey="seeinfield12"/> <seeInFormFields selector="2" parameterArray="[1]" stepKey="seeinformfields1"/> <seeInFormFields selector="2" parameterArray="[1]" stepKey="seeinformfields12"/> - <seeInPageSource html="1" stepKey="seeinsource1"/> - <seeInPageSource html="1" stepKey="seeinsource12"/> + <seeInPageSource html="1" stepKey="seeinpgsource1"/> + <seeInPageSource html="1" stepKey="seeinpgsource12"/> <seeInPopup stepKey="seeinpopup1"/> <seeInPopup stepKey="seeinpopup12"/> <seeInSource html="1" stepKey="seeinsource1"/> diff --git a/etc/di.xml b/etc/di.xml index e52c58b31..d03ed2267 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -286,11 +286,12 @@ </arguments> </virtualType> - <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\ActionGroupData" type="Magento\FunctionalTestingFramework\Config\Reader\Filesystem"> + <virtualType name="Magento\FunctionalTestingFramework\Config\Reader\ActionGroupData" type="Magento\FunctionalTestingFramework\Test\Config\Reader\Filesystem"> <arguments> <argument name="fileResolver" xsi:type="object">Magento\FunctionalTestingFramework\Config\FileResolver\Module</argument> <argument name="converter" xsi:type="object">Magento\FunctionalTestingFramework\Config\ActionGroupDataConverter</argument> <argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\ActionGroup</argument> + <argument name="domDocumentClass" xsi:type="string">Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom</argument> <argument name="idAttributes" xsi:type="array"> <item name="/actionGroups/actionGroup" xsi:type="string">name</item> <item name="/actionGroups/actionGroup/arguments/argument" xsi:type="string">name</item> diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php new file mode 100644 index 000000000..9bd874a57 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Test\Config; + +class ActionGroupDom extends Dom +{ + const ACTION_GROUP_FILE_NAME_ENDING = "ActionGroup.xml"; + + /** + * Takes a dom element from xml and appends the filename based on location while also validating the action group + * step key. + * + * @param string $xml + * @param string|null $filename + * @return \DOMDocument + */ + public function initDom($xml, $filename = null) + { + $dom = parent::initDom($xml); + + if (strpos($filename, self::ACTION_GROUP_FILE_NAME_ENDING)) { + $actionGroupNodes = $dom->getElementsByTagName('actionGroup'); + foreach ($actionGroupNodes as $actionGroupNode) { + /** @var \DOMElement $actionGroupNode */ + $actionGroupNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); + $this->validateDomStepKeys($actionGroupNode, $filename, 'Action Group'); + } + } + + return $dom; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php index a2d9c180f..44b990b3f 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php @@ -58,7 +58,7 @@ public function initDom($xml, $filename = null) foreach ($testNodes as $testNode) { /** @var \DOMElement $testNode */ $testNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); - $this->validateTestDomStepKeys($testNode, $filename); + $this->validateDomStepKeys($testNode, $filename, 'Test'); } } @@ -83,10 +83,11 @@ public function merge($xml, $filename = null) * * @param \DOMElement $testNode * @param string $filename + * @param string $type * @return void * @throws XmlException */ - private function validateTestDomStepKeys($testNode, $filename) + protected function validateDomStepKeys($testNode, $filename, $type) { $childNodes = $testNode->childNodes; @@ -99,7 +100,7 @@ private function validateTestDomStepKeys($testNode, $filename) } if (in_array($currentNode->nodeName, self::TEST_HOOK_NAMES)) { - $this->validateTestDomStepKeys($currentNode, $filename); + $this->validateDomStepKeys($currentNode, $filename, $type); } if ($currentNode->hasAttribute('stepKey')) { @@ -116,7 +117,9 @@ private function validateTestDomStepKeys($testNode, $filename) $stepKeyError .= "\tstepKey: {$duplicateValue} is used more than once.\n"; } - throw new XmlException("Tests cannot use stepKey more than once!\t\n{$stepKeyError}\tin file: {$filename}"); + throw new XmlException( + "{$type}s cannot use stepKey more than once!\t\n{$stepKeyError}\tin file: {$filename}" + ); } } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index a49246d44..aeef21760 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -17,6 +17,7 @@ class ActionGroupObjectExtractor extends BaseObjectExtractor { const DEFAULT_VALUE = 'defaultValue'; const ACTION_GROUP_ARGUMENTS = 'arguments'; + const FILENAME = 'filename'; /** * Action Object Extractor for converting actions into objects @@ -47,9 +48,11 @@ public function extractActionGroup($actionGroupData) $actionGroupData, self::NODE_NAME, self::ACTION_GROUP_ARGUMENTS, - self::NAME + self::NAME, + self::FILENAME ); + // TODO filename is now available to the ActionGroupObject, integrate this into debug and error statements $actions = $this->actionObjectExtractor->extractActions($actionData); if (array_key_exists(self::ACTION_GROUP_ARGUMENTS, $actionGroupData)) { diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd index 5c5fc0059..74badd40c 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd @@ -32,6 +32,7 @@ </xs:element> </xs:choice> <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:string" name="filename"/> </xs:complexType> <xs:simpleType name="dataTypeEnum" final="restriction"> <xs:restriction base="xs:string"> From ab81af625f89b0d2fdff9778960da3005d6fb914 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 12 Apr 2018 14:00:43 -0500 Subject: [PATCH 54/77] MQE-790: Error for duplicate step keys in a single action group definition - add new ExceptionCollector to throw all exceptions following stepKey validation --- .../Test/Config/ActionGroupDomTest.php | 10 +-- .../Collector/ExceptionCollector.php | 67 +++++++++++++++++++ .../Test/Config/ActionGroupDom.php | 7 +- .../Test/Config/Dom.php | 25 ++++--- .../Test/Config/Reader/Filesystem.php | 14 ++-- 5 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php index 50b23c045..b7ea66f65 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Config/ActionGroupDomTest.php @@ -5,7 +5,7 @@ */ namespace Tests\unit\Magento\FunctionalTestFramework\Test\Config; -use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Test\Config\ActionGroupDom; use PHPUnit\Framework\TestCase; @@ -23,10 +23,10 @@ public function testActionGroupDomStepKeyValidation() </actionGroup> </actionGroups>"; - $actionDom = new ActionGroupDom($sampleXml, 'test.xml'); - $this->expectException(XmlException::class); + $exceptionCollector = new ExceptionCollector(); + $actionDom = new ActionGroupDom($sampleXml, 'dupeStepKeyActionGroup.xml', $exceptionCollector); - // an exception is only thrown for Action Group files. - $actionDom->initDom($sampleXml, 'dupeStepKeyActionGroup.xml'); + $this->expectException(\Exception::class); + $exceptionCollector->throwException(); } } diff --git a/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php new file mode 100644 index 000000000..4b67add0b --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Exceptions/Collector/ExceptionCollector.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Exceptions\Collector; + +class ExceptionCollector +{ + /** + * Private array containing all errors to be thrown as part of the exception. + * + * @var array + */ + private $errors = []; + + /** + * Function to add a filename and message for the filename + * + * @param string $filename + * @param string $message + * @return void + */ + public function addError($filename, $message) + { + $error[$filename] = $message; + $this->errors = array_merge_recursive($this->errors, $error); + } + + /** + * Function which throws an exception when there are errors present. + * + * @return void + * @throws \Exception + */ + public function throwException() + { + if (empty($this->errors)) { + return; + } + + $errorMsg = implode("\n\n", $this->formatErrors($this->errors)); + throw new \Exception("\n" . $errorMsg); + } + + /** + * If there are multiple exceptions for a single file, the function flattens the array so they can be printed + * as separate messages. + * + * @param array $errors + * @return array + */ + private function formatErrors($errors) + { + $flattenedErrors = []; + foreach ($errors as $key => $errorMsg) { + if (is_array($errorMsg)) { + $flattenedErrors = array_merge($flattenedErrors, $this->formatErrors($errorMsg)); + continue; + } + + $flattenedErrors[] = $errorMsg; + } + + return $flattenedErrors; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php index 9bd874a57..794007dde 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/ActionGroupDom.php @@ -5,6 +5,8 @@ */ namespace Magento\FunctionalTestingFramework\Test\Config; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; + class ActionGroupDom extends Dom { const ACTION_GROUP_FILE_NAME_ENDING = "ActionGroup.xml"; @@ -15,9 +17,10 @@ class ActionGroupDom extends Dom * * @param string $xml * @param string|null $filename + * @param ExceptionCollector $exceptionCollector * @return \DOMDocument */ - public function initDom($xml, $filename = null) + public function initDom($xml, $filename = null, $exceptionCollector = null) { $dom = parent::initDom($xml); @@ -26,7 +29,7 @@ public function initDom($xml, $filename = null) foreach ($actionGroupNodes as $actionGroupNode) { /** @var \DOMElement $actionGroupNode */ $actionGroupNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); - $this->validateDomStepKeys($actionGroupNode, $filename, 'Action Group'); + $this->validateDomStepKeys($actionGroupNode, $filename, 'Action Group', $exceptionCollector); } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php index 44b990b3f..24710243f 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Dom.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Test\Config; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Config\Dom\NodeMergingConfig; use Magento\FunctionalTestingFramework\Config\Dom\NodePathMatcher; @@ -21,6 +22,7 @@ class Dom extends \Magento\FunctionalTestingFramework\Config\Dom * TestDom constructor. * @param string $xml * @param string $filename + * @param ExceptionCollector $exceptionCollector * @param array $idAttributes * @param string $typeAttributeName * @param string $schemaFile @@ -29,6 +31,7 @@ class Dom extends \Magento\FunctionalTestingFramework\Config\Dom public function __construct( $xml, $filename, + $exceptionCollector, array $idAttributes = [], $typeAttributeName = null, $schemaFile = null, @@ -38,7 +41,7 @@ public function __construct( $this->nodeMergingConfig = new NodeMergingConfig(new NodePathMatcher(), $idAttributes); $this->typeAttributeName = $typeAttributeName; $this->errorFormat = $errorFormat; - $this->dom = $this->initDom($xml, $filename); + $this->dom = $this->initDom($xml, $filename, $exceptionCollector); $this->rootNamespace = $this->dom->lookupNamespaceUri($this->dom->namespaceURI); } @@ -47,9 +50,10 @@ public function __construct( * * @param string $xml * @param string|null $filename + * @param ExceptionCollector $exceptionCollector * @return \DOMDocument */ - public function initDom($xml, $filename = null) + public function initDom($xml, $filename = null, $exceptionCollector = null) { $dom = parent::initDom($xml); @@ -58,7 +62,7 @@ public function initDom($xml, $filename = null) foreach ($testNodes as $testNode) { /** @var \DOMElement $testNode */ $testNode->setAttribute(self::TEST_META_FILENAME_ATTRIBUTE, $filename); - $this->validateDomStepKeys($testNode, $filename, 'Test'); + $this->validateDomStepKeys($testNode, $filename, 'Test', $exceptionCollector); } } @@ -70,11 +74,12 @@ public function initDom($xml, $filename = null) * * @param string $xml * @param string|null $filename + * @param ExceptionCollector $exceptionCollector * @return void */ - public function merge($xml, $filename = null) + public function merge($xml, $filename = null, $exceptionCollector = null) { - $dom = $this->initDom($xml, $filename); + $dom = $this->initDom($xml, $filename, $exceptionCollector); $this->mergeNode($dom->documentElement, ''); } @@ -84,10 +89,11 @@ public function merge($xml, $filename = null) * @param \DOMElement $testNode * @param string $filename * @param string $type + * @param ExceptionCollector $exceptionCollector * @return void * @throws XmlException */ - protected function validateDomStepKeys($testNode, $filename, $type) + protected function validateDomStepKeys($testNode, $filename, $type, $exceptionCollector) { $childNodes = $testNode->childNodes; @@ -100,7 +106,7 @@ protected function validateDomStepKeys($testNode, $filename, $type) } if (in_array($currentNode->nodeName, self::TEST_HOOK_NAMES)) { - $this->validateDomStepKeys($currentNode, $filename, $type); + $this->validateDomStepKeys($currentNode, $filename, $type, $exceptionCollector); } if ($currentNode->hasAttribute('stepKey')) { @@ -117,9 +123,8 @@ protected function validateDomStepKeys($testNode, $filename, $type) $stepKeyError .= "\tstepKey: {$duplicateValue} is used more than once.\n"; } - throw new XmlException( - "{$type}s cannot use stepKey more than once!\t\n{$stepKeyError}\tin file: {$filename}" - ); + $errorMsg = "{$type}s cannot use stepKey more than once.\t\n{$stepKeyError}\tin file: {$filename}"; + $exceptionCollector->addError($filename, $errorMsg); } } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Config/Reader/Filesystem.php b/src/Magento/FunctionalTestingFramework/Test/Config/Reader/Filesystem.php index c2d16a13a..d96a926c5 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Config/Reader/Filesystem.php +++ b/src/Magento/FunctionalTestingFramework/Test/Config/Reader/Filesystem.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Test\Config\Reader; +use Magento\FunctionalTestingFramework\Exceptions\Collector\ExceptionCollector; use Magento\FunctionalTestingFramework\Util\Iterator\File; class Filesystem extends \Magento\FunctionalTestingFramework\Config\Reader\Filesystem @@ -19,6 +20,8 @@ class Filesystem extends \Magento\FunctionalTestingFramework\Config\Reader\Files */ public function readFiles($fileList) { + $exceptionCollector = new ExceptionCollector(); + $errors = []; /** @var \Magento\FunctionalTestingFramework\Test\Config\Dom $configMerger */ $configMerger = null; foreach ($fileList as $key => $content) { @@ -27,17 +30,18 @@ public function readFiles($fileList) $configMerger = $this->createConfigMerger( $this->domDocumentClass, $content, - $fileList->getFilename() + $fileList->getFilename(), + $exceptionCollector ); } else { - $configMerger->merge($content, $fileList->getFilename()); + $configMerger->merge($content, $fileList->getFilename(), $exceptionCollector); } } catch (\Magento\FunctionalTestingFramework\Config\Dom\ValidationException $e) { throw new \Exception("Invalid XML in file " . $key . ":\n" . $e->getMessage()); } } + $exceptionCollector->throwException(); if ($this->validationState->isValidationRequired()) { - $errors = []; if ($configMerger && !$configMerger->validate($this->schemaFile, $errors)) { $message = "Invalid Document \n"; throw new \Exception($message . implode("\n", $errors)); @@ -57,14 +61,16 @@ public function readFiles($fileList) * @param string $mergerClass * @param string $initialContents * @param string $filename + * @param ExceptionCollector $exceptionCollector * @return \Magento\FunctionalTestingFramework\Config\Dom * @throws \UnexpectedValueException */ - protected function createConfigMerger($mergerClass, $initialContents, $filename = null) + protected function createConfigMerger($mergerClass, $initialContents, $filename = null, $exceptionCollector = null) { $result = new $mergerClass( $initialContents, $filename, + $exceptionCollector, $this->idAttributes, null, $this->perFileSchema From 9a0bb1398e09ccdc666c24e0f72639d78b90c3d5 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Tue, 10 Apr 2018 14:33:16 -0500 Subject: [PATCH 55/77] MQE-809: Throw a warning or error when step key referencing in merges is invalid or ambiguous - add validation to ambiguous action object ref during merge - add validtion to invalid action object ref during merge - add unit test around action object extraction --- .../Test/Util/ActionObjectExtractorTest.php | 109 ++++++++++++++++++ .../Test/Util/ActionObjectExtractor.php | 53 ++++++++- .../Test/Util/TestObjectExtractor.php | 4 +- 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php new file mode 100644 index 000000000..1c09e5879 --- /dev/null +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php @@ -0,0 +1,109 @@ +<?php +/** + * Created by PhpStorm. + * User: imeron + * Date: 4/10/18 + * Time: 11:39 AM + */ + +namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; + +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; +use Magento\FunctionalTestingFramework\Test\Util\ActionObjectExtractor; +use PHPUnit\Framework\TestCase; + +class ActionObjectExtractorTest extends TestCase +{ + /** @var ActionObjectExtractor */ + private $testActionObjectExtractor; + + /** + * Setup method + */ + public function setUp() + { + $this->testActionObjectExtractor = new ActionObjectExtractor(); + } + + /** + * Tests basic action object extraction with a valid parser array. + */ + public function testBasicActionObjectExtration() + { + $actionObjects = $this->testActionObjectExtractor->extractActions($this->createBasicActionObjectArray()); + $this->assertCount(1, $actionObjects); + + /** @var ActionObject $firstElement */ + $firstElement = array_values($actionObjects)[0]; + $this->assertEquals('testAction1', $firstElement->getStepKey()); + $this->assertCount(1, $firstElement->getCustomActionAttributes()); + } + + /** + * Tests an invalid merge order reference (i.e. a step referencing itself). + */ + public function testInvalidMergeOrderReference() + { + $invalidArray = $this->createBasicActionObjectArray('invalidTestAction1', 'invalidTestAction1'); + + $this->expectException('\Magento\FunctionalTestingFramework\Exceptions\TestReferenceException'); + $expectedExceptionMessage = "Invalid ordering configuration in test TestWithSelfReferencingStepKey with step" . + " key(s):\n\tinvalidTestAction1\n"; + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->testActionObjectExtractor->extractActions($invalidArray, 'TestWithSelfReferencingStepKey'); + } + + /** + * Validates a warning is printed to the console when multiple actions reference the same actions for merging. + */ + public function testAmbiguousMergeOrderRefernece() + { + $ambiguousArray = $this->createBasicActionObjectArray('testAction1'); + $ambiguousArray = array_merge( + $ambiguousArray, + $this->createBasicActionObjectArray('testAction2', 'testAction1') + ); + + $ambiguousArray = array_merge( + $ambiguousArray, + $this->createBasicActionObjectArray('testAction3', null, 'testAction1') + ); + + $outputString = "multiple actions referencing step key testAction1 in test AmbiguousRefTest:\n" . + "\ttestAction2\n" . + "\ttestAction3\n"; + + $this->expectOutputString($outputString); + $this->testActionObjectExtractor->extractActions($ambiguousArray, 'AmbiguousRefTest'); + } + + /** + * Utility function to return mock parser output for testing extraction into ActionObjects. + * + * @param string $stepKey + * @param string $before + * @param string $after + * @return array + */ + private function createBasicActionObjectArray($stepKey = 'testAction1', $before = null, $after = null) + { + $baseArray = [ + $stepKey => [ + "nodeName" => "sampleAction", + "stepKey" => $stepKey, + "someAttribute" => "someAttributeValue" + ] + ]; + + if ($before) { + $baseArray[$stepKey] = array_merge($baseArray[$stepKey], ['before' => $before]); + } + + if ($after) { + $baseArray[$stepKey] = array_merge($baseArray[$stepKey], ['after' => $after]); + } + + return $baseArray; + } +} \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index eb05c7e89..906c4f25f 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; +use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -42,12 +44,14 @@ public function __construct() * irrelevant tags and returned as an array of ActionObjects. * * @param array $testActions + * @param string $testName * @return array * @throws XmlException */ - public function extractActions($testActions) + public function extractActions($testActions, $testName = null) { $actions = []; + $stepKeyRefs = []; foreach ($testActions as $actionName => $actionData) { $stepKey = $actionData[self::TEST_STEP_MERGE_KEY]; @@ -89,6 +93,10 @@ public function extractActions($testActions) $actions = $this->extractFieldActions($actionData, $actions); $actionAttributes = $this->extractFieldReferences($actionData, $actionAttributes); + if ($linkedAction != null) { + $stepKeyRefs[$linkedAction][] = $stepKey; + } + // TODO this is to be implemented later. Currently the schema does not use or need return var. /*if (array_key_exists(ActionGroupObjectHandler::TEST_ACTION_RETURN_VARIABLE, $actionData)) { $returnVariable = $actionData[ActionGroupObjectHandler::TEST_ACTION_RETURN_VARIABLE]; @@ -103,6 +111,8 @@ public function extractActions($testActions) ); } + $this->auditMergeSteps($stepKeyRefs, $testName); + return $actions; } @@ -194,4 +204,45 @@ private function extractFieldReferences($actionData, $actionAttributes) return $attributes; } + + /** + * Function which validates stepKey references within mergeable actions + * + * @param array $stepKeyRefs + * @param string $testName + * @return void + * @throws TestReferenceException + */ + private function auditMergeSteps($stepKeyRefs, $testName) + { + if (empty($stepKeyRefs)) { + return; + } + + // check for step keys which are referencing themselves as before/after + $invalidStepRef = array_filter($stepKeyRefs, function ($value, $key) { + return in_array($key, $value); + }, ARRAY_FILTER_USE_BOTH); + + if (!empty($invalidStepRef)) { + $errorMsg = "Invalid ordering configuration in test {$testName} with step key(s):\n"; + array_walk($invalidStepRef, function ($value, $key) use (&$errorMsg) { + $errorMsg.="\t{$key}\n"; + }); + + throw new TestReferenceException($errorMsg); + } + + // check for ambiguous references to step keys (multiple refs across test merges). + $atRiskStepRef = array_filter($stepKeyRefs, function ($value) { + return count($value) > 1; + }); + + foreach ($atRiskStepRef as $stepKey => $stepRefs) { + print "multiple actions referencing step key {$stepKey} in test {$testName}:\n"; + array_walk($stepRefs, function ($value) { + print "\t{$value}\n"; + }); + } + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php index 48b7d5552..be4e3c97c 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/TestObjectExtractor.php @@ -6,7 +6,9 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; +use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; use Magento\FunctionalTestingFramework\Test\Objects\TestObject; use Magento\FunctionalTestingFramework\Util\Validation\NameValidationUtil; @@ -109,7 +111,7 @@ public function extractTestData($testData) try { return new TestObject( $testData[self::NAME], - $this->actionObjectExtractor->extractActions($testActions), + $this->actionObjectExtractor->extractActions($testActions, $testData[self::NAME]), $testAnnotations, $testHooks, $filename From 3f79812120d3911d41e4b154ebda6d223caf0823 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 12 Apr 2018 10:08:26 -0500 Subject: [PATCH 56/77] MQE-809: Throw a warning or error when step key referencing in merges is invalid or ambiguous - add Application Config for static ref --- RoboFile.php | 1 + dev/tests/_bootstrap.php | 8 +- .../Test/Util/ActionObjectExtractorTest.php | 9 +- .../Config/MftfApplicationConfig.php | 127 ++++++++++++++++++ .../Test/Objects/ActionObject.php | 4 +- .../Test/Util/ActionObjectExtractor.php | 64 ++++++--- .../Util/ConfigSanitizerUtil.php | 5 +- .../Util/ModuleResolver.php | 21 +-- 8 files changed, 196 insertions(+), 43 deletions(-) create mode 100644 src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php diff --git a/RoboFile.php b/RoboFile.php index c0af4c72c..717200a81 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -49,6 +49,7 @@ function generateTests(array $tests, $opts = ['config' => null, 'force' => true, { require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; $GLOBALS['GENERATE_TESTS'] = true; + if (!$this->isProjectBuilt()) { $this->say("<info>Please run bin/mftf build:project and configure your environment (.env) first.</info>"); exit(\Robo\Result::EXITCODE_ERROR); diff --git a/dev/tests/_bootstrap.php b/dev/tests/_bootstrap.php index 981da5b8b..655e33910 100644 --- a/dev/tests/_bootstrap.php +++ b/dev/tests/_bootstrap.php @@ -16,8 +16,12 @@ 'includePaths' => [PROJECT_ROOT . '/src'] ]); -// force php to generate -$GLOBALS['FORCE_PHP_GENERATE'] = true; +// set mftf appplication context +\Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::create( + true, + \Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::GENERATION_PHASE, + true +); // Load needed framework env params $TEST_ENVS = [ diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php index 1c09e5879..873e622f7 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Util/ActionObjectExtractorTest.php @@ -1,11 +1,8 @@ <?php /** - * Created by PhpStorm. - * User: imeron - * Date: 4/10/18 - * Time: 11:39 AM + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. */ - namespace tests\unit\Magento\FunctionalTestFramework\Test\Util; use Magento\FunctionalTestingFramework\Test\Objects\ActionObject; @@ -106,4 +103,4 @@ private function createBasicActionObjectArray($stepKey = 'testAction1', $before return $baseArray; } -} \ No newline at end of file +} diff --git a/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php new file mode 100644 index 000000000..4d4500b1e --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Config/MftfApplicationConfig.php @@ -0,0 +1,127 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\FunctionalTestingFramework\Config; + +use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; + +class MftfApplicationConfig +{ + const GENERATION_PHASE = "generation"; + const EXECUTION_PHASE = "execution"; + const MFTF_PHASES = [self::GENERATION_PHASE, self::EXECUTION_PHASE]; + + /** + * Determines whether the user has specified a force option for generation + * + * @var bool + */ + private $forceGenerate; + + /** + * String which identifies the current phase of mftf execution + * + * @var string + */ + private $phase; + + /** + * Determines whether the user would like to execute mftf in a verbose run. + * + * @var bool + */ + private $verboseEnabled; + + /** + * MftfApplicationConfig Singelton Instance + * + * @var MftfApplicationConfig + */ + private static $MFTF_APPLICATION_CONTEXT; + + /** + * MftfApplicationConfig constructor. + * + * @param bool $forceGenerate + * @param string $phase + * @param bool $verboseEnabled + * @throws TestFrameworkException + */ + private function __construct($forceGenerate = false, $phase = self::EXECUTION_PHASE, $verboseEnabled = null) + { + $this->forceGenerate = $forceGenerate; + + if (!in_array($phase, self::MFTF_PHASES)) { + throw new TestFrameworkException("{$phase} is not an mftf phase"); + } + + $this->phase = $phase; + $this->verboseEnabled = $verboseEnabled; + } + + /** + * Creates an instance of the configuration instance for reference once application has started. This function + * returns void and is only run once during the lifetime of the application. + * + * @param bool $forceGenerate + * @param string $phase + * @param bool $verboseEnabled + * @return void + */ + public static function create($forceGenerate, $phase, $verboseEnabled) + { + if (self::$MFTF_APPLICATION_CONTEXT == null) { + self::$MFTF_APPLICATION_CONTEXT = new MftfApplicationConfig($forceGenerate, $phase, $verboseEnabled); + } + } + + /** + * This function returns an instance of the MftfApplicationConfig which is created once the application starts. + * + * @return MftfApplicationConfig + */ + public static function getConfig() + { + // TODO explicitly set this with AcceptanceTester or MagentoWebDriver + // during execution we cannot guarantee the use of the robofile so we return the default application config, + // we don't want to set the application context in case the user explicitly does so at a later time. + if (self::$MFTF_APPLICATION_CONTEXT == null) { + return new MftfApplicationConfig(); + } + + return self::$MFTF_APPLICATION_CONTEXT; + } + + /** + * Returns a booelan indiciating whether or not the user has indicated a forced generation. + * + * @return bool + */ + public function forceGenerateEnabled() + { + return $this->forceGenerate; + } + + /** + * Returns a boolean indicating whether the user has indicated a verbose run, which will cause all applicable + * text to print to the console. + * + * @return bool + */ + public function verboseEnabled() + { + return $this->verboseEnabled ?? getenv('MFTF_DEBUG'); + } + + /** + * Returns a string which indicates the phase of mftf execution. + * + * @return string + */ + public function getPhase() + { + return $this->phase; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 9a55755cc..ad062dbed 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -5,6 +5,7 @@ */ namespace Magento\FunctionalTestingFramework\Test\Objects; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\DataObjectHandler; use Magento\FunctionalTestingFramework\DataGenerator\Objects\EntityDataObject; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -264,7 +265,8 @@ public function trimAssertionAttributes() $oldAttributes = array_intersect($actionAttributeKeys, ActionObject::OLD_ASSERTION_ATTRIBUTES); if (!empty($oldAttributes)) { // @codingStandardsIgnoreStart - if ($GLOBALS['GENERATE_TESTS'] ?? false == true) { + $appConfig = MftfApplicationConfig::getConfig(); + if ($appConfig->getPhase() == MftfApplicationConfig::GENERATION_PHASE && $appConfig->verboseEnabled()) { echo("WARNING: Use of one line Assertion actions will be deprecated in MFTF 3.0.0, please use nested syntax (Action: {$this->type} StepKey: {$this->stepKey})" . PHP_EOL); } // @codingStandardsIgnoreEnd diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php index 906c4f25f..a3ce58a5e 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Test\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; use Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException; use Magento\FunctionalTestingFramework\Exceptions\TestReferenceException; use Magento\FunctionalTestingFramework\Exceptions\XmlException; @@ -65,8 +66,6 @@ public function extractActions($testActions, $testName = null) self::TEST_STEP_MERGE_KEY, self::NODE_NAME ); - $linkedAction = null; - $order = null; // Flatten AssertSorted "array" element to parameterArray if (isset($actionData["array"])) { @@ -77,24 +76,12 @@ public function extractActions($testActions, $testName = null) $actionAttributes = $this->processActionGroupArgs($actionAttributes); } - if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData) - and array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { - throw new XmlException(sprintf(self::BEFORE_AFTER_ERROR_MSG, $actionName)); - } - - if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData)) { - $linkedAction = $actionData[self::TEST_ACTION_BEFORE]; - $order = self::TEST_ACTION_BEFORE; - } elseif (array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { - $linkedAction = $actionData[self::TEST_ACTION_AFTER]; - $order = self::TEST_ACTION_AFTER; - } - + $linkedAction = $this->processLinkedActions($actionName, $actionData); $actions = $this->extractFieldActions($actionData, $actions); $actionAttributes = $this->extractFieldReferences($actionData, $actionAttributes); - if ($linkedAction != null) { - $stepKeyRefs[$linkedAction][] = $stepKey; + if ($linkedAction['stepKey'] != null) { + $stepKeyRefs[$linkedAction['stepKey']][] = $stepKey; } // TODO this is to be implemented later. Currently the schema does not use or need return var. @@ -106,8 +93,8 @@ public function extractActions($testActions, $testName = null) $stepKey, $actionData[self::NODE_NAME], $actionAttributes, - $linkedAction, - $order + $linkedAction['stepKey'], + $linkedAction['order'] ); } @@ -116,6 +103,34 @@ public function extractActions($testActions, $testName = null) return $actions; } + /** + * Function which processes any actions which have an explicit reference to an additional step for merging purposes. + * Returns an array with keys corresponding to the linked action's stepKey and order. + * + * @param string $actionName + * @param array $actionData + * @return array + * @throws XmlException + */ + private function processLinkedActions($actionName, $actionData) + { + $linkedAction =['stepKey' => null, 'order' => null]; + if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData) + and array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { + throw new XmlException(sprintf(self::BEFORE_AFTER_ERROR_MSG, $actionName)); + } + + if (array_key_exists(self::TEST_ACTION_BEFORE, $actionData)) { + $linkedAction['stepKey'] = $actionData[self::TEST_ACTION_BEFORE]; + $linkedAction['order'] = self::TEST_ACTION_BEFORE; + } elseif (array_key_exists(self::TEST_ACTION_AFTER, $actionData)) { + $linkedAction['stepKey'] = $actionData[self::TEST_ACTION_AFTER]; + $linkedAction['order'] = self::TEST_ACTION_AFTER; + } + + return $linkedAction; + } + /** * Takes the action group reference and parses out arguments as an array that can be passed to override defaults * defined in the action group xml. @@ -238,11 +253,16 @@ private function auditMergeSteps($stepKeyRefs, $testName) return count($value) > 1; }); + $multipleActionsError = ""; foreach ($atRiskStepRef as $stepKey => $stepRefs) { - print "multiple actions referencing step key {$stepKey} in test {$testName}:\n"; - array_walk($stepRefs, function ($value) { - print "\t{$value}\n"; + $multipleActionsError.= "multiple actions referencing step key {$stepKey} in test {$testName}:\n"; + array_walk($stepRefs, function ($value) use (&$multipleActionsError) { + $multipleActionsError.= "\t{$value}\n"; }); } + + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print $multipleActionsError; + } } } diff --git a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php index c6244ca82..802db4127 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php +++ b/src/Magento/FunctionalTestingFramework/Util/ConfigSanitizerUtil.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; + /** * Class ConfigSanitizerUtil */ @@ -86,8 +88,7 @@ private static function validateConfigBasedVars($config) */ public static function sanitizeUrl($url) { - $forceGenerate = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - if (strlen($url) == 0 && $forceGenerate === false) { + if (strlen($url) == 0 && !MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { trigger_error("MAGENTO_BASE_URL must be defined in .env", E_USER_ERROR); } diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 324e206ce..0fdf37f75 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -6,6 +6,8 @@ namespace Magento\FunctionalTestingFramework\Util; +use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig; + /** * Class ModuleResolver, resolve module path based on enabled modules of target Magento instance. * @@ -126,13 +128,11 @@ private function __construct() */ public function getEnabledModules() { - $testGenerationPhase = $GLOBALS['GENERATE_TESTS'] ?? false; - if (isset($this->enabledModules)) { return $this->enabledModules; } - if ($testGenerationPhase) { + if (MftfApplicationConfig::getConfig()->getPhase() == MftfApplicationConfig::GENERATION_PHASE) { $this->printMagentoVersionInfo(); } @@ -190,9 +190,7 @@ public function getModulesPath() } $enabledModules = $this->getEnabledModules(); - $forceGeneration = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - - if (empty($enabledModules) && !$forceGeneration) { + if (empty($enabledModules) && !MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { trigger_error( "Could not retrieve enabled modules from provided 'MAGENTO_BASE_URL'," . "please make sure Magento is available at this url", @@ -321,12 +319,13 @@ private function getEnabledDirectoryPaths($enabledModules, $allModulePaths) */ private function printMagentoVersionInfo() { - $forceGeneration = $GLOBALS['FORCE_PHP_GENERATE'] ?? false; - if ($forceGeneration) { + + if (MftfApplicationConfig::getConfig()->forceGenerateEnabled()) { return; } $url = ConfigSanitizerUtil::sanitizeUrl(getenv('MAGENTO_BASE_URL')) . $this->versionUrl; - print "Fetching version information from {$url}"; + print "Fetching version information from {$url}\n"; + $ch = curl_init($url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -336,7 +335,9 @@ private function printMagentoVersionInfo() $response = "No version information available."; } - print "\nVersion Information: {$response}\n"; + if (MftfApplicationConfig::getConfig()->verboseEnabled()) { + print "\nVersion Information: {$response}\n"; + } } /** From 9d03330d5ad2ffbcb79926209439255694dc87cb Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 12 Apr 2018 10:11:21 -0500 Subject: [PATCH 57/77] MQE-809: Throw a warning or error when step key referencing in merges is invalid or ambiguous - undo edits to wrong Robofile --- RoboFile.php | 1 - 1 file changed, 1 deletion(-) diff --git a/RoboFile.php b/RoboFile.php index 717200a81..c0af4c72c 100644 --- a/RoboFile.php +++ b/RoboFile.php @@ -49,7 +49,6 @@ function generateTests(array $tests, $opts = ['config' => null, 'force' => true, { require 'dev' . DIRECTORY_SEPARATOR . 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; $GLOBALS['GENERATE_TESTS'] = true; - if (!$this->isProjectBuilt()) { $this->say("<info>Please run bin/mftf build:project and configure your environment (.env) first.</info>"); exit(\Robo\Result::EXITCODE_ERROR); From f00b2ff4342d934df073b7e2008cf62d7e142f8a Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 16 Apr 2018 22:09:51 +0300 Subject: [PATCH 58/77] MQE-914: Multiple Uniqueness References On Same Entity Generates Incorrectly - refactored uniquenessFunctionCall in TestGenerator; fixes bug. - updated verification tests to catch test case. --- ...thPassedArgumentAndStringSelectorParam.txt | 2 +- ...gleParameterSelectorFromPassedArgument.txt | 2 +- .../Resources/DataReplacementTest.txt | 30 ++++++++++--------- .../Resources/ParameterArrayTest.txt | 12 ++++---- .../Resources/SectionReplacementTest.txt | 20 ++++++------- .../TestModule/Test/DataReplacementTest.xml | 2 ++ .../Util/TestGenerator.php | 29 +++++++----------- 7 files changed, 46 insertions(+), 51 deletions(-) diff --git a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt index c16a6a26e..043a71c94 100644 --- a/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt +++ b/dev/tests/verification/Resources/ActionGroupWithPassedArgumentAndStringSelectorParam.txt @@ -29,6 +29,6 @@ class ActionGroupWithPassedArgumentAndStringSelectorParamCest */ public function ActionGroupWithPassedArgumentAndStringSelectorParam(AcceptanceTester $I) { - $I->see("John".msq("UniquePerson"), "#element .test1"); + $I->see("John" . msq("UniquePerson"), "#element .test1"); } } diff --git a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt index 98c561e3c..fb10e7847 100644 --- a/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt +++ b/dev/tests/verification/Resources/ActionGroupWithSingleParameterSelectorFromPassedArgument.txt @@ -29,6 +29,6 @@ class ActionGroupWithSingleParameterSelectorFromPassedArgumentCest */ public function ActionGroupWithSingleParameterSelectorFromPassedArgument(AcceptanceTester $I) { - $I->see("Doe", "#element .John".msq("UniquePerson")); + $I->see("Doe", "#element .John" . msq("UniquePerson")); } } diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index a6d620a2c..d5121931c 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -33,20 +33,22 @@ class DataReplacementTestCest $I->conditionalClick("Doe", "#John", true); $I->amOnUrl("John.html"); $I->searchAndMultiSelectOption("#selector", ["John", "Doe"]); - $I->fillField("#selector", "StringBefore ".msq("uniqueData")."John StringAfter"); - $I->fillField("#".msq("uniqueData")."John", "input"); - $I->dragAndDrop("#".msq("uniqueData")."John", msq("uniqueData")."John"); - $I->conditionalClick(msq("uniqueData")."John", "#".msq("uniqueData")."John", true); - $I->amOnUrl(msq("uniqueData")."John.html"); - $I->searchAndMultiSelectOption("#selector", [msq("uniqueData")."John", "Doe"]); - $I->fillField("#selector", "StringBefore Doe".msq("uniqueData")." StringAfter"); - $I->fillField("#Doe".msq("uniqueData"), "input"); - $I->dragAndDrop("#Doe".msq("uniqueData"), "Doe".msq("uniqueData")); - $I->conditionalClick("Doe".msq("uniqueData"), "#Doe".msq("uniqueData"), true); - $I->amOnUrl("Doe".msq("uniqueData").".html"); - $I->searchAndMultiSelectOption("#selector", ["John", "Doe".msq("uniqueData")]); - $I->searchAndMultiSelectOption("#selector", [msq("uniqueData")."John", "Doe".msq("uniqueData")]); - $I->selectMultipleOptions("#Doe".msq("uniqueData"), "#element", [msq("uniqueData")."John", "Doe".msq("uniqueData")]); + $I->fillField("#selector", "StringBefore " . msq("uniqueData") . "John StringAfter"); + $I->fillField("#" . msq("uniqueData") . "John", "input"); + $I->dragAndDrop("#" . msq("uniqueData") . "John", msq("uniqueData") . "John"); + $I->conditionalClick(msq("uniqueData") . "John", "#" . msq("uniqueData") . "John", true); + $I->amOnUrl(msq("uniqueData") . "John.html"); + $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe"]); + $I->click("#" . msq("uniqueData") . "John#" . msq("uniqueData") . "John"); + $I->click("#Doe" . msq("uniqueData") . "#Doe" . msq("uniqueData")); + $I->fillField("#selector", "StringBefore Doe" . msq("uniqueData") . " StringAfter"); + $I->fillField("#Doe" . msq("uniqueData"), "input"); + $I->dragAndDrop("#Doe" . msq("uniqueData"), "Doe" . msq("uniqueData")); + $I->conditionalClick("Doe" . msq("uniqueData"), "#Doe" . msq("uniqueData"), true); + $I->amOnUrl("Doe" . msq("uniqueData") . ".html"); + $I->searchAndMultiSelectOption("#selector", ["John", "Doe" . msq("uniqueData")]); + $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); + $I->selectMultipleOptions("#Doe" . msq("uniqueData"), "#element", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); $I->fillField(".selector", "0"); } } diff --git a/dev/tests/verification/Resources/ParameterArrayTest.txt b/dev/tests/verification/Resources/ParameterArrayTest.txt index 2e196225d..f60478037 100644 --- a/dev/tests/verification/Resources/ParameterArrayTest.txt +++ b/dev/tests/verification/Resources/ParameterArrayTest.txt @@ -32,19 +32,19 @@ class ParameterArrayTestCest $simpleDataKey = new DataPersistenceHandler($simpleParamData, []); $simpleDataKey->createEntity(); $I->searchAndMultiSelectOption("#selector", ["name"]); - $I->searchAndMultiSelectOption("#selector", [msq("simpleParamData")."prename"]); - $I->searchAndMultiSelectOption("#selector", ["postname".msq("simpleParamData")]); + $I->searchAndMultiSelectOption("#selector", [msq("simpleParamData") . "prename"]); + $I->searchAndMultiSelectOption("#selector", ["postname" . msq("simpleParamData")]); $I->searchAndMultiSelectOption("#selector", [$simpleDataKey->getCreatedDataByName('name')]); $I->searchAndMultiSelectOption("#selector", ["name", $simpleDataKey->getCreatedDataByName('name')]); $I->searchAndMultiSelectOption("#selector", ['someKey' => $simpleDataKey->getCreatedDataByName('name')]); $I->searchAndMultiSelectOption("#selector", ['someKey' => "name"]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => msq("simpleParamData")."prename"]); - $I->searchAndMultiSelectOption("#selector", ['someKey' => "postname".msq("simpleParamData")]); + $I->searchAndMultiSelectOption("#selector", ['someKey' => msq("simpleParamData") . "prename"]); + $I->searchAndMultiSelectOption("#selector", ['someKey' => "postname" . msq("simpleParamData")]); $I->unselectOption("#selector", ['foo']); $I->unselectOption("#selector", ['foo', 'bar']); $I->unselectOption("#selector", ["name"]); - $I->unselectOption("#selector", [msq("simpleParamData")."prename"]); - $I->unselectOption("#selector", ["postname".msq("simpleParamData")]); + $I->unselectOption("#selector", [msq("simpleParamData") . "prename"]); + $I->unselectOption("#selector", ["postname" . msq("simpleParamData")]); $I->unselectOption("#selector", [$simpleDataKey->getCreatedDataByName('name')]); $I->unselectOption("#selector", ["name", $simpleDataKey->getCreatedDataByName('name')]); } diff --git a/dev/tests/verification/Resources/SectionReplacementTest.txt b/dev/tests/verification/Resources/SectionReplacementTest.txt index b89eb130b..07a8e7400 100644 --- a/dev/tests/verification/Resources/SectionReplacementTest.txt +++ b/dev/tests/verification/Resources/SectionReplacementTest.txt @@ -38,14 +38,14 @@ class SectionReplacementTestCest $I->click("#John .Doe"); $I->click("#John-Doe .Tiberius"); $I->click("#John-Doe .John [Tiberius]"); - $I->click("#element .".msq("uniqueData")."John"); - $I->click("#".msq("uniqueData")."John .stringLiteral2"); - $I->click("#".msq("uniqueData")."John-stringLiteral2 .stringLiteral3"); - $I->click("#".msq("uniqueData")."John-stringLiteral2 ."); - $I->click("#element .Doe".msq("uniqueData")); - $I->click("#Doe".msq("uniqueData")." .stringLiteral2"); - $I->click("#Doe".msq("uniqueData")."-stringLiteral2 .stringLiteral3"); - $I->click("#Doe".msq("uniqueData")."-stringLiteral2 .Doe"); + $I->click("#element ." . msq("uniqueData") . "John"); + $I->click("#" . msq("uniqueData") . "John .stringLiteral2"); + $I->click("#" . msq("uniqueData") . "John-stringLiteral2 .stringLiteral3"); + $I->click("#" . msq("uniqueData") . "John-stringLiteral2 ." . msq("uniqueData") . "John [stringLiteral3]"); + $I->click("#element .Doe" . msq("uniqueData")); + $I->click("#Doe" . msq("uniqueData") . " .stringLiteral2"); + $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .stringLiteral3"); + $I->click("#Doe" . msq("uniqueData") . "-stringLiteral2 .Doe" . msq("uniqueData") . " [stringLiteral3]"); $I->amGoingTo("create entity that has the stepKey: createdData"); $simpleData = DataObjectHandler::getInstance()->getObject("simpleData"); $createdData = new DataPersistenceHandler($simpleData, []); @@ -60,7 +60,7 @@ class SectionReplacementTestCest $I->click("#John-Doe .John [Tiberius]"); $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " .John"); $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " .{$data}"); - $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " .".msq("uniqueData")."John"); - $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " .Doe".msq("uniqueData")); + $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " ." . msq("uniqueData") . "John"); + $I->click("#stringLiteral1-" . $createdData->getCreatedDataByName('firstname') . " .Doe" . msq("uniqueData")); } } diff --git a/dev/tests/verification/TestModule/Test/DataReplacementTest.xml b/dev/tests/verification/TestModule/Test/DataReplacementTest.xml index 7aadebb47..f12c70b26 100644 --- a/dev/tests/verification/TestModule/Test/DataReplacementTest.xml +++ b/dev/tests/verification/TestModule/Test/DataReplacementTest.xml @@ -22,6 +22,8 @@ <conditionalClick stepKey="dependentSelectorReplaceMSQPrefix" dependentSelector="#{{uniqueData.firstname}}" selector="{{uniqueData.firstname}}" visible="true"/> <amOnUrl stepKey="urlReplaceMSQPrefix" url="{{uniqueData.firstname}}.html"/> <searchAndMultiSelectOption stepKey="parameterArrayReplacementMSQPrefix" selector="#selector" parameterArray="[{{uniqueData.firstname}}, {{simpleData.lastname}}]"/> + <click stepKey="selectorReplaceDupedMSQPrefix" selector="#{{uniqueData.firstname}}#{{uniqueData.firstname}}"/> + <click stepKey="selectorReplaceDupedMSQSuffix" selector="#{{uniqueData.lastname}}#{{uniqueData.lastname}}"/> <fillField stepKey="inputReplaceMSQSuffix" selector="#selector" userInput="StringBefore {{uniqueData.lastname}} StringAfter"/> <fillField stepKey="selectorReplaceMSQSuffix" selector="#{{uniqueData.lastname}}" userInput="input"/> diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index e10626f64..81e055fe6 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -1576,26 +1576,17 @@ private function addUniquenessToParamArray($input) */ private function addUniquenessFunctionCall($input) { - $output = ''; - - preg_match('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\("[\w]+"\)/', $input, $matches); - if (!empty($matches)) { - $parts = preg_split('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\("[\w]+"\)/', $input, -1); - for ($i = 0; $i < count($parts); $i++) { - $parts[$i] = $this->stripWrappedQuotes($parts[$i]); - } - if (!empty($parts[0])) { - $output = $this->wrapWithDoubleQuotes($parts[0]); - } - $output .= $output === '' ? $matches[0] : '.' . $matches[0]; - if (!empty($parts[1])) { - $output .= '.' . $this->wrapWithDoubleQuotes($parts[1]); - } - } else { - $output = $this->wrapWithDoubleQuotes($input); + $output = $this->wrapWithDoubleQuotes($input); + + //Match on msq(\"entityName\") + preg_match_all('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\(\\\\"[\w]+\\\\"\)/', $output, $matches); + foreach (array_unique($matches[0]) as $match) { + preg_match('/\\\\"([\w]+)\\\\"/', $match, $entityMatch); + $entity = $entityMatch[1]; + $output = str_replace($match, '" . msq("' . $entity . '") . "', $output); } - - return $output; + // trim unnecessary "" . and . "" + return preg_replace('/(?(?<![\\\\])"" \. )| \. ""/', "", $output); } /** From 31e913e63411c02e2dde14fe108d74ca8fb4cd5b Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 16 Apr 2018 22:32:00 +0300 Subject: [PATCH 59/77] MQE-699: Exception message for "conditionalClick" failure uses '$selector' instead of '$dependentSelector' - $selector -> $dependentSelector. --- .../FunctionalTestingFramework/Module/MagentoWebDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index cfffac9bc..3736433f5 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -472,7 +472,7 @@ public function conditionalClick($selector, $dependentSelector, $visible) { $el = $this->_findElements($dependentSelector); if (sizeof($el) > 1) { - throw new \Exception("more than one element matches selector " . $selector); + throw new \Exception("more than one element matches selector " . $dependentSelector); } $clickCondition = null; From 29522c8cb22bb16ba79a1ef0d4d6329a9b0a4c06 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Tue, 17 Apr 2018 17:21:23 +0300 Subject: [PATCH 60/77] MQE-919: Order of test merging - Changed order to module, appcode, then vendor. --- src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 0fdf37f75..ccf96cb06 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -236,8 +236,8 @@ private function aggregateTestModulePaths() . 'vendor' . DIRECTORY_SEPARATOR; $codePathsToPattern = [ - $appCodePath => '/Test/Acceptance', $modulePath => '', + $appCodePath => '/Test/Acceptance', $vendorCodePath => '/Test/Acceptance' ]; From 8d6eadfc3db0b4016f5d997d84c0cc94d62b5803 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Tue, 17 Apr 2018 19:29:24 +0300 Subject: [PATCH 61/77] MQE-918: Support for MFTF folder - appCode/VendorCode paths changed to meet AC. --- .../FunctionalTestingFramework/Util/ModuleResolver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index ccf96cb06..2a1adfe56 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -237,8 +237,8 @@ private function aggregateTestModulePaths() $codePathsToPattern = [ $modulePath => '', - $appCodePath => '/Test/Acceptance', - $vendorCodePath => '/Test/Acceptance' + $appCodePath => '/Test/MFTF', + $vendorCodePath => '/Test/MFTF' ]; foreach ($codePathsToPattern as $codePath => $pattern) { From d3ad9555c31e1bc4df80ec336b7181a912dc38f6 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathanjosiah@gmail.com> Date: Wed, 18 Apr 2018 11:02:30 -0500 Subject: [PATCH 62/77] Added slash escapement and moved condition for array entity arguments --- .../Test/Objects/ActionObjectTest.php | 5 +++-- .../Test/Objects/ActionObject.php | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php index aafc32fcf..b5aa8c4b1 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Test/Objects/ActionObjectTest.php @@ -268,7 +268,8 @@ public function testResolveArrayData() $entityDataObject = new EntityDataObject('EntityDataObject', 'test', [ 'values' => [ 'value1', - 'value2' + 'value2', + '"My" Value' ] ], [], '', ''); $this->mockDataHandlerWithData($entityDataObject); @@ -279,7 +280,7 @@ public function testResolveArrayData() // Verify $expected = [ 'selector' => '#selector', - 'userInput' => '["value1","value2"]' + 'userInput' => '["value1","value2","\"My\" Value"]' ]; $this->assertEquals($expected, $actionObject->getCustomActionAttributes()); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 8841f6471..f84b8f46b 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -493,13 +493,12 @@ private function findAndReplaceReferences($objectHandler, $inputString) throw new TestReferenceException("Could not resolve entity reference " . $inputString); } } + elseif (is_array($replacement)) { + $replacement = '["' . implode('","', array_map('addSlashes',$replacement)) . '"]'; + } $replacement = $this->resolveParameterization($parameterized, $replacement, $match, $obj); - if (is_array($replacement)) { - $replacement = '["' . implode('","', $replacement) . '"]'; - } - $outputString = str_replace($match, $replacement, $outputString); } return $outputString; From 9bce2765b3061f92d831fdfe4fd6950ea5c63d18 Mon Sep 17 00:00:00 2001 From: Nathan Smith <nathanjosiah@gmail.com> Date: Wed, 18 Apr 2018 13:45:00 -0500 Subject: [PATCH 63/77] Moved array entity data replacement conditional --- .../Test/Objects/ActionObject.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index aa8721874..8a57013b1 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -495,6 +495,10 @@ private function findAndReplaceReferences($objectHandler, $inputString) $this->setTimeout($obj->getElement($objField)->getTimeout()); } elseif (get_class($obj) == EntityDataObject::class) { $replacement = $this->resolveEntityDataObjectReference($obj, $match); + + if (is_array($replacement)) { + $replacement = '["' . implode('","', array_map('addSlashes', $replacement)) . '"]'; + } } if ($replacement === null) { @@ -504,9 +508,6 @@ private function findAndReplaceReferences($objectHandler, $inputString) throw new TestReferenceException("Could not resolve entity reference " . $inputString); } } - elseif (is_array($replacement)) { - $replacement = '["' . implode('","', array_map('addSlashes',$replacement)) . '"]'; - } $replacement = $this->resolveParameterization($parameterized, $replacement, $match, $obj); From ebbe2d1080356b62a17ea4165e8642e82fc42011 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 19 Apr 2018 17:31:17 +0300 Subject: [PATCH 64/77] MQE-501: JavaScript reporting during test run - Added ErrorLogger object, called after steps in the ContextListener --- .../Extension/ErrorLogger.php | 71 +++++++++++++++++++ .../Extension/TestContextExtension.php | 14 ++++ 2 files changed, 85 insertions(+) create mode 100644 src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php diff --git a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php new file mode 100644 index 000000000..287884065 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\FunctionalTestingFramework\Extension; + +/** + * Class ErrorLogger + * @package Magento\FunctionalTestingFramework\Extension + */ +class ErrorLogger +{ + /** + * Error Logger Instance + * @var ErrorLogger + */ + private static $errorLogger; + + /** + * Singleton method to return ErrorLogger. + * @return ErrorLogger + */ + public static function getInstance() + { + if (!self::$errorLogger) { + self::$errorLogger = new ErrorLogger(); + } + + return self::$errorLogger; + } + + /** + * ErrorLogger constructor. + */ + private function __construct() + { + // private constructor + } + + /** + * Loops through stepEvent for browser log entries + * @param \Facebook\WebDriver\Remote\RemoteWebDriver $webDriver + * @param \Codeception\Event\StepEvent $stepEvent + * @return void + */ + public function logErrors($webDriver, $stepEvent) + { + //Types available should be "server", "browser", "driver". Only care about browser at the moment. + $browserLogEntries = $webDriver->manage()->getLog("browser"); + foreach ($browserLogEntries as $entry) { + if ($entry["source"] === "javascript") { + $this->logError("javascript", $stepEvent, $entry); + } + } + } + + /** + * Logs errors to console/report. + * @param string $type + * @param \Codeception\Event\StepEvent $stepEvent + * @param array $entry + * @return void + */ + private function logError($type, $stepEvent, $entry) + { + //TODO Add to overall log + $stepEvent->getTest()->getScenario()->comment("{$type} ERROR({$entry["level"]}) - " . $entry["message"]); + } +} diff --git a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php index a518c9234..c03743dc3 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -7,6 +7,7 @@ namespace Magento\FunctionalTestingFramework\Extension; use \Codeception\Events; +use Magento\FunctionalTestingFramework\Extension\ErrorLogger; /** * Class TestContextExtension @@ -22,6 +23,7 @@ class TestContextExtension extends \Codeception\Extension */ public static $events = [ Events::TEST_FAIL => 'testFail', + Events::STEP_AFTER => 'afterStep' ]; /** @@ -68,4 +70,16 @@ public function extractContext($trace, $class) } return null; } + + /** + * Codeception event listener function, triggered after step. + * Calls ErrorLogger to log JS errors encountered. + * @param \Codeception\Event\StepEvent $e + * @return void + */ + public function afterStep(\Codeception\Event\StepEvent $e) + { + $webDriver = $this->getModule("\Magento\FunctionalTestingFramework\Module\MagentoWebDriver")->webDriver; + ErrorLogger::getInstance()->logErrors($webDriver, $e); + } } From 298bbcafa1886970813f80ce2b17902b9fdcce0a Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 19 Apr 2018 19:04:25 +0300 Subject: [PATCH 65/77] MQE-896: Page schema needs to allow empty section - PageObjectHandler now allows for empty pages. --- .../Page/Handlers/PageObjectHandlerTest.php | 19 +++++++++++++++++++ .../Page/Handlers/PageObjectHandler.php | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php index 62264569a..62e87022d 100644 --- a/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php +++ b/dev/tests/unit/Magento/FunctionalTestFramework/Page/Handlers/PageObjectHandlerTest.php @@ -51,6 +51,25 @@ public function testGetPageObject() $this->assertNull($invalidPage); } + public function testGetEmptyPage() + { + $mockData = [ + "testPage1" => [ + "url" => "testURL1", + "module" => "testModule1", + "section" => [ + ], + "area" => "test" + ]]; + $this->setMockParserOutput($mockData); + + // get pages + $page = PageObjectHandler::getInstance()->getObject('testPage1'); + + // Empty page has been read in and gotten without an exception being thrown. + $this->addToAssertionCount(1); + } + /** * Function used to set mock for parser return and force init method to run between tests. * diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index 1985af473..0c47ab7c3 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -62,7 +62,7 @@ private function __construct() } $module = $pageData[self::MODULE]; - $sectionNames = array_keys($pageData[self::SECTION]); + $sectionNames = array_keys($pageData[self::SECTION] ?? []); $parameterized = $pageData[self::PARAMETERIZED] ?? false; $this->pageObjects[$pageName] = From df4db87c4b21476895614816e6d92e8b08405789 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Thu, 19 Apr 2018 22:53:14 +0300 Subject: [PATCH 66/77] MQE-899: Excessive double quotes are being generated in WaitForElementChange method arguments - Fixed quote issue in waitForElementChanged. Also included fix for performOn, as it's the only other codeception function that takes a closure as an argument. --- dev/tests/verification/Resources/BasicFunctionalTest.txt | 2 ++ .../verification/TestModule/Test/BasicFunctionalTest.xml | 2 ++ .../FunctionalTestingFramework/Test/Objects/ActionObject.php | 1 + src/Magento/FunctionalTestingFramework/Util/TestGenerator.php | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 3452b8883..cebccc5d1 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -112,6 +112,7 @@ class BasicFunctionalTestCest $I->moveMouseOver(".functionalTestSelector"); $I->openNewTab(); $I->pauseExecution(); + $I->performOn("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}); $I->pressKey("#page", "a"); $I->pressKey("#page", ['ctrl','a'],'new'); $I->pressKey("#page", ['shift','111'],'1','x'); @@ -150,6 +151,7 @@ class BasicFunctionalTestCest $I->waitForElement(".functionalTestSelector", 30); $I->waitForElementNotVisible(".functionalTestSelector", 30); $I->waitForElementVisible(".functionalTestSelector", 30); + $I->waitForElementChange("#selector", function(\WebDriverElement $el) {return $el->isDisplayed();}); $I->waitForJS("someJsFunction", 30); $I->waitForText("someInput", 30, ".functionalTestSelector"); } diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index 70ba6299f..9af9187a8 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -73,6 +73,7 @@ <moveMouseOver selector=".functionalTestSelector" stepKey="moveMouseOverKey1"/> <openNewTab stepKey="openNewTabKey1"/> <pauseExecution stepKey="pauseExecutionKey1"/> + <performOn selector="#selector" function="function(\WebDriverElement $el) {return $el->isDisplayed();}" stepKey="performOnKey1"/> <pressKey selector="#page" userInput="a" stepKey="pressKey1"/> <pressKey selector="#page" parameterArray="[['ctrl','a'],'new']" stepKey="pressKey2"/> <pressKey selector="#page" parameterArray="[['shift','111'],'1','x']" stepKey="pressKey3"/> @@ -113,6 +114,7 @@ <waitForElement time="30" selector=".functionalTestSelector" stepKey="waitForElementKey1" /> <waitForElementNotVisible selector=".functionalTestSelector" time="30" stepKey="waitForElementNotVisibleKey1" /> <waitForElementVisible selector=".functionalTestSelector" time="30" stepKey="waitForElementVisibleKey1" /> + <waitForElementChange selector="#selector" function="function(\WebDriverElement $el) {return $el->isDisplayed();}" stepKey="waitForElementChangeKey1"/> <waitForJS function="someJsFunction" time="30" stepKey="waitForJSKey1" /> <waitForText selector=".functionalTestSelector" userInput="someInput" time="30" stepKey="waitForText1"/> </test> diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 8a57013b1..a0436bc19 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -47,6 +47,7 @@ class ActionObject const ASSERTION_VALUE_ATTRIBUTE = "value"; const DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES = ["url", "createDataKey"]; const EXTERNAL_URL_AREA_INVALID_ACTIONS = ['amOnPage']; + const FUNCTION_CLOSURE_ACTIONS = ['waitForElementChange', 'performOn']; const MERGE_ACTION_ORDER_AFTER = 'after'; const MERGE_ACTION_ORDER_BEFORE = 'before'; const ACTION_ATTRIBUTE_URL = 'url'; diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 81e055fe6..ac1402c47 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -597,6 +597,10 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " if (isset($customActionAttributes['function'])) { $function = $this->addUniquenessFunctionCall($customActionAttributes['function']); + if (in_array($actionObject->getType(), ActionObject::FUNCTION_CLOSURE_ACTIONS)) { + // Argument must be a closure function, not a string. + $function = trim($function, '"'); + } } if (isset($customActionAttributes['html'])) { From 83dab03624a7f4df6896e6faadc9cccd63df213c Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 18 Apr 2018 09:57:30 -0500 Subject: [PATCH 67/77] MQE-957: MFTF Changes to support DEVOPS-2029 - add new skipped logic to TestObject - update Group Object file generation to include try/catch --- .../Allure/Adapter/MagentoAllureAdapter.php | 29 ++++++++++++++++++- .../Suite/views/partials/testActions.mustache | 10 +++++-- .../Test/Objects/TestObject.php | 10 ++++++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php index c10a04506..393f29008 100644 --- a/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php +++ b/src/Magento/FunctionalTestingFramework/Allure/Adapter/MagentoAllureAdapter.php @@ -6,6 +6,7 @@ namespace Magento\FunctionalTestingFramework\Allure\Adapter; use Magento\FunctionalTestingFramework\Data\Argument\Interpreter\NullType; +use Magento\FunctionalTestingFramework\Suite\Handlers\SuiteObjectHandler; use Yandex\Allure\Adapter\AllureAdapter; use Codeception\Event\SuiteEvent; @@ -44,7 +45,7 @@ public function suiteBefore(SuiteEvent $suiteEvent) if ($this->getGroup() != null) { $suite = $suiteEvent->getSuite(); - $suiteName = ($suite->getName()) . "\\" . $this->getGroup(); + $suiteName = ($suite->getName()) . "\\" . $this->sanitizeGroupName($this->getGroup()); call_user_func(\Closure::bind( function () use ($suite, $suiteName) { @@ -64,4 +65,30 @@ function () use ($suite, $suiteName) { // call parent function parent::suiteBefore($changeSuiteEvent); } + + /** + * Function which santizes any group names changed by the framework for execution in order to consolidate reporting. + * + * @param string $group + * @return string + */ + private function sanitizeGroupName($group) + { + $suiteNames = array_keys(SuiteObjectHandler::getInstance()->getAllObjects()); + $exactMatch = in_array($group, $suiteNames); + + // if this is an existing suite name we dont' need to worry about changing it + if ($exactMatch || strpos($group, "_") === false) { + return $group; + } + + // if we can't find this group in the generated suites we have to assume that the group was split for generation + $groupNameSplit = explode("_", $group); + array_pop($groupNameSplit); + $originalName = implode("_", $groupNameSplit); + + // confirm our original name is one of the existing suite names otherwise just return the original group name + $originalName = in_array($originalName, $suiteNames) ? $originalName : $group; + return $originalName; + } } diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache index 2f6c41ef4..33dadc1d7 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache @@ -10,17 +10,21 @@ if ($webDriver->webDriver != null) { // initialize the webdriver session $webDriver->_initializeSession(); - -// execute user specified actions +try { + // execute user specified actions {{/webDriverInit}} {{#webDriverReset}} +} catch (\Exception $e) { + print $e->getMessage(); +} + // reset configuration and close session $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); $webDriver->webDriver->close(); $webDriver->webDriver = null; {{/webDriverReset}} {{#action}} -{{{action}}} + {{{action}}} {{/action}} {{#createData}} ${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}"); diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php index b2e4ee907..8d8700868 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/TestObject.php @@ -132,6 +132,10 @@ public function getAnnotations() */ public function getHooks() { + // if this test is skipped we do not want any before/after actions to generate as the tests will not run + if ($this->isSkipped()) { + return []; + } return $this->hooks; } @@ -142,6 +146,11 @@ public function getHooks() */ public function getTestActionCount() { + // a skipped action results in a single skip being appended to the beginning of the test and no execution + if ($this->isSkipped()) { + return 1; + } + $hookActions = 0; if (array_key_exists('before', $this->hooks)) { $hookActions += count($this->hooks['before']->getActions()); @@ -152,7 +161,6 @@ public function getTestActionCount() } $testActions = count($this->getOrderedActions()); - return $hookActions + $testActions; } From 32770342ab927b31c0ed48a9c23ebc1426058d95 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Wed, 18 Apr 2018 13:58:22 -0500 Subject: [PATCH 68/77] MQE-957: MFTF Changes to support DEVOPS-2029 - fix for try catch not capturing delete/create data --- .../Suite/views/SuiteClass.mustache | 13 +++++++++++-- .../Suite/views/partials/testActions.mustache | 7 +------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index a1eac08fc..b6051912d 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -34,7 +34,11 @@ class {{suiteName}} extends \Codeception\GroupObject if (self::$CURRENT_TEST_RUN == 1) { print sprintf(self::$HOOK_EXECUTION_INIT, "before"); - {{> testActions}} + try { + {{> testActions}} + } catch (\Exception $e) { + print $e->getMessage(); + } print sprintf(self::$HOOK_EXECUTION_END, "before"); } @@ -47,7 +51,12 @@ class {{suiteName}} extends \Codeception\GroupObject if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { print sprintf(self::$HOOK_EXECUTION_INIT, "after"); - {{> testActions}} + try { + {{> testActions}} + } catch (\Exception $e) { + print $e->getMessage(); + } + print sprintf(self::$HOOK_EXECUTION_END, "after"); } diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache index 33dadc1d7..46951aa46 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/partials/testActions.mustache @@ -10,13 +10,8 @@ if ($webDriver->webDriver != null) { // initialize the webdriver session $webDriver->_initializeSession(); -try { - // execute user specified actions {{/webDriverInit}} {{#webDriverReset}} -} catch (\Exception $e) { - print $e->getMessage(); -} // reset configuration and close session $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig(); @@ -24,7 +19,7 @@ $webDriver->webDriver->close(); $webDriver->webDriver = null; {{/webDriverReset}} {{#action}} - {{{action}}} +{{{action}}} {{/action}} {{#createData}} ${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}"); From e4c1f25841704a0b1aa137f8eba76303dbd43492 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Thu, 19 Apr 2018 10:29:16 -0500 Subject: [PATCH 69/77] MQE-957: MFTF Changes to support DEVOPS-2029 - mark tests as skipped if we see precondition failure --- .../Suite/views/SuiteClass.mustache | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache index b6051912d..cee281f36 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache +++ b/src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache @@ -17,8 +17,9 @@ use Magento\FunctionalTestingFramework\DataGenerator\Persist\DataPersistenceHand class {{suiteName}} extends \Codeception\GroupObject { public static $group = '{{suiteName}}'; - private static $TEST_COUNT = {{testCount}}; - private static $CURRENT_TEST_RUN = 0; + private $testCount = {{testCount}}; + private $preconditionFailure = null; + private $currentTestRun = 0; 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}} @@ -29,15 +30,25 @@ class {{suiteName}} extends \Codeception\GroupObject public function _before(\Codeception\Event\TestEvent $e) { // increment test count per execution - self::$CURRENT_TEST_RUN++; + $this->currentTestRun++; + $this->executePreConditions(); - if (self::$CURRENT_TEST_RUN == 1) { + if ($this->preconditionFailure != null) { + //if our preconditions fail, we need to mark all the tests as incomplete. + $e->getTest()->getMetadata()->setIncomplete($this->preconditionFailure); + } + } + + + private function executePreConditions() + { + if ($this->currentTestRun == 1) { print sprintf(self::$HOOK_EXECUTION_INIT, "before"); try { {{> testActions}} - } catch (\Exception $e) { - print $e->getMessage(); + } catch (\Exception $exception) { + $this->preconditionFailure = $exception->getMessage(); } print sprintf(self::$HOOK_EXECUTION_END, "before"); @@ -48,16 +59,21 @@ class {{suiteName}} extends \Codeception\GroupObject {{#after}} public function _after(\Codeception\Event\TestEvent $e) { - if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) { + $this->executePostConditions(); + } + + + private function executePostConditions() + { + if ($this->currentTestRun == $this->testCount) { print sprintf(self::$HOOK_EXECUTION_INIT, "after"); try { {{> testActions}} - } catch (\Exception $e) { - print $e->getMessage(); + } catch (\Exception $exception) { + print $exception->getMessage(); } - print sprintf(self::$HOOK_EXECUTION_END, "after"); } } From d7b3ce658b97642ee462b7dca9e92c579ea5cf63 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Fri, 20 Apr 2018 17:20:04 +0300 Subject: [PATCH 70/77] MQE-766: Drag & Drop support to allow resizing of elements - added override for dragAndDrop, and x/y offset options. --- .../Resources/BasicFunctionalTest.txt | 1 + .../TestModule/Test/BasicFunctionalTest.xml | 1 + .../Module/MagentoWebDriver.php | 31 +++++++++++++++++++ .../Test/etc/actionTypeTags.xsd | 17 +++++++++- .../Util/TestGenerator.php | 2 +- 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index cebccc5d1..57bd9dd01 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -94,6 +94,7 @@ class BasicFunctionalTestCest $I->dontSeeOptionIsSelected(".functionalTestSelector", "someInput"); $I->doubleClick(".functionalTestSelector"); $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2"); + $I->dragAndDrop(".functionalTestSelector", ".functionalTestSelector2", 100, 900); $executeJSKey1 = $I->executeJS("someJSFunction"); $I->fillField(".functionalTestSelector", "someInput"); $I->fillField(".functionalTestSelector", "0"); diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index 9af9187a8..bd7c23522 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -56,6 +56,7 @@ <dontSeeOptionIsSelected selector=".functionalTestSelector" userInput="someInput" stepKey="dontSeeOptionIsSelectedKey1" /> <doubleClick selector=".functionalTestSelector" stepKey="doubleClickKey1"/> <dragAndDrop selector1=".functionalTestSelector" selector2=".functionalTestSelector2" stepKey="dragAndDropKey1" /> + <dragAndDrop selector1=".functionalTestSelector" selector2=".functionalTestSelector2" stepKey="dragAndDropKey2" x="100" y="900"/> <executeJS function="someJSFunction" stepKey="executeJSKey1"/> <fillField selector=".functionalTestSelector" userInput="someInput" stepKey="fillFieldKey1" /> <fillField selector=".functionalTestSelector" userInput="0" stepKey="fillFieldKey2" /> diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 3736433f5..1917e8bab 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -13,6 +13,7 @@ use Facebook\WebDriver\WebDriverSelect; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\Exception\NoSuchElementException; +use Facebook\WebDriver\Interactions\WebDriverActions; use Codeception\Exception\ElementNotFound; use Codeception\Exception\ModuleConfigException; use Codeception\Exception\ModuleException; @@ -526,6 +527,36 @@ public function _before(TestInterface $test) parent::_before($test); } + /** + * Override for codeception's default dragAndDrop to include offset options. + * @param string $source + * @param string $target + * @param int $xOffset + * @param int $yOffset + * @return void + */ + public function dragAndDrop($source, $target, $xOffset = null, $yOffset = null) + { + if ($xOffset !== null || $yOffset !== null) { + $snodes = $this->matchFirstOrFail($this->baseElement, $source); + $tnodes = $this->matchFirstOrFail($this->baseElement, $target); + + $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 = new WebDriverActions($this->webDriver); + $action->moveToElement($snodes)->perform(); + $action->clickAndHold($snodes)->perform(); + $action->moveByOffset($travelX, $travelY)->perform(); + $action->release()->perform(); + } else { + parent::dragAndDrop($source, $target); + } + } + /** * Override for _failed method in Codeception method. Adds png and html attachments to allure report * following parent execution of test failure processing. diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd index ebb2e3570..9aa772858 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/actionTypeTags.xsd @@ -269,7 +269,22 @@ <xs:documentation> End point selector for drag-and-drop. </xs:documentation> - </xs:annotation></xs:attribute> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="x"> + <xs:annotation> + <xs:documentation> + X offset for drag-and-drop destination. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute type="xs:string" name="y"> + <xs:annotation> + <xs:documentation> + Y offset for drag-and-drop destination. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attributeGroup ref="commonActionAttributes"/> </xs:extension> </xs:simpleContent> diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index ac1402c47..211f010c0 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -980,7 +980,7 @@ public function generateStepsPhp($actionObjects, $hookObject = false, $actor = " $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $parameterArray, $button); break; case "dragAndDrop": - $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector1, $selector2); + $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector1, $selector2, $x, $y); break; case "selectMultipleOptions": $testSteps .= $this->wrapFunctionCall( From 0c0f3d6fbb5c8895bc793f7e048d48f24348ee71 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Fri, 20 Apr 2018 20:42:53 +0300 Subject: [PATCH 71/77] MQE-970: Tests in Suites being omitted when line counts are exact match - array_diff to array_diff_key --- .../Util/Sorter/ParallelGroupSorter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php index dfe5d3915..555eb5244 100644 --- a/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php +++ b/src/Magento/FunctionalTestingFramework/Util/Sorter/ParallelGroupSorter.php @@ -243,7 +243,7 @@ private function splitTestSuite($suiteName, $tests, $lineLimit) $split_suites["{$suiteName}_${split_count}"] = $group; $this->addSuiteToConfig($suiteName, "{$suiteName}_${split_count}", $group); - $availableTests = array_diff($availableTests, $group); + $availableTests = array_diff_key($availableTests, $group); $split_count++; } From 53ec0e87b7c883309098f81eca91346bc7ba75e7 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 23 Apr 2018 18:00:57 +0300 Subject: [PATCH 72/77] MQE-971: DeleteData using url is generated incorrectly in Suite hooks - Changed conditional for actions to use TestGenerator for deleteData where url is present and createDataKey is not. --- .../Suite/Generators/GroupClassGenerator.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php index 5ba1c3cac..e374d0e6e 100644 --- a/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php @@ -27,7 +27,6 @@ class GroupClassGenerator const LAST_REQUIRED_ENTITY_TAG = 'last'; const MUSTACHE_VAR_TAG = 'var'; const MAGENTO_CLI_COMMAND_COMMAND = 'command'; - const DATA_PERSISTENCE_ACTIONS = ["createData", "deleteData"]; const REPLACEMENT_ACTIONS = [ 'comment' => 'print' ]; @@ -133,7 +132,9 @@ private function buildHookMustacheArray($hookObj) foreach ($hookObj->getActions() as $action) { /** @var ActionObject $action */ $index = count($actions); - if (!in_array($action->getType(), self::DATA_PERSISTENCE_ACTIONS)) { + //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())) { if (!$hasWebDriverActions) { $hasWebDriverActions = true; } From 883c32784984d08b145edeebf1705b73436cf73e Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 23 Apr 2018 21:20:50 +0300 Subject: [PATCH 73/77] MQE-974: Change folder name from `MFTF` to `Mftf` - MFTF => Mftf --- .../FunctionalTestingFramework/Util/ModuleResolver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php index 2a1adfe56..f3a6eb6e6 100644 --- a/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php +++ b/src/Magento/FunctionalTestingFramework/Util/ModuleResolver.php @@ -237,8 +237,8 @@ private function aggregateTestModulePaths() $codePathsToPattern = [ $modulePath => '', - $appCodePath => '/Test/MFTF', - $vendorCodePath => '/Test/MFTF' + $appCodePath => '/Test/Mftf', + $vendorCodePath => '/Test/Mftf' ]; foreach ($codePathsToPattern as $codePath => $pattern) { From 1b40c8ec303c64b82f7a2c6c5c4987df1036dab3 Mon Sep 17 00:00:00 2001 From: Kevin Kozan <kkozan@magento.com> Date: Mon, 23 Apr 2018 21:57:59 +0300 Subject: [PATCH 74/77] MQE-925: 2.2.0 CHANGELOG - Added changelog additions for 2.2.0. --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 805595c1a..8d403f911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,56 @@ Magento Functional Testing Framework Changelog ================================================ +2.2.0 +----- +### Enhancements +* Traceability + * Javascript errors are now logged and reported in test output. + * Test failures are no longer overwritten by failures in an `<after>` hook. + * Tests will no longer execute an `<after>` hook twice if a failure triggered in the `<after>` hook. + * Tests marked with `<group value="skip">` will now appear in generated Allure reports. + * Along with the above, the `robo group` command no longer omits the `skip` group (skipped tests are picked up but not fully executed). +* Modularity + * MFTF no longer relies on relative pathing to determine its path to tests or Magento (favoring composer information if available). + * Tests and test materials are now read in from Magento modules as well as extensions in addition to `dev/tests/acceptance`. + * See DevDocs `Getting Started` for details on expected paths and merge order. +* Customizability + * Creation of Suites is now supported + * `<suite>` can include tests via `name`, module, or `<group>` tags. + * Consolidation of preconditions can be achieved via use of `<before/after>` tags in a `<suite>` + * All normal test actions are supported + * Data returned from actions is not available for reference in subsequent tests (`createData` or `grab` actions). + * `robo generate:tests` generates all suites and tests, and can be given a JSON configuration to generate specific test/suites. + * See MFTF Devdocs "Suite" page for more details. + * `<deleteData>` may now be called against a `url` instead of a stepKey reference. + * `<dragAndDrop>` may now be given an additional `x/y` offset. + * `<executeJS>` now returns a variable based on what the executed script returns. + * Added `<element>` `type="block"`. + * `<page>` elements may now be blank (contain no child sections). +* Maintainability + * `robo generate:tests --config parallel` now accepts a `--lines` argument, for grouping and sorting based on test length. + * `robo generate:tests` now checks for: + * Duplicate step keys within an `actionGroup`. + * Ambiguous or invalid `stepKey` references (in merge files). + * `robo generate:tests` now suppresses warnings by default. The command now accepts a `--verbose` flag to show full output including warnings. + +### Fixes +* Exception message for the `<conditionalClick>` action now correctly references the `selector` given. +* Usage of multiple parameterized elements in a `selector` now correctly resolves all element references. +* Usage of multiple uniqueness references on the same entity now generate correctly. +* Persisted entity references are correctly interpolated with `<page>` url of `type="admin"`. +* Metadata that contains 2 or more params in its `url` now correctly resolve parameters. +* Arguments can now be passed to `x` and `y` attributes in `actionGroup`. +* Arguments can now be passed to nested `<assert*>` action elements. +* The `<seeInField>` action can now be used to assert against empty strings. +* Empty `<data>` elements within an `<entity>` now generate correctly. +* Mapping of the `<magentoCLI>` to the custom command has been fixed. + +### GitHub Issues/Pull requests: +* [#89](https://github.com/magento/magento2-functional-testing-framework/pull/89) -- Add ability to use array entities as arguments. +* [#68](https://github.com/magento/magento2-functional-testing-framework/issues/68) -- Excessive double quotes are being generated in WaitForElementChange method arguments (fixed in [#103](https://github.com/magento/magento2-functional-testing-framework/pull/103)) +* [#31](https://github.com/magento/magento2-functional-testing-framework/issues/31) -- Can't run tests without a store having "default" store code (fixed in [#86](https://github.com/magento/magento2-functional-testing-framework/pull/86)) + 2.1.2 ----- ### Enhancements From 93f3b62ec88b2bd9971cdbc9dba98947acf49e6f Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 23 Apr 2018 15:07:22 -0500 Subject: [PATCH 75/77] MQE-977: ErrorLogger expects source to be a log paramater - update logger to validate param existence before using "source" param --- .../FunctionalTestingFramework/Extension/ErrorLogger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php index 287884065..822395921 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php +++ b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php @@ -50,7 +50,7 @@ public function logErrors($webDriver, $stepEvent) //Types available should be "server", "browser", "driver". Only care about browser at the moment. $browserLogEntries = $webDriver->manage()->getLog("browser"); foreach ($browserLogEntries as $entry) { - if ($entry["source"] === "javascript") { + if (array_key_exists("source", $entry) && $entry["source"] === "javascript") { $this->logError("javascript", $stepEvent, $entry); } } From ead3447181141604307eedd4fe477a3edfd41d1d Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 23 Apr 2018 20:27:59 -0500 Subject: [PATCH 76/77] MQE-967: Resolve MFTF/Acceptance composer.json with Magento2CE - remove symfony dependency from mftf --- composer.json | 2 +- composer.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 24257af67..e91903a6b 100755 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "flow/jsonpath": ">0.2", "fzaninotto/faker": "^1.6", "mustache/mustache": "~2.5", - "symfony/process": ">=2.7 <3.4", + "symfony/process": "^2.8 || ^3.1 || ^4.0", "vlucas/phpdotenv": "^2.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 5a43c38e2..de8728da1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "88a74b2a547a3fffeef374ec9f3865ea", + "content-hash": "d394ebb149854c00f790da70a8f0a3f3", "packages": [ { "name": "allure-framework/allure-codeception", @@ -4024,16 +4024,16 @@ }, { "name": "symfony/process", - "version": "v3.3.16", + "version": "v3.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "104b5acd605e3a7bd3ee92423540adb86f33d356" + "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/104b5acd605e3a7bd3ee92423540adb86f33d356", - "reference": "104b5acd605e3a7bd3ee92423540adb86f33d356", + "url": "https://api.github.com/repos/symfony/process/zipball/4b7d64e852886319e93ddfdecff0d744ab87658b", + "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b", "shasum": "" }, "require": { @@ -4042,7 +4042,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -4069,7 +4069,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-01-29T09:02:23+00:00" + "time": "2018-04-03T05:22:50+00:00" }, { "name": "symfony/yaml", From 4dd196d745bf836cbf0c5904a1df6dd241124309 Mon Sep 17 00:00:00 2001 From: Ian Meron <imeron@magento.com> Date: Mon, 23 Apr 2018 20:46:09 -0500 Subject: [PATCH 77/77] MQE-955: Commit proper version in MFTF composer.json/create the new release tag - increment version tag to 2.2.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e91903a6b..b59d02dce 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2-functional-testing-framework", "description": "Magento2 Functional Testing Framework", "type": "library", - "version": "2.1.2", + "version": "2.2.0", "license": "AGPL-3.0", "keywords": ["magento", "automation", "functional", "testing"], "config": {