diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index 5bcfd55dd..e78f1b49c 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -126,6 +126,12 @@ class BasicFunctionalTestCest $I->comment($magentoCli3); // stepKey: magentoCli3 $magentoCli4 = $I->magentoCLISecret("config:set somePath " . $I->getSecret("someKey"), 120); // stepKey: magentoCli4 $I->comment($magentoCli4); // stepKey: magentoCli4 + $cronAllGroups = $I->magentoCron("", 70); // stepKey: cronAllGroups + $I->comment($cronAllGroups); + $cronSingleGroup = $I->magentoCron("index", 70); // stepKey: cronSingleGroup + $I->comment($cronSingleGroup); + $cronMultipleGroups = $I->magentoCron("a b c", 70); // stepKey: cronMultipleGroups + $I->comment($cronMultipleGroups); $I->makeScreenshot("screenShotInput"); // stepKey: makeScreenshotKey1 $I->maximizeWindow(); // stepKey: maximizeWindowKey1 $I->moveBack(); // stepKey: moveBackKey1 diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index c23a3ce60..1ef25b43d 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -79,6 +79,9 @@ + + + @@ -143,4 +146,4 @@ - \ No newline at end of file + diff --git a/docs/test/actions.md b/docs/test/actions.md index c5dc83fdb..59fd12aa2 100644 --- a/docs/test/actions.md +++ b/docs/test/actions.md @@ -1274,6 +1274,29 @@ Attribute|Type|Use|Description ``` +### magentoCron + +Used to execute Magento Cron jobs. Groups may be provided optionally. Internal mechanism of `` ensures that Cron Job of single group is ran with 60 seconds interval. + +Attribute|Type|Use|Description +---|---|---|--- +`groups`|string |optional| Run only specified groups of Cron Jobs +`arguments`|string |optional| Unescaped arguments to be passed in with the CLI command. +`timeout`|string|optional| Number of seconds CLI command can run without outputting anything. +`stepKey`|string|required| A unique identifier of the action. +`before`|string|optional| `stepKey` of action that must be executed next. +`after`|string|optional| `stepKey` of preceding action. + + +#### Example +```xml + + + + + +``` + ### makeScreenshot See [makeScreenshot docs on codeception.com](http://codeception.com/docs/modules/WebDriver#makeScreenshot). diff --git a/etc/di.xml b/etc/di.xml index 3e6313bf3..e5e31cf87 100644 --- a/etc/di.xml +++ b/etc/di.xml @@ -8,7 +8,7 @@ + ]> diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index a95586b26..f44d7e1a0 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -53,6 +53,9 @@ class MagentoWebDriver extends WebDriver { use AttachmentSupport; + const MAGENTO_CRON_INTERVAL = 60; + const MAGENTO_CRON_COMMAND = 'cron:run'; + /** * List of known magento loading masks by selector * @@ -121,6 +124,13 @@ class MagentoWebDriver extends WebDriver */ private $jsErrors = []; + /** + * Contains last execution times for Cron + * + * @var int[] + */ + private $cronExecution = []; + /** * Sanitizes config, then initializes using parent. * @@ -552,6 +562,75 @@ public function magentoCLI($command, $timeout = null, $arguments = null) return $response; } + /** + * Executes Magento Cron keeping the interval (> 60 seconds between each run) + * + * @param string|null $cronGroups + * @param integer|null $timeout + * @param string|null $arguments + * @return string + */ + public function magentoCron($cronGroups = null, $timeout = null, $arguments = null) + { + $cronGroups = explode(' ', $cronGroups); + return $this->executeCronjobs($cronGroups, $timeout, $arguments); + } + + /** + * Updates last execution time for Cron + * + * @param array $cronGroups + * @return void + */ + private function notifyCronFinished(array $cronGroups = []) + { + if (empty($cronGroups)) { + $this->cronExecution['*'] = time(); + } + + foreach ($cronGroups as $group) { + $this->cronExecution[$group] = time(); + } + } + + /** + * Returns last Cron execution time for specific cron or all crons + * + * @param array $cronGroups + * @return integer + */ + private function getLastCronExecution(array $cronGroups = []) + { + if (empty($cronGroups)) { + return (int)max($this->cronExecution); + } + + $cronGroups = array_merge($cronGroups, ['*']); + + return array_reduce($cronGroups, function ($lastExecution, $group) { + if (isset($this->cronExecution[$group]) && $this->cronExecution[$group] > $lastExecution) { + $lastExecution = $this->cronExecution[$group]; + } + + return (int)$lastExecution; + }, 0); + } + + /** + * Returns time to wait for next run + * + * @param array $cronGroups + * @param integer $cronInterval + * @return integer + */ + private function getCronWait(array $cronGroups = [], int $cronInterval = self::MAGENTO_CRON_INTERVAL) + { + $nextRun = $this->getLastCronExecution($cronGroups) + $cronInterval; + $toNextRun = $nextRun - time(); + + return max(0, $toNextRun); + } + /** * Runs DELETE request to delete a Magento entity against the url given. * @@ -971,4 +1050,36 @@ public function getSecret($key) { return CredentialStore::getInstance()->getSecret($key); } + + /** + * Waits proper amount of time to perform Cron execution + * + * @param string $cronGroups + * @param integer $timeout + * @param string $arguments + * @return string + * @throws TestFrameworkException + */ + private function executeCronjobs($cronGroups, $timeout, $arguments): string + { + $cronGroups = array_filter($cronGroups); + + $waitFor = $this->getCronWait($cronGroups); + + if ($waitFor) { + $this->wait($waitFor); + } + + $command = array_reduce($cronGroups, function ($command, $cronGroup) { + $command .= ' --group=' . $cronGroup; + return $command; + }, self::MAGENTO_CRON_COMMAND); + $timeStart = microtime(true); + $cronResult = $this->magentoCLI($command, $timeout, $arguments); + $timeEnd = microtime(true); + + $this->notifyCronFinished($cronGroups); + + return sprintf('%s (wait: %ss, execution: %ss)', $cronResult, $waitFor, round($timeEnd - $timeStart, 2)); + } } diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 2424d0d31..8670d9885 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -12,6 +12,7 @@ + @@ -62,6 +63,40 @@ + + + + Executes Magento Cron Jobs (selected groups) + + + + + + + + Cron groups to be executed (separated by space) + + + + + + + Arguments for Magento CLI command, will not be escaped. + + + + + + + Idle timeout in seconds, defaulted to 60s when not specified. + + + + + + + + @@ -285,4 +320,4 @@ - \ No newline at end of file + diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 66c11975c..c67d8288f 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -52,12 +52,14 @@ class TestGenerator 'retrieveEntityField', 'getSecret', 'magentoCLI', + 'magentoCron', 'generateDate', 'field' ]; const RULE_ERROR = 'On step with stepKey "%s", only one of the attributes: "%s" can be use for action "%s"'; const STEP_KEY_ANNOTATION = " // stepKey: %s"; + const CRON_INTERVAL = 60; const ARRAY_WRAP_OPEN = '['; const ARRAY_WRAP_CLOSE = ']'; @@ -538,6 +540,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $dependentSelector = null; $visible = null; $command = null; + $cronGroups = ''; $arguments = null; $sortOrder = null; $storeCode = null; @@ -555,6 +558,9 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['command'])) { $command = $this->addUniquenessFunctionCall($customActionAttributes['command']); } + if (isset($customActionAttributes['groups'])) { + $cronGroups = $this->addUniquenessFunctionCall($customActionAttributes['groups']); + } if (isset($customActionAttributes['arguments'])) { $arguments = $this->addUniquenessFunctionCall($customActionAttributes['arguments']); } @@ -1252,6 +1258,22 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $stepKey ); break; + case 'magentoCron': + $testSteps .= $this->wrapFunctionCallWithReturnValue( + $stepKey, + $actor, + $actionObject, + $cronGroups, + self::CRON_INTERVAL + $time, + $arguments + ); + $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL; + $testSteps .= sprintf( + "\t\t$%s->comment(\$%s);", + $actor, + $stepKey + ); + break; case "field": $fieldKey = $actionObject->getCustomActionAttributes()['key']; $input = $this->resolveStepKeyReferences($input, $actionObject->getActionOrigin());