diff --git a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt index eddaaf784..d1cae1da4 100644 --- a/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt +++ b/dev/tests/verification/Resources/ActionGroupWithStepKeyReferences.txt @@ -45,7 +45,7 @@ class ActionGroupWithStepKeyReferencesCest $I->fillField($action1); // stepKey: action1ActionGroup $I->comment("Invocation stepKey will be appended in non stepKey instances"); $action3ActionGroup = $I->executeJS($action3ActionGroup); // stepKey: action3ActionGroup - $action4ActionGroup = $I->magentoCLI($action4ActionGroup, "\"stuffHere\""); // stepKey: action4ActionGroup + $action4ActionGroup = $I->magentoCLI($action4ActionGroup, 60, "\"stuffHere\""); // stepKey: action4ActionGroup $I->comment($action4ActionGroup); $date = new \DateTime(); $date->setTimestamp(strtotime("{$action5}")); diff --git a/dev/tests/verification/Resources/BasicFunctionalTest.txt b/dev/tests/verification/Resources/BasicFunctionalTest.txt index f82c84119..bc7fe0b94 100644 --- a/dev/tests/verification/Resources/BasicFunctionalTest.txt +++ b/dev/tests/verification/Resources/BasicFunctionalTest.txt @@ -126,8 +126,14 @@ class BasicFunctionalTestCest $grabMultipleKey1 = $I->grabMultiple(".functionalTestSelector"); // stepKey: grabMultipleKey1 $grabTextFromKey1 = $I->grabTextFrom(".functionalTestSelector"); // stepKey: grabTextFromKey1 $grabValueFromKey1 = $I->grabValueFrom(".functionalTestSelector"); // stepKey: grabValueFromKey1 - $magentoCli1 = $I->magentoCLI("maintenance:enable", "\"stuffHere\""); // stepKey: magentoCli1 + $magentoCli1 = $I->magentoCLI("maintenance:enable", 60, "\"stuffHere\""); // stepKey: magentoCli1 $I->comment($magentoCli1); + $magentoCli2 = $I->magentoCLI("maintenance:enable", 120, "\"stuffHere\""); // stepKey: magentoCli2 + $I->comment($magentoCli2); + $magentoCli3 = $I->magentoCLISecret("config:set somePath " . CredentialStore::getInstance()->getSecret("someKey"), 60); // stepKey: magentoCli3 + $I->comment($magentoCli3); // stepKey: magentoCli3 + $magentoCli4 = $I->magentoCLISecret("config:set somePath " . CredentialStore::getInstance()->getSecret("someKey"), 120); // stepKey: magentoCli4 + $I->comment($magentoCli4); // stepKey: magentoCli4 $I->makeScreenshot("screenShotInput"); // stepKey: makeScreenshotKey1 $I->maximizeWindow(); // stepKey: maximizeWindowKey1 $I->moveBack(); // stepKey: moveBackKey1 diff --git a/dev/tests/verification/Resources/DataReplacementTest.txt b/dev/tests/verification/Resources/DataReplacementTest.txt index 731ae63f8..4b4531a4e 100644 --- a/dev/tests/verification/Resources/DataReplacementTest.txt +++ b/dev/tests/verification/Resources/DataReplacementTest.txt @@ -52,7 +52,7 @@ class DataReplacementTestCest $I->searchAndMultiSelectOption("#selector", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); // stepKey: parameterArrayReplacementMSQBoth $I->selectMultipleOptions("#Doe" . msq("uniqueData"), "#element", [msq("uniqueData") . "John", "Doe" . msq("uniqueData")]); // stepKey: multiSelectDataReplacement $I->fillField(".selector", "0"); // stepKey: insertZero - $insertCommand = $I->magentoCLI("do something Doe" . msq("uniqueData") . " with uniqueness"); // stepKey: insertCommand + $insertCommand = $I->magentoCLI("do something Doe" . msq("uniqueData") . " with uniqueness", 60); // stepKey: insertCommand $I->comment($insertCommand); $I->seeInPageSource("StringBefore John StringAfter"); // stepKey: htmlReplace1 $I->seeInPageSource("#John"); // stepKey: htmlReplace2 diff --git a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml index ff0708554..c23a3ce60 100644 --- a/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml +++ b/dev/tests/verification/TestModule/Test/BasicFunctionalTest.xml @@ -76,6 +76,9 @@ + + + diff --git a/docs/test/actions.md b/docs/test/actions.md index 9b125d7fc..c5dc83fdb 100644 --- a/docs/test/actions.md +++ b/docs/test/actions.md @@ -1262,6 +1262,7 @@ Attribute|Type|Use|Description ---|---|---|--- `command`|string |optional| CLI command to be executed in Magento environment. `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. diff --git a/etc/config/command.php b/etc/config/command.php index adc372e43..e3b8f1191 100644 --- a/etc/config/command.php +++ b/etc/config/command.php @@ -14,6 +14,7 @@ $tokenPassedIn = urldecode($_POST['token'] ?? ''); $command = urldecode($_POST['command'] ?? ''); $arguments = urldecode($_POST['arguments'] ?? ''); + $timeout = floatval(urldecode($_POST['timeout'] ?? 60)); // Token returned will be null if the token we passed in is invalid $tokenFromMagento = $tokenModel->loadByToken($tokenPassedIn)->getToken(); @@ -24,7 +25,7 @@ if ($valid) { $fullCommand = escapeshellcmd($magentoBinary . " $command" . " $arguments"); $process = new Symfony\Component\Process\Process($fullCommand); - $process->setIdleTimeout(60); + $process->setIdleTimeout($timeout); $process->setTimeout(0); $idleTimeout = false; try { diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 73311e0fa..cbde1ddfc 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -511,14 +511,16 @@ public function scrollToTopOfPage() /** * Takes given $command and executes it against bin/magento or custom exposed entrypoint. Returns command output. * - * @param string $command - * @param string $arguments + * @param string $command + * @param integer $timeout + * @param string $arguments * @return string + * * @throws TestFrameworkException */ - public function magentoCLI($command, $arguments = null) + public function magentoCLI($command, $timeout = null, $arguments = null) { - return $this->curlExecMagentoCLI($command, $arguments); + return $this->curlExecMagentoCLI($command, $timeout, $arguments); //TODO: calling bin/magento from pipeline is timing out, needs investigation (ref: MQE-1774) // try { // return $this->shellExecMagentoCLI($command, $arguments); @@ -673,17 +675,18 @@ public function fillSecretField($field, $value) * The data is decrypted immediately prior to data creation to avoid exposure in console or log. * * @param string $command + * @param null $timeout * @param null $arguments * @throws TestFrameworkException * @return string */ - public function magentoCLISecret($command, $arguments = null) + public function magentoCLISecret($command, $timeout = null, $arguments = null) { // to protect any secrets from being printed to console the values are executed only at the webdriver level as a // decrypted value $decryptedCommand = CredentialStore::getInstance()->decryptAllSecretsInString($command); - return $this->magentoCLI($decryptedCommand, $arguments); + return $this->magentoCLI($decryptedCommand, $timeout, $arguments); } /** @@ -835,20 +838,21 @@ public function makeScreenshot($name = null) /** * Takes given $command and executes it against bin/magento executable. Returns stdout output from the command. * - * @param string $command - * @param string $arguments + * @param string $command + * @param integer $timeout + * @param string $arguments * * @throws \RuntimeException * @return string * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ - private function shellExecMagentoCLI($command, $arguments): string + private function shellExecMagentoCLI($command, $timeout, $arguments): string { $php = PHP_BINDIR ? PHP_BINDIR . DIRECTORY_SEPARATOR. 'php' : 'php'; $binMagento = realpath(MAGENTO_BP . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'magento'); $command = $php . ' -f ' . $binMagento . ' ' . $command . ' ' . $arguments; $process = new Process(escapeshellcmd($command), MAGENTO_BP); - $process->setIdleTimeout(60); + $process->setIdleTimeout($timeout); $process->setTimeout(0); $exitCode = $process->run(); if ($exitCode !== 0) { @@ -861,12 +865,14 @@ private function shellExecMagentoCLI($command, $arguments): string /** * Takes given $command and executes it against exposed MTF CLI entry point. Returns response from server. * - * @param string $command - * @param string $arguments + * @param string $command + * @param integer $timeout + * @param string $arguments + * * @return string * @throws TestFrameworkException */ - private function curlExecMagentoCLI($command, $arguments): string + private function curlExecMagentoCLI($command, $timeout, $arguments): string { // Remove index.php if it's present in url $baseUrl = rtrim( @@ -887,6 +893,7 @@ private function curlExecMagentoCLI($command, $arguments): string 'token' => $restExecutor->getAuthToken(), getenv('MAGENTO_CLI_COMMAND_PARAMETER') => $command, 'arguments' => $arguments, + 'timeout' => $timeout, ], CurlInterface::POST, [] diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 8f60e1e90..ad5ca0c33 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -63,6 +63,7 @@ class ActionObject const DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES = ["url", "createDataKey"]; const EXTERNAL_URL_AREA_INVALID_ACTIONS = ['amOnPage']; const FUNCTION_CLOSURE_ACTIONS = ['waitForElementChange', 'performOn', 'executeInSelenium']; + const COMMAND_ACTION_ATTRIBUTES = ['magentoCLI', 'magentoCLISecret']; const MERGE_ACTION_ORDER_AFTER = 'after'; const MERGE_ACTION_ORDER_BEFORE = 'before'; const ACTION_ATTRIBUTE_TIMEZONE = 'timezone'; @@ -71,6 +72,7 @@ class ActionObject const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\((?(?!}}).)+\)}})/'; const STRING_PARAMETER_REGEX = "/'[^']+'/"; + const DEFAULT_COMMAND_WAIT_TIMEOUT = 60; const ACTION_ATTRIBUTE_USERINPUT = 'userInput'; const ACTION_TYPE_COMMENT = 'comment'; diff --git a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd index 3a002e126..2424d0d31 100644 --- a/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd +++ b/src/Magento/FunctionalTestingFramework/Test/etc/Actions/customActions.xsd @@ -50,6 +50,13 @@ + + + + Idle timeout in seconds, defaulted to 60s when not specified. + + + diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index 7b0d2916e..fcbb49ac9 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -613,7 +613,12 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato if (isset($customActionAttributes['timeout'])) { $time = $customActionAttributes['timeout']; } - $time = $time ?? ActionObject::getDefaultWaitTimeout(); + + if (in_array($actionObject->getType(), ActionObject::COMMAND_ACTION_ATTRIBUTES)) { + $time = $time ?? ActionObject::DEFAULT_COMMAND_WAIT_TIMEOUT; + } else { + $time = $time ?? ActionObject::getDefaultWaitTimeout(); + } if (isset($customActionAttributes['parameterArray']) && $actionObject->getType() != 'pressKey') { // validate the param array is in the correct format @@ -1282,6 +1287,7 @@ public function generateStepsPhp($actionObjects, $generationScope = TestGenerato $actor, $actionObject, $command, + $time, $arguments ); $testSteps .= sprintf(self::STEP_KEY_ANNOTATION, $stepKey) . PHP_EOL;