diff --git a/src/Magento/FunctionalTestingFramework/Console/CommandList.php b/src/Magento/FunctionalTestingFramework/Console/CommandList.php index ce971146e..7623bea66 100644 --- a/src/Magento/FunctionalTestingFramework/Console/CommandList.php +++ b/src/Magento/FunctionalTestingFramework/Console/CommandList.php @@ -38,6 +38,7 @@ public function __construct(array $commands = []) 'run:failed' => new RunTestFailedCommand(), 'setup:env' => new SetupEnvCommand(), 'upgrade:tests' => new UpgradeTestsCommand(), + 'static-checks' => new StaticChecksCommand(), ] + $commands; } diff --git a/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php new file mode 100644 index 000000000..fde306791 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Console/StaticChecksCommand.php @@ -0,0 +1,56 @@ +setName('static-checks') + ->setDescription('This command will run all static checks on xml test materials.'); + $this->staticChecksList = new StaticChecksList(); + } + + /** + * + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null|void + * @throws \Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $staticCheckObjects = $this->staticChecksList->getStaticChecks(); + foreach ($staticCheckObjects as $staticCheck) { + $staticOutput = $staticCheck->execute($input); + LoggingUtil::getInstance()->getLogger(get_class($staticCheck))->info($staticOutput); + $output->writeln($staticOutput); + } + } +} diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php index 7305fdc37..7cc9e8a0e 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Handlers/DataObjectHandler.php @@ -32,6 +32,7 @@ class DataObjectHandler implements ObjectHandlerInterface const _ENTITY_KEY = 'entityKey'; const _SEPARATOR = '->'; const _REQUIRED_ENTITY = 'requiredEntity'; + const _FILENAME = 'filename'; const DATA_NAME_ERROR_MSG = "Entity names cannot contain non alphanumeric characters.\tData='%s'"; /** @@ -134,6 +135,7 @@ private function processParserOutput($parserOutput) $uniquenessData = []; $vars = []; $parentEntity = null; + $filename = $rawEntity[self::_FILENAME] ?? null; if (array_key_exists(self::_DATA, $rawEntity)) { $data = $this->processDataElements($rawEntity); @@ -167,7 +169,8 @@ private function processParserOutput($parserOutput) $linkedEntities, $uniquenessData, $vars, - $parentEntity + $parentEntity, + $filename ); $entityDataObjects[$entityDataObject->getName()] = $entityDataObject; diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php index 1ba126e5f..070a08bf1 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Objects/EntityDataObject.php @@ -72,6 +72,12 @@ class EntityDataObject */ private $parentEntity; + /** + * String of filename + * @var string + */ + private $filename; + /** * Constructor * @@ -82,9 +88,18 @@ class EntityDataObject * @param string[] $uniquenessData * @param string[] $vars * @param string $parentEntity + * @param string $filename */ - public function __construct($name, $type, $data, $linkedEntities, $uniquenessData, $vars = [], $parentEntity = null) - { + public function __construct( + $name, + $type, + $data, + $linkedEntities, + $uniquenessData, + $vars = [], + $parentEntity = null, + $filename = null + ) { $this->name = $name; $this->type = $type; $this->data = $data; @@ -95,6 +110,7 @@ public function __construct($name, $type, $data, $linkedEntities, $uniquenessDat $this->vars = $vars; $this->parentEntity = $parentEntity; + $this->filename = $filename; } /** @@ -107,6 +123,16 @@ public function getName() return $this->name; } + /** + * Getter for the Entity Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Get the type of this entity data object * diff --git a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php index 1123531ed..2766b39a0 100644 --- a/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/DataGenerator/Util/DataExtensionUtil.php @@ -92,7 +92,8 @@ public function extendEntity($entityObject) $newLinkedReferences, $newUniqueReferences, $newVarReferences, - $entityObject->getParentName() + $entityObject->getParentName(), + $entityObject->getFilename() ); return $extendedEntity; } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php index caf2262fa..02c7bac66 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/PageObjectHandler.php @@ -20,6 +20,7 @@ class PageObjectHandler implements ObjectHandlerInterface const MODULE = 'module'; const PARAMETERIZED = 'parameterized'; const AREA = 'area'; + const FILENAME = 'filename'; const NAME_BLACKLIST_ERROR_MSG = "Page names cannot contain non alphanumeric characters.\tPage='%s'"; /** @@ -66,9 +67,10 @@ private function __construct() $module = $pageData[self::MODULE]; $sectionNames = array_keys($pageData[self::SECTION] ?? []); $parameterized = $pageData[self::PARAMETERIZED] ?? false; + $filename = $pageData[self::FILENAME] ?? null; $this->pageObjects[$pageName] = - new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area); + new PageObject($pageName, $url, $module, $sectionNames, $parameterized, $area, $filename); } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php index b63d5b49b..e77e4c502 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php +++ b/src/Magento/FunctionalTestingFramework/Page/Handlers/SectionObjectHandler.php @@ -22,6 +22,7 @@ class SectionObjectHandler implements ObjectHandlerInterface const LOCATOR_FUNCTION = 'locatorFunction'; const TIMEOUT = 'timeout'; const PARAMETERIZED = 'parameterized'; + const FILENAME = 'filename'; const SECTION_NAME_ERROR_MSG = "Section names cannot contain non alphanumeric characters.\tSection='%s'"; const ELEMENT_NAME_ERROR_MSG = "Element names cannot contain non alphanumeric characters.\tElement='%s'"; @@ -86,7 +87,8 @@ private function __construct() throw new XmlException($exception->getMessage() . " in Section '{$sectionName}'"); } - $this->sectionObjects[$sectionName] = new SectionObject($sectionName, $elements); + $filename = $sectionData[self::FILENAME] ?? null; + $this->sectionObjects[$sectionName] = new SectionObject($sectionName, $elements, $filename); } } diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php index 3533a13f3..d99c40240 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/PageObject.php @@ -58,6 +58,13 @@ class PageObject */ private $area; + /** + * Filename of where the page came from + * + * @var string + */ + private $filename; + /** * PageObject constructor. * @param string $name @@ -66,8 +73,9 @@ class PageObject * @param array $sections * @param boolean $parameterized * @param string $area + * @param string $filename */ - public function __construct($name, $url, $module, $sections, $parameterized, $area) + public function __construct($name, $url, $module, $sections, $parameterized, $area, $filename = null) { $this->name = $name; $this->url = $url; @@ -75,6 +83,7 @@ public function __construct($name, $url, $module, $sections, $parameterized, $ar $this->sectionNames = $sections; $this->parameterized = $parameterized; $this->area = $area; + $this->filename = $filename; } /** @@ -87,6 +96,16 @@ public function getName() return $this->name; } + /** + * Getter for the Page Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for Page URL * diff --git a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php index 9ac641bca..8c1cac326 100644 --- a/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php +++ b/src/Magento/FunctionalTestingFramework/Page/Objects/SectionObject.php @@ -25,15 +25,24 @@ class SectionObject */ private $elements = []; + /** + * Filename of where the section came from + * + * @var string + */ + private $filename; + /** * SectionObject constructor. * @param string $name * @param array $elements + * @param string $filename */ - public function __construct($name, $elements) + public function __construct($name, $elements, $filename = null) { $this->name = $name; $this->elements = $elements; + $this->filename = $filename; } /** @@ -46,6 +55,16 @@ public function getName() return $this->name; } + /** + * Getter for the Section Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for an array containing all of a section's elements. * diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckInterface.php b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckInterface.php new file mode 100644 index 000000000..45ebdea8d --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/StaticCheckInterface.php @@ -0,0 +1,22 @@ +checks = [ + 'testDependencies' => new TestDependencyCheck(), + ] + $checks; + } + + /** + * {@inheritdoc} + */ + public function getStaticChecks() + { + return $this->checks; + } +} diff --git a/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php new file mode 100644 index 000000000..e0b3cefd6 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/StaticCheck/TestDependencyCheck.php @@ -0,0 +1,386 @@ +]*name="([^"\']*)/'; + + /** + * Array of FullModuleName => [dependencies] + * @var array + */ + private $allDependencies; + + /** + * Array of FullModuleName => [dependencies], including flattened dependency tree + * @var array + */ + private $flattenedDependencies; + + /** + * Array of FullModuleName => PathToModule + * @var array + */ + private $moduleNameToPath; + + /** + * Array of FullModuleName => ComposerModuleName + * @var array + */ + private $moduleNameToComposerName; + + /** + * Transactional Array to keep track of what dependencies have already been extracted. + * @var array + */ + private $alreadyExtractedDependencies; + + /** + * Checks test dependencies, determined by references in tests versus the dependencies listed in the Magento module + * + * @param InputInterface $input + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function execute(InputInterface $input) + { + MftfApplicationConfig::create( + true, + MftfApplicationConfig::UNIT_TEST_PHASE, + false, + false + ); + + ModuleResolver::getInstance()->getModulesPath(); + if (!class_exists('\Magento\Framework\Component\ComponentRegistrar')) { + return "TEST DEPENDENCY CHECK ABORTED: MFTF must be attached or pointing to Magento codebase."; + } + $registrar = new \Magento\Framework\Component\ComponentRegistrar(); + $this->moduleNameToPath = $registrar->getPaths(\Magento\Framework\Component\ComponentRegistrar::MODULE); + $this->moduleNameToComposerName = $this->buildModuleNameToComposerName($this->moduleNameToPath); + $this->flattenedDependencies = $this->buildComposerDependencyList(); + + $allModules = ModuleResolver::getInstance()->getModulesPath(); + $filePaths = [ + DIRECTORY_SEPARATOR . 'Test' . DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR . 'ActionGroup' . DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR, + ]; + // These files can contain references to other modules. + $testXmlFiles = $this->buildFileList($allModules, $filePaths[0]); + $actionGroupXmlFiles = $this->buildFileList($allModules, $filePaths[1]); + $dataXmlFiles= $this->buildFileList($allModules, $filePaths[2]); + + $testErrors = []; + $testErrors += $this->findErrorsInFileSet($testXmlFiles); + $testErrors += $this->findErrorsInFileSet($actionGroupXmlFiles); + $testErrors += $this->findErrorsInFileSet($dataXmlFiles); + + //print all errors to file + return $this->printErrorsToFile($testErrors); + } + + /** + * Finds all reference errors in given set of files + * @param Finder $files + * @return array + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException + */ + private function findErrorsInFileSet($files) + { + $testErrors = []; + foreach ($files as $filePath) { + $modulePath = dirname(dirname(dirname(dirname($filePath)))); + $moduleFullName = array_search($modulePath, $this->moduleNameToPath) ?? null; + // Not a module, is either dev/tests/acceptance or loose folder with test materials + if ($moduleFullName == null) { + continue; + } + + $contents = file_get_contents($filePath); + $allEntities = []; + preg_match_all(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN, $contents, $braceReferences); + preg_match_all(self::ACTIONGROUP_REGEX_PATTERN, $contents, $actionGroupReferences); + preg_match_all(self::EXTENDS_REGEX_PATTERN, $contents, $extendReferences); + + // Remove Duplicates + $braceReferences[0] = array_unique($braceReferences[0]); + $actionGroupReferences[1] = array_unique($actionGroupReferences[1]); + $braceReferences[1] = array_unique($braceReferences[1]); + $braceReferences[2] = array_filter(array_unique($braceReferences[2])); + + // Check `data` entities in {{data.field}} or {{data.field('param')}} + foreach ($braceReferences[0] as $reference) { + // trim `{{data.field}}` to `data` + preg_match('/{{([^.]+)/', $reference, $entityName); + // Double check that {{data.field}} isn't an argument for an ActionGroup + $entity = $this->findEntity($entityName[1]); + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); + if (array_search($entityName[1], $possibleArgument[1]) !== false) { + continue; + } + if ($entity !== null) { + $allEntities[$entity->getName()] = $entity; + } + } + + // Drill down into params in {{ref.params('string', $data.key$, entity.reference)}} + foreach ($braceReferences[2] as $parameterizedReference) { + preg_match( + ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, + $parameterizedReference, + $arguments + ); + $splitArguments = explode(',', ltrim(rtrim($arguments[0], ")"), "(")); + foreach ($splitArguments as $argument) { + // Do nothing for 'string' or $persisted.data$ + if (preg_match(ActionObject::STRING_PARAMETER_REGEX, $argument)) { + continue; + } elseif (preg_match(TestGenerator::PERSISTED_OBJECT_NOTATION_REGEX, $argument)) { + continue; + } + // trim `data.field` to `data` + preg_match('/([^.]+)/', $argument, $entityName); + // Double check that {{data.field}} isn't an argument for an ActionGroup + $entity = $this->findEntity($entityName[1]); + preg_match_all(self::ACTIONGROUP_ARGUMENT_REGEX_PATTERN, $contents, $possibleArgument); + if (array_search($entityName[1], $possibleArgument[1]) !== false) { + continue; + } + if ($entity !== null) { + $allEntities[$entity->getName()] = $entity; + } + } + } + // Check actionGroup references + foreach ($actionGroupReferences[1] as $reference) { + $entity = $this->findEntity($reference); + if ($entity !== null) { + $allEntities[$entity->getName()] = $entity; + } + } + // Check extended objects + foreach ($extendReferences[1] as $reference) { + $entity = $this->findEntity($reference); + if ($entity !== null) { + $allEntities[$entity->getName()] = $entity; + } + } + + $currentModule = $this->moduleNameToComposerName[$moduleFullName]; + $modulesReferencedInTest = $this->getModuleDependenciesFromReferences($allEntities, $currentModule); + $moduleDependencies = $this->flattenedDependencies[$moduleFullName]; + // Find Violations + $violatingReferences = []; + foreach ($modulesReferencedInTest as $entityName => $files) { + $valid = false; + foreach ($files as $module) { + if (array_key_exists($module, $moduleDependencies) || $module == $currentModule) { + $valid = true; + break; + } + } + if (!$valid) { + $violatingReferences[$entityName] = $files; + } + } + + if (!empty($violatingReferences)) { + // Build error output + $errorOutput = "\nFile \"{$filePath->getRealPath()}\""; + $errorOutput .= "\ncontains entity references that violate dependency constraints:\n\t\t"; + foreach ($violatingReferences as $entityName => $files) { + $errorOutput .= "\n\t {$entityName} from module(s): " . implode(", ", $files); + } + $testErrors[$filePath->getRealPath()][] = $errorOutput; + } + } + return $testErrors; + } + + /** + * Builds and returns array of FullModuleNae => composer name + * @param array $array + * @return array + */ + private function buildModuleNameToComposerName($array) + { + $returnList = []; + foreach ($array as $moduleName => $path) { + $composerData = json_decode(file_get_contents($path . DIRECTORY_SEPARATOR . "composer.json")); + $returnList[$moduleName] = $composerData->name; + } + return $returnList; + } + + /** + * Builds and returns flattened dependency list based on composer dependencies + * @return array + */ + private function buildComposerDependencyList() + { + $flattenedDependencies = []; + + foreach ($this->moduleNameToPath as $moduleName => $pathToModule) { + $composerData = json_decode(file_get_contents($pathToModule . DIRECTORY_SEPARATOR . "composer.json"), true); + $this->allDependencies[$moduleName] = $composerData['require']; + } + foreach ($this->allDependencies as $moduleName => $dependencies) { + $this->alreadyExtractedDependencies = []; + $flattenedDependencies[$moduleName] = $this->extractSubDependencies($moduleName); + } + return $flattenedDependencies; + } + + /** + * Recursive function to fetch dependencies of given dependency, and its child dependencies + * @param string $subDependencyName + * @return array + */ + private function extractSubDependencies($subDependencyName) + { + $flattenedArray = []; + + if (array_search($subDependencyName, $this->alreadyExtractedDependencies) !== false) { + return $flattenedArray; + } + + if (isset($this->allDependencies[$subDependencyName])) { + $subDependencyArray = $this->allDependencies[$subDependencyName]; + $flattenedArray = array_merge($flattenedArray, $this->allDependencies[$subDependencyName]); + + // Keep track of dependencies that have already been used, prevents circular dependency problems + $this->alreadyExtractedDependencies[] = $subDependencyName; + foreach ($subDependencyArray as $composerDependencyName => $version) { + $subDependencyFullName = array_search($composerDependencyName, $this->moduleNameToComposerName); + $flattenedArray = array_merge( + $flattenedArray, + $this->extractSubDependencies($subDependencyFullName) + ); + } + } + return $flattenedArray; + } + + /** + * Finds unique array ofcomposer dependencies of given testObjects + * @param array $array + * @return array + */ + private function getModuleDependenciesFromReferences($array) + { + $filenames = []; + foreach ($array as $item) { + // Should it append ALL filenames, including merges? + $allFiles = explode(",", $item->getFilename()); + foreach ($allFiles as $file) { + $modulePath = dirname(dirname(dirname(dirname($file)))); + $fullModuleName = array_search($modulePath, $this->moduleNameToPath); + $composerModuleName = $this->moduleNameToComposerName[$fullModuleName]; + $filenames[$item->getName()][] = $composerModuleName; + } + } + return $filenames; + } + + /** + * Builds list of all XML files in given modulePaths + path given + * @param string $modulePaths + * @param string $path + * @return Finder + */ + private function buildFileList($modulePaths, $path) + { + $finder = new Finder(); + foreach ($modulePaths as $modulePath) { + if (!realpath($modulePath . $path)) { + continue; + } + $finder->files()->in($modulePath . $path)->name("*.xml"); + } + return $finder->files(); + } + + /** + * Attempts to find any MFTF entity by its name. Returns null if none are found. + * @param string $name + * @return mixed + * @throws \Magento\FunctionalTestingFramework\Exceptions\TestReferenceException + * @throws \Magento\FunctionalTestingFramework\Exceptions\XmlException + */ + private function findEntity($name) + { + if ($name == '_ENV' || $name == '_CREDS') { + return null; + } + + if (DataObjectHandler::getInstance()->getObject($name)) { + return DataObjectHandler::getInstance()->getObject($name); + } elseif (PageObjectHandler::getInstance()->getObject($name)) { + return PageObjectHandler::getInstance()->getObject($name); + } elseif (SectionObjectHandler::getInstance()->getObject($name)) { + return SectionObjectHandler::getInstance()->getObject($name); + } + + try { + return ActionGroupObjectHandler::getInstance()->getObject($name); + } catch (TestReferenceException $e) { + } + try { + return TestObjectHandler::getInstance()->getObject($name); + } catch (TestReferenceException $e) { + } + return null; + } + + /** + * Prints out given errors to file, and returns summary result string + * @param array $errors + * @return string + */ + private function printErrorsToFile($errors) + { + if (empty($errors)) { + return "No Dependency errors found."; + } + $outputPath = getcwd() . DIRECTORY_SEPARATOR . "mftf-dependency-checks.txt"; + $fileResource = fopen($outputPath, 'w'); + $header = "MFTF File Dependency Check:\n"; + fwrite($fileResource, $header); + foreach ($errors as $test => $error) { + fwrite($fileResource, $error[0] . PHP_EOL); + } + fclose($fileResource); + $errorCount = count($errors); + $output = "Dependency errors found across {$errorCount} file(s). Error details output to {$outputPath}"; + return $output; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php index 67c1ba25c..2bed003e5 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionGroupObject.php @@ -71,6 +71,13 @@ class ActionGroupObject */ private $parentActionGroup; + /** + * Filename where actionGroup came from + * + * @var string + */ + private $filename; + /** * ActionGroupObject constructor. * @@ -78,8 +85,9 @@ class ActionGroupObject * @param ArgumentObject[] $arguments * @param array $actions * @param string $parentActionGroup + * @param string $filename */ - public function __construct($name, $arguments, $actions, $parentActionGroup) + public function __construct($name, $arguments, $actions, $parentActionGroup, $filename = null) { $this->varAttributes = array_merge( ActionObject::SELECTOR_ENABLED_ATTRIBUTES, @@ -90,6 +98,7 @@ public function __construct($name, $arguments, $actions, $parentActionGroup) $this->arguments = $arguments; $this->parsedActions = $actions; $this->parentActionGroup = $parentActionGroup; + $this->filename = $filename; } /** @@ -409,6 +418,16 @@ public function getName() return $this->name; } + /** + * Getter for the Action Group Filename + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + /** * Getter for the Parent Action Group Name * diff --git a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php index 166a61889..5205d69cf 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php +++ b/src/Magento/FunctionalTestingFramework/Test/Objects/ActionObject.php @@ -68,6 +68,7 @@ class ActionObject const ACTION_ATTRIBUTE_SELECTOR = 'selector'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER = '/\(.+\)/'; const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\((?(?!}}).)+\)}})/'; + const STRING_PARAMETER_REGEX = "/'[^']+'/"; const DEFAULT_WAIT_TIMEOUT = 10; /** @@ -464,8 +465,6 @@ private function stripAndSplitReference($reference) */ private function stripAndReturnParameters($reference) { - // 'string', or 'string,!@#$%^&*()_+, ' - $literalParametersRegex = "/'[^']+'/"; $postCleanupDelimiter = "::::"; preg_match(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, $reference, $matches); @@ -473,8 +472,9 @@ private function stripAndReturnParameters($reference) $strippedReference = ltrim(rtrim($matches[0], ")"), "("); // Pull out all 'string' references, as they can contain 'string with , comma in it' - preg_match_all($literalParametersRegex, $strippedReference, $literalReferences); - $strippedReference = preg_replace($literalParametersRegex, '&&stringReference&&', $strippedReference); + // 'string', or 'string,!@#$%^&*()_+, ' + preg_match_all(self::STRING_PARAMETER_REGEX, $strippedReference, $literalReferences); + $strippedReference = preg_replace(self::STRING_PARAMETER_REGEX, '&&stringReference&&', $strippedReference); // Sanitize 'string, data.field,$persisted.field$' => 'string::::data.field::::$persisted.field$' $strippedReference = preg_replace('/,/', ', ', $strippedReference); diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php index 04f739af6..6d7e67b38 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ActionGroupObjectExtractor.php @@ -76,7 +76,8 @@ public function extractActionGroup($actionGroupData) $actionGroupData[self::NAME], $arguments, $actions, - $actionGroupReference + $actionGroupReference, + $actionGroupData[self::FILENAME] ); } diff --git a/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php b/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php index e67037940..4c3949ef3 100644 --- a/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php +++ b/src/Magento/FunctionalTestingFramework/Test/Util/ObjectExtensionUtil.php @@ -131,7 +131,8 @@ public function extendActionGroup($actionGroupObject) $actionGroupObject->getName(), $extendedArguments, $newActions, - $actionGroupObject->getParentName() + $actionGroupObject->getParentName(), + $actionGroupObject->getFilename() ); return $extendedActionGroup; } diff --git a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php index abc62eb20..07cb96b3a 100644 --- a/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php +++ b/src/Magento/FunctionalTestingFramework/Util/TestGenerator.php @@ -38,6 +38,7 @@ class TestGenerator const HOOK_SCOPE = 'hook'; const SUITE_SCOPE = 'suite'; const PRESSKEY_ARRAY_ANCHOR_KEY = '987654321098765432109876543210'; + const PERSISTED_OBJECT_NOTATION_REGEX = '/\${1,2}[\w.\[\]]+\${1,2}/'; /** * Path to the export dir. @@ -1332,7 +1333,7 @@ private function resolveTestVariable($args, $actionOrigin) } $outputArg = $arg; // Math on $data.key$ and $$data.key$$ - preg_match_all('/\${1,2}[\w.\[\]]+\${1,2}/', $outputArg, $matches); + preg_match_all(self::PERSISTED_OBJECT_NOTATION_REGEX, $outputArg, $matches); $this->replaceMatchesIntoArg($matches[0], $outputArg); //trim "{$variable}" into $variable