Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
TestGenerator.php
Go to the documentation of this file.
1 <?php
8 
27 
33 {
34  const REQUIRED_ENTITY_REFERENCE = 'createDataKey';
35  const GENERATED_DIR = '_generated';
36  const DEFAULT_DIR = 'default';
37  const TEST_SCOPE = 'test';
38  const HOOK_SCOPE = 'hook';
39  const SUITE_SCOPE = 'suite';
40  const PRESSKEY_ARRAY_ANCHOR_KEY = '987654321098765432109876543210';
41 
47  private $exportDirectory;
48 
54  private $exportDirName;
55 
61  private $tests;
62 
68  private $consoleOutput;
69 
75  private $debug;
76 
82  private $currentGenerationScope;
83 
91  private function __construct($exportDir, $tests, $debug = false)
92  {
93  // private constructor for factory
94  $this->exportDirName = $exportDir ?? self::DEFAULT_DIR;
95  $exportDir = $exportDir ?? self::DEFAULT_DIR;
96  $this->exportDirectory = TESTS_MODULE_PATH
97  . DIRECTORY_SEPARATOR
98  . self::GENERATED_DIR
99  . DIRECTORY_SEPARATOR
100  . $exportDir;
101  $this->tests = $tests;
102  $this->consoleOutput = new \Symfony\Component\Console\Output\ConsoleOutput();
103  $this->debug = $debug;
104  }
105 
114  public static function getInstance($dir = null, $tests = [], $debug = false)
115  {
116  return new TestGenerator($dir, $tests, $debug);
117  }
118 
124  public function getExportDir()
125  {
126  return $this->exportDirectory;
127  }
128 
136  private function loadAllTestObjects($testsToIgnore)
137  {
138  if ($this->tests === null || empty($this->tests)) {
139  $testObjects = TestObjectHandler::getInstance()->getAllObjects();
140  return array_diff_key($testObjects, $testsToIgnore);
141  }
142 
143  // If we have a custom configuration, we need to check the tests passed in to insure that we can generate
144  // them in the current context.
145  $invalidTestObjects = array_intersect_key($this->tests, $testsToIgnore);
146  if (!empty($invalidTestObjects)) {
147  throw new TestReferenceException(
148  "Cannot reference test configuration for generation without accompanying suite.",
149  ['tests' => array_keys($invalidTestObjects)]
150  );
151  }
152 
153  return $this->tests;
154  }
155 
165  private function createCestFile($testPhp, $filename)
166  {
167  $exportFilePath = $this->exportDirectory . DIRECTORY_SEPARATOR . $filename . ".php";
168  $file = fopen($exportFilePath, 'w');
169 
170  if (!$file) {
171  throw new \Exception("Could not open the file.");
172  }
173 
174  fwrite($file, $testPhp);
175  fclose($file);
176  }
177 
188  public function createAllTestFiles($testManifest = null, $testsToIgnore = null)
189  {
190  if ($this->tests === null) {
191  // no-op if the test configuration is null
192  return;
193  }
194 
195  DirSetupUtil::createGroupDir($this->exportDirectory);
196  if ($testsToIgnore === null) {
197  $testsToIgnore = SuiteObjectHandler::getInstance()->getAllTestReferences();
198  }
199 
200  $testPhpArray = $this->assembleAllTestPhp($testManifest, $testsToIgnore);
201  foreach ($testPhpArray as $testPhpFile) {
202  $this->createCestFile($testPhpFile[1], $testPhpFile[0]);
203  }
204  }
205 
215  private function assembleTestPhp($testObject)
216  {
217  $usePhp = $this->generateUseStatementsPhp();
218  $classAnnotationsPhp = $this->generateAnnotationsPhp($testObject->getAnnotations());
219 
220  $className = $testObject->getCodeceptionName();
221  try {
222  if (!$testObject->isSkipped()) {
223  $hookPhp = $this->generateHooksPhp($testObject->getHooks());
224  } else {
225  $hookPhp = null;
226  }
227  $testsPhp = $this->generateTestPhp($testObject);
228  } catch (TestReferenceException $e) {
229  throw new TestReferenceException($e->getMessage() . "\n" . $testObject->getFilename());
230  }
231 
232  $cestPhp = "<?php\n";
233  $cestPhp .= "namespace Magento\AcceptanceTest\\_" . $this->exportDirName . "\Backend;\n\n";
234  $cestPhp .= $usePhp;
235  $cestPhp .= $classAnnotationsPhp;
236  $cestPhp .= sprintf("class %s\n", $className);
237  $cestPhp .= "{\n";
238  $cestPhp .= $hookPhp;
239  $cestPhp .= $testsPhp;
240  $cestPhp .= "}\n";
241 
242  return $cestPhp;
243  }
244 
252  private function assembleAllTestPhp($testManifest, array $testsToIgnore)
253  {
255  $testObjects = $this->loadAllTestObjects($testsToIgnore);
256  $cestPhpArray = [];
257 
258  foreach ($testObjects as $test) {
259  // Do not generate test if it is an extended test and parent does not exist
260  if ($test->isSkipped() && !empty($test->getParentName())) {
261  try {
262  TestObjectHandler::getInstance()->getObject($test->getParentName());
263  } catch (TestReferenceException $e) {
264  print("{$test->getName()} will not be generated. Parent {$e->getMessage()} \n");
265  continue;
266  }
267  }
268 
269  $this->debug("<comment>Start creating test: " . $test->getCodeceptionName() . "</comment>");
270  $php = $this->assembleTestPhp($test);
271  $cestPhpArray[] = [$test->getCodeceptionName(), $php];
272 
273  $debugInformation = $test->getDebugInformation();
274  $this->debug($debugInformation);
275  $this->debug("<comment>Finish creating test: " . $test->getCodeceptionName() . "</comment>" . PHP_EOL);
276 
277  //write to manifest here if manifest is not null
278  if ($testManifest != null) {
279  $testManifest->addTest($test);
280  }
281  }
282 
283  return $cestPhpArray;
284  }
285 
292  private function debug($messages)
293  {
294  if ($this->debug && $messages) {
295  $messages = (array)$messages;
296  foreach ($messages as $message) {
297  $this->consoleOutput->writeln($message);
298  }
299  }
300  }
301 
308  private function generateUseStatementsPhp()
309  {
310  $useStatementsPhp = "use Magento\FunctionalTestingFramework\AcceptanceTester;\n";
311  $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Handlers\CredentialStore;\n";
312  $useStatementsPhp .= "use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler;\n";
313  $useStatementsPhp .= "use \Codeception\Util\Locator;\n";
314 
315  $allureStatements = [
316  "Yandex\Allure\Adapter\Annotation\Features;",
317  "Yandex\Allure\Adapter\Annotation\Stories;",
318  "Yandex\Allure\Adapter\Annotation\Title;",
319  "Yandex\Allure\Adapter\Annotation\Description;",
320  "Yandex\Allure\Adapter\Annotation\Parameter;",
321  "Yandex\Allure\Adapter\Annotation\Severity;",
322  "Yandex\Allure\Adapter\Model\SeverityLevel;",
323  "Yandex\Allure\Adapter\Annotation\TestCaseId;\n"
324  ];
325 
326  foreach ($allureStatements as $allureUseStatement) {
327  $useStatementsPhp .= sprintf("use %s\n", $allureUseStatement);
328  }
329 
330  return $useStatementsPhp;
331  }
332 
340  private function generateAnnotationsPhp($annotationsObject, $isMethod = false)
341  {
342  //TODO: Refactor to deal with PHPMD.CyclomaticComplexity
343  if ($isMethod) {
344  $indent = "\t";
345  } else {
346  $indent = "";
347  }
348 
349  $annotationsPhp = "{$indent}/**\n";
350 
351  foreach ($annotationsObject as $annotationType => $annotationName) {
352  //Remove conditional and output useCaseId upon completion of MQE-588
353  if ($annotationType == "useCaseId") {
354  continue;
355  }
356  if (!$isMethod) {
357  $annotationsPhp .= $this->generateClassAnnotations($annotationType, $annotationName);
358  } else {
359  $annotationsPhp .= $this->generateMethodAnnotations($annotationType, $annotationName);
360  }
361  }
362 
363  if ($isMethod) {
364  $annotationsPhp .= $this->generateMethodAnnotations();
365  }
366 
367  $annotationsPhp .= "{$indent} */\n";
368 
369  return $annotationsPhp;
370  }
371 
379  private function generateMethodAnnotations($annotationType = null, $annotationName = null)
380  {
381  $annotationToAppend = null;
382  $indent = "\t";
383 
384  switch ($annotationType) {
385  case "features":
386  $features = "";
387  foreach ($annotationName as $name) {
388  $features .= sprintf("\"%s\"", $name);
389 
390  if (next($annotationName)) {
391  $features .= ", ";
392  }
393  }
394  $annotationToAppend .= sprintf("{$indent} * @Features({%s})\n", $features);
395  break;
396 
397  case "stories":
398  $stories = "";
399  foreach ($annotationName as $name) {
400  $stories .= sprintf("\"%s\"", $name);
401 
402  if (next($annotationName)) {
403  $stories .= ", ";
404  }
405  }
406  $annotationToAppend .= sprintf("{$indent} * @Stories({%s})\n", $stories);
407  break;
408 
409  case "severity":
410  $annotationToAppend = sprintf("{$indent} * @Severity(level = SeverityLevel::%s)\n", $annotationName[0]);
411  break;
412 
413  case null:
414  $annotationToAppend = sprintf(
415  "{$indent} * @Parameter(name = \"%s\", value=\"$%s\")\n",
416  "AcceptanceTester",
417  "I"
418  );
419  $annotationToAppend .= sprintf("{$indent} * @param %s $%s\n", "AcceptanceTester", "I");
420  $annotationToAppend .= "{$indent} * @return void\n";
421  $annotationToAppend .= "{$indent} * @throws \Exception\n";
422  break;
423  }
424 
426  }
427 
436  private function generateClassAnnotations($annotationType, $annotationName)
437  {
438  $annotationToAppend = null;
439 
440  switch ($annotationType) {
441  case "title":
442  $annotationToAppend = sprintf(" * @Title(\"%s\")\n", $annotationName[0]);
443  break;
444 
445  case "description":
446  $annotationToAppend = sprintf(" * @Description(\"%s\")\n", $annotationName[0]);
447  break;
448 
449  case "testCaseId":
450  $annotationToAppend = sprintf(" * @TestCaseId(\"%s\")\n", $annotationName[0]);
451  break;
452 
453  case "useCaseId":
454  $annotationToAppend = sprintf(" * @UseCaseId(\"%s\")\n", $annotationName[0]);
455  break;
456 
457  case "group":
458  foreach ($annotationName as $group) {
459  $annotationToAppend .= sprintf(" * @group %s\n", $group);
460  }
461  break;
462  }
463 
464  return $annotationToAppend;
465  }
466 
481  public function generateStepsPhp($actionObjects, $generationScope = TestGenerator::TEST_SCOPE, $actor = "I")
482  {
483  //TODO: Refactor Method according to PHPMD warnings, remove @SuppressWarnings accordingly.
484  $testSteps = "";
485  $this->currentGenerationScope = $generationScope;
486 
487  foreach ($actionObjects as $actionObject) {
488  $stepKey = $actionObject->getStepKey();
489  $customActionAttributes = $actionObject->getCustomActionAttributes();
490  $attribute = null;
491  $selector = null;
492  $selector1 = null;
493  $selector2 = null;
494  $input = null;
495  $parameterArray = null;
496  $returnVariable = null;
497  $x = null;
498  $y = null;
499  $html = null;
500  $url = null;
501  $function = null;
502  $time = null;
503  $locale = null;
504  $username = null;
505  $password = null;
506  $width = null;
507  $height = null;
508  $requiredAction = null;
509  $value = null;
510  $button = null;
511  $parameter = null;
512  $dependentSelector = null;
513  $visible = null;
514  $command = null;
515  $arguments = null;
516  $sortOrder = null;
517  $storeCode = null;
518  $format = null;
519 
520  $assertExpected = null;
521  $assertActual = null;
522  $assertMessage = null;
523  $assertIsStrict = null;
524  $assertDelta = null;
525 
526  // Validate action attributes and print notice messages on violation.
527  $this->validateXmlAttributesMutuallyExclusive($stepKey, $actionObject->getType(), $customActionAttributes);
528 
529  if (isset($customActionAttributes['command'])) {
530  $command = $this->addUniquenessFunctionCall($customActionAttributes['command']);
531  }
532  if (isset($customActionAttributes['arguments'])) {
533  $arguments = $this->addUniquenessFunctionCall($customActionAttributes['arguments']);
534  }
535 
536  if (isset($customActionAttributes['attribute'])) {
537  $attribute = $customActionAttributes['attribute'];
538  }
539 
540  if (isset($customActionAttributes['sortOrder'])) {
541  $sortOrder = $customActionAttributes['sortOrder'];
542  }
543 
544  if (isset($customActionAttributes['userInput']) && isset($customActionAttributes['url'])) {
545  $input = $this->addUniquenessFunctionCall($customActionAttributes['userInput']);
546  $url = $this->addUniquenessFunctionCall($customActionAttributes['url']);
547  } elseif (isset($customActionAttributes['userInput'])) {
548  $input = $this->addUniquenessFunctionCall($customActionAttributes['userInput']);
549  } elseif (isset($customActionAttributes['url'])) {
550  $input = $this->addUniquenessFunctionCall($customActionAttributes['url']);
551  $url = $this->addUniquenessFunctionCall($customActionAttributes['url']);
552  } elseif (isset($customActionAttributes['expectedValue'])) {
553  //For old Assert backwards Compatibility, remove when deprecating
554  $assertExpected = $this->addUniquenessFunctionCall($customActionAttributes['expectedValue']);
555  } elseif (isset($customActionAttributes['regex'])) {
556  $input = $this->addUniquenessFunctionCall($customActionAttributes['regex']);
557  }
558 
559  if (isset($customActionAttributes['date']) && isset($customActionAttributes['format'])) {
560  $input = $this->addUniquenessFunctionCall($customActionAttributes['date']);
561  if ($input === "") {
562  $input = "\"Now\"";
563  }
564  $format = $this->addUniquenessFunctionCall($customActionAttributes['format']);
565  if ($format === "") {
566  $format = "\"r\"";
567  }
568  }
569 
570  if (isset($customActionAttributes['expected'])) {
571  $assertExpected = $this->resolveValueByType(
572  $customActionAttributes['expected'],
573  isset($customActionAttributes['expectedType']) ? $customActionAttributes['expectedType'] : null
574  );
575  }
576  if (isset($customActionAttributes['actual'])) {
577  $assertActual = $this->resolveValueByType(
578  $customActionAttributes['actual'],
579  isset($customActionAttributes['actualType']) ? $customActionAttributes['actualType'] : null
580  );
581  }
582  if (isset($customActionAttributes['message'])) {
583  $assertMessage = $this->addUniquenessFunctionCall($customActionAttributes['message']);
584  }
585  if (isset($customActionAttributes['delta'])) {
586  $assertDelta = $this->resolveValueByType($customActionAttributes['delta'], "float");
587  }
588  if (isset($customActionAttributes['strict'])) {
589  $assertIsStrict = $this->resolveValueByType($customActionAttributes['strict'], "bool");
590  }
591 
592  if (isset($customActionAttributes['time'])) {
593  $time = $customActionAttributes['time'];
594  }
595  if (isset($customActionAttributes['timeout'])) {
596  $time = $customActionAttributes['timeout'];
597  }
598 
599  if (isset($customActionAttributes['parameterArray']) && $actionObject->getType() != 'pressKey') {
600  // validate the param array is in the correct format
601  $this->validateParameterArray($customActionAttributes['parameterArray']);
602 
603  $parameterArray = "[";
604  $parameterArray .= $this->addUniquenessToParamArray($customActionAttributes['parameterArray']);
605  $parameterArray .= "]";
606  }
607 
608  if (isset($customActionAttributes['requiredAction'])) {
609  $requiredAction = $customActionAttributes['requiredAction'];
610  }
611 
612  if (isset($customActionAttributes['selectorArray'])) {
613  $selector = $customActionAttributes['selectorArray'];
614  } elseif (isset($customActionAttributes['selector'])) {
615  $selector = $this->addUniquenessFunctionCall($customActionAttributes['selector']);
616  $selector = $this->resolveLocatorFunctionInAttribute($selector);
617  }
618 
619  if (isset($customActionAttributes['selector1']) || isset($customActionAttributes['filterSelector'])) {
620  $selectorOneValue = $customActionAttributes['selector1'] ?? $customActionAttributes['filterSelector'];
621  $selector1 = $this->addUniquenessFunctionCall($selectorOneValue);
622  $selector1 = $this->resolveLocatorFunctionInAttribute($selector1);
623  }
624 
625  if (isset($customActionAttributes['selector2']) || isset($customActionAttributes['optionSelector'])) {
626  $selectorTwoValue = $customActionAttributes['selector2'] ?? $customActionAttributes['optionSelector'];
627  $selector2 = $this->addUniquenessFunctionCall($selectorTwoValue);
628  $selector2 = $this->resolveLocatorFunctionInAttribute($selector2);
629  }
630 
631  if (isset($customActionAttributes['x'])) {
632  $x = $customActionAttributes['x'];
633  }
634 
635  if (isset($customActionAttributes['y'])) {
636  $y = $customActionAttributes['y'];
637  }
638 
639  if (isset($customActionAttributes['function'])) {
640  $function = $this->addUniquenessFunctionCall($customActionAttributes['function']);
641  if (in_array($actionObject->getType(), ActionObject::FUNCTION_CLOSURE_ACTIONS)) {
642  // Argument must be a closure function, not a string.
643  $function = trim($function, '"');
644  }
645  // turn $javaVariable => \$javaVariable but not {$mftfVariable}
646  if ($actionObject->getType() == "executeJS") {
647  $function = preg_replace('/(?<!{)(\$[A-Za-z._]+)(?![A-z.]*+\$)/', '\\\\$1', $function);
648  }
649  }
650 
651  if (isset($customActionAttributes['html'])) {
652  $html = $customActionAttributes['html'];
653  }
654 
655  if (isset($customActionAttributes['locale'])) {
656  $locale = $this->wrapWithDoubleQuotes($customActionAttributes['locale']);
657  }
658 
659  if (isset($customActionAttributes['username'])) {
660  $username = $this->wrapWithDoubleQuotes($customActionAttributes['username']);
661  }
662 
663  if (isset($customActionAttributes['password'])) {
664  $password = $this->wrapWithDoubleQuotes($customActionAttributes['password']);
665  }
666 
667  if (isset($customActionAttributes['width'])) {
668  $width = $customActionAttributes['width'];
669  }
670 
671  if (isset($customActionAttributes['height'])) {
672  $height = $customActionAttributes['height'];
673  }
674 
675  if (isset($customActionAttributes['value'])) {
676  $value = $this->wrapWithDoubleQuotes($customActionAttributes['value']);
677  }
678 
679  if (isset($customActionAttributes['button'])) {
680  $button = $this->wrapWithDoubleQuotes($customActionAttributes['button']);
681  }
682 
683  if (isset($customActionAttributes['parameter'])) {
684  $parameter = $this->wrapWithDoubleQuotes($customActionAttributes['parameter']);
685  }
686 
687  if (isset($customActionAttributes['dependentSelector'])) {
688  $dependentSelector = $this->addUniquenessFunctionCall($customActionAttributes['dependentSelector']);
689  }
690 
691  if (isset($customActionAttributes['visible'])) {
692  $visible = $customActionAttributes['visible'];
693  }
694 
695  if (isset($customActionAttributes['storeCode'])) {
696  $storeCode = $customActionAttributes['storeCode'];
697  }
698  switch ($actionObject->getType()) {
699  case "createData":
700  $entity = $customActionAttributes['entity'];
701  //Add an informative statement to help the user debug test runs
702  $testSteps .= sprintf(
703  "\t\t$%s->amGoingTo(\"create entity that has the stepKey: %s\");\n",
704  $actor,
705  $stepKey
706  );
707 
708  //TODO refactor entity field override to not be individual actionObjects
709  $customEntityFields =
710  $customActionAttributes[ActionObjectExtractor::ACTION_OBJECT_PERSISTENCE_FIELDS] ?? [];
711 
712  $requiredEntityKeys = [];
713  foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) {
714  if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') {
715  //append ActionGroup if provided
716  $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null;
717  $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup;
718  }
719  }
720  // Build array of requiredEntities
721  $requiredEntityKeysArray = "";
722  if (!empty($requiredEntityKeys)) {
723  $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
724  }
725  //Determine Scope
727  if ($generationScope == TestGenerator::HOOK_SCOPE) {
729  } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
731  }
732 
733  $createEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->createEntity(";
734  $createEntityFunctionCall .= "\n\t\t\t\"{$stepKey}\",";
735  $createEntityFunctionCall .= "\n\t\t\t\"{$scope}\",";
736  $createEntityFunctionCall .= "\n\t\t\t\"{$entity}\"";
737  $createEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]";
738  if (count($customEntityFields) > 1) {
739  $createEntityFunctionCall .= ",\n\t\t\t\${$stepKey}Fields";
740  } else {
741  $createEntityFunctionCall .= ",\n\t\t\tnull";
742  }
743  if ($storeCode !== null) {
744  $createEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\"";
745  }
746  $createEntityFunctionCall .= "\n\t\t);\n";
747  $testSteps .= $createEntityFunctionCall;
748  break;
749  case "deleteData":
750  if (isset($customActionAttributes['createDataKey'])) {
751  $key = $this->resolveStepKeyReferences(
752  $customActionAttributes['createDataKey'],
753  $actionObject->getActionOrigin(),
754  true
755  );
756  $actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null;
757  $key .= $actionGroup;
758  //Add an informative statement to help the user debug test runs
759  $contextSetter = sprintf(
760  "\t\t$%s->amGoingTo(\"delete entity that has the createDataKey: %s\");\n",
761  $actor,
762  $key
763  );
764 
765  //Determine Scope
767  if ($generationScope == TestGenerator::HOOK_SCOPE) {
769  } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
771  }
772 
773  $deleteEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->deleteEntity(";
774  $deleteEntityFunctionCall .= "\n\t\t\t\"{$key}\",";
775  $deleteEntityFunctionCall .= "\n\t\t\t\"{$scope}\"";
776  $deleteEntityFunctionCall .= "\n\t\t);\n";
777 
778  $testSteps .= $contextSetter;
779  $testSteps .= $deleteEntityFunctionCall;
780  } else {
781  $url = $this->resolveAllRuntimeReferences([$url])[0];
782  $url = $this->resolveTestVariable([$url], null)[0];
783  $output = sprintf(
784  "\t\t$%s->deleteEntityByUrl(%s);\n",
785  $actor,
786  $url
787  );
788  $testSteps .= $output;
789  }
790  break;
791  case "updateData":
792  $key = $this->resolveStepKeyReferences(
793  $customActionAttributes['createDataKey'],
794  $actionObject->getActionOrigin(),
795  true
796  );
797  $updateEntity = $customActionAttributes['entity'];
798  $actionGroup = $actionObject->getCustomActionAttributes()['actionGroup'] ?? null;
799  $key .= $actionGroup;
800 
801  //Add an informative statement to help the user debug test runs
802  $testSteps .= sprintf(
803  "\t\t$%s->amGoingTo(\"update entity that has the createdDataKey: %s\");\n",
804  $actor,
805  $key
806  );
807 
808  // Build array of requiredEntities
809  $requiredEntityKeys = [];
810  foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) {
811  if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') {
812  //append ActionGroup if provided
813  $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null;
814  $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup;
815  }
816  }
817  $requiredEntityKeysArray = "";
818  if (!empty($requiredEntityKeys)) {
819  $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
820  }
821 
823  if ($generationScope == TestGenerator::HOOK_SCOPE) {
825  } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
827  }
828 
829  $updateEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->updateEntity(";
830  $updateEntityFunctionCall .= "\n\t\t\t\"{$key}\",";
831  $updateEntityFunctionCall .= "\n\t\t\t\"{$scope}\",";
832  $updateEntityFunctionCall .= "\n\t\t\t\"{$updateEntity}\"";
833  $updateEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]";
834  if ($storeCode !== null) {
835  $updateEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\"";
836  }
837  $updateEntityFunctionCall .= "\n\t\t);\n";
838  $testSteps .= $updateEntityFunctionCall;
839 
840  break;
841  case "getData":
842  $entity = $customActionAttributes['entity'];
843  $index = null;
844  if (isset($customActionAttributes['index'])) {
845  $index = (int)$customActionAttributes['index'];
846  }
847  //Add an informative statement to help the user debug test runs
848  $testSteps .= sprintf(
849  "\t\t$%s->amGoingTo(\"get entity that has the stepKey: %s\");\n",
850  $actor,
851  $stepKey
852  );
853 
854  // Build array of requiredEntities
855  $requiredEntityKeys = [];
856  foreach ($actionObject->getCustomActionAttributes() as $actionAttribute) {
857  if (is_array($actionAttribute) && $actionAttribute['nodeName'] == 'requiredEntity') {
858  $requiredEntityActionGroup = $actionAttribute['actionGroup'] ?? null;
859  $requiredEntityKeys[] = $actionAttribute['createDataKey'] . $requiredEntityActionGroup;
860  }
861  }
862  $requiredEntityKeysArray = "";
863  if (!empty($requiredEntityKeys)) {
864  $requiredEntityKeysArray = '"' . implode('", "', $requiredEntityKeys) . '"';
865  }
866 
867  //Determine Scope
869  if ($generationScope == TestGenerator::HOOK_SCOPE) {
871  } elseif ($generationScope == TestGenerator::SUITE_SCOPE) {
873  }
874 
875  //Create Function
876  $getEntityFunctionCall = "\t\tPersistedObjectHandler::getInstance()->getEntity(";
877  $getEntityFunctionCall .= "\n\t\t\t\"{$stepKey}\",";
878  $getEntityFunctionCall .= "\n\t\t\t\"{$scope}\",";
879  $getEntityFunctionCall .= "\n\t\t\t\"{$entity}\"";
880  $getEntityFunctionCall .= ",\n\t\t\t[{$requiredEntityKeysArray}]";
881  if ($storeCode !== null) {
882  $getEntityFunctionCall .= ",\n\t\t\t\"{$storeCode}\"";
883  } else {
884  $getEntityFunctionCall .= ",\n\t\t\tnull";
885  }
886  if ($index !== null) {
887  $getEntityFunctionCall .= ",\n\t\t\t{$index}";
888  }
889  $getEntityFunctionCall .= "\n\t\t);\n";
890  $testSteps .= $getEntityFunctionCall;
891 
892  break;
893  case "assertArrayIsSorted":
894  $testSteps .= $this->wrapFunctionCall(
895  $actor,
896  $actionObject,
897  $parameterArray,
898  $this->wrapWithDoubleQuotes($sortOrder)
899  );
900  break;
901  case "seeCurrentUrlEquals":
902  case "seeCurrentUrlMatches":
903  case "dontSeeCurrentUrlEquals":
904  case "dontSeeCurrentUrlMatches":
905  case "seeInPopup":
906  case "saveSessionSnapshot":
907  case "seeInTitle":
908  case "seeInCurrentUrl":
909  case "switchToIFrame":
910  case "switchToWindow":
911  case "typeInPopup":
912  case "dontSee":
913  case "see":
914  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $input, $selector);
915  break;
916  case "switchToNextTab":
917  case "switchToPreviousTab":
918  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $input);
919  break;
920  case "clickWithLeftButton":
921  case "clickWithRightButton":
922  case "moveMouseOver":
923  case "scrollTo":
924  if (!$selector) {
925  $selector = 'null';
926  }
927  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $x, $y);
928  break;
929  case "dontSeeCookie":
930  case "resetCookie":
931  case "seeCookie":
932  $testSteps .= $this->wrapFunctionCall(
933  $actor,
934  $actionObject,
935  $input,
936  $parameterArray
937  );
938  break;
939  case "grabCookie":
940  $testSteps .= $this->wrapFunctionCallWithReturnValue(
941  $stepKey,
942  $actor,
943  $actionObject,
944  $input,
945  $parameterArray
946  );
947  break;
948  case "dontSeeElement":
949  case "dontSeeElementInDOM":
950  case "dontSeeInFormFields":
951  case "seeElement":
952  case "seeElementInDOM":
953  case "seeInFormFields":
954  $testSteps .= $this->wrapFunctionCall(
955  $actor,
956  $actionObject,
957  $selector,
958  $parameterArray
959  );
960  break;
961  case "pressKey":
962  $parameterArray = $customActionAttributes['parameterArray'] ?? null;
963  if ($parameterArray) {
964  $parameterArray = $this->processPressKey($parameterArray);
965  }
966  $testSteps .= $this->wrapFunctionCall(
967  $actor,
968  $actionObject,
969  $selector,
970  $input,
971  $parameterArray
972  );
973  break;
974  case "selectOption":
975  case "unselectOption":
976  $testSteps .= $this->wrapFunctionCall(
977  $actor,
978  $actionObject,
979  $selector,
980  $input,
981  $parameterArray
982  );
983  break;
984  case "submitForm":
985  $testSteps .= $this->wrapFunctionCall(
986  $actor,
987  $actionObject,
988  $selector,
989  $parameterArray,
990  $button
991  );
992  break;
993  case "dragAndDrop":
994  $testSteps .= $this->wrapFunctionCall(
995  $actor,
996  $actionObject,
997  $selector1,
998  $selector2,
999  $x,
1000  $y
1001  );
1002  break;
1003  case "selectMultipleOptions":
1004  $testSteps .= $this->wrapFunctionCall(
1005  $actor,
1006  $actionObject,
1007  $selector1,
1008  $selector2,
1009  $input,
1010  $parameterArray
1011  );
1012  break;
1013  case "executeInSelenium":
1014  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $function);
1015  break;
1016  case "executeJS":
1017  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1018  $stepKey,
1019  $actor,
1020  $actionObject,
1021  $function
1022  );
1023  break;
1024  case "performOn":
1025  case "waitForElementChange":
1026  $testSteps .= $this->wrapFunctionCall(
1027  $actor,
1028  $actionObject,
1029  $selector,
1030  $function,
1031  $time
1032  );
1033  break;
1034  case "waitForJS":
1035  $testSteps .= $this->wrapFunctionCall(
1036  $actor,
1037  $actionObject,
1038  $function,
1039  $time
1040  );
1041  break;
1042  case "wait":
1043  case "waitForAjaxLoad":
1044  case "waitForElement":
1045  case "waitForElementVisible":
1046  case "waitForElementNotVisible":
1047  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $time);
1048  break;
1049  case "waitForPageLoad":
1050  case "waitForText":
1051  $testSteps .= $this->wrapFunctionCall(
1052  $actor,
1053  $actionObject,
1054  $input,
1055  $time,
1056  $selector
1057  );
1058  break;
1059  case "formatMoney":
1060  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1061  $stepKey,
1062  $actor,
1063  $actionObject,
1064  $input,
1065  $locale
1066  );
1067  break;
1068  case "mSetLocale":
1069  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $input, $locale);
1070  break;
1071  case "grabAttributeFrom":
1072  case "grabMultiple":
1073  case "grabFromCurrentUrl":
1074  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1075  $stepKey,
1076  $actor,
1077  $actionObject,
1078  $selector,
1079  $input
1080  );
1081  break;
1082  case "grabTextFrom":
1083  case "grabValueFrom":
1084  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1085  $stepKey,
1086  $actor,
1087  $actionObject,
1088  $selector
1089  );
1090  break;
1091  case "grabPageSource":
1092  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1093  $stepKey,
1094  $actor,
1095  $actionObject
1096  );
1097  break;
1098  case "resizeWindow":
1099  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $width, $height);
1100  break;
1101  case "searchAndMultiSelectOption":
1102  $testSteps .= $this->wrapFunctionCall(
1103  $actor,
1104  $actionObject,
1105  $selector,
1106  $input,
1107  $parameterArray,
1108  $requiredAction
1109  );
1110  break;
1111  case "seeLink":
1112  case "dontSeeLink":
1113  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $input, $url);
1114  break;
1115  case "setCookie":
1116  $testSteps .= $this->wrapFunctionCall(
1117  $actor,
1118  $actionObject,
1119  $selector,
1120  $input,
1121  $value,
1122  $parameterArray
1123  );
1124  break;
1125  case "amOnPage":
1126  case "amOnSubdomain":
1127  case "amOnUrl":
1128  case "appendField":
1129  case "attachFile":
1130  case "click":
1131  case "dontSeeInField":
1132  case "dontSeeInCurrentUrl":
1133  case "dontSeeInTitle":
1134  case "dontSeeInPageSource":
1135  case "dontSeeOptionIsSelected":
1136  case "fillField":
1137  case "loadSessionSnapshot":
1138  case "seeInField":
1139  case "seeOptionIsSelected":
1140  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $selector, $input);
1141  break;
1142  case "seeNumberOfElements":
1143  $testSteps .= $this->wrapFunctionCall(
1144  $actor,
1145  $actionObject,
1146  $selector,
1147  $input,
1148  $parameterArray
1149  );
1150  break;
1151  case "seeInPageSource":
1152  case "seeInSource":
1153  case "dontSeeInSource":
1154  // TODO: Need to fix xml parser to allow parsing html.
1155  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $html);
1156  break;
1157  case "conditionalClick":
1158  $testSteps .= $this->wrapFunctionCall(
1159  $actor,
1160  $actionObject,
1161  $selector,
1162  $dependentSelector,
1163  $visible
1164  );
1165  break;
1166  case "assertEquals":
1167  case "assertGreaterOrEquals":
1168  case "assertGreaterThan":
1169  case "assertGreaterThanOrEqual":
1170  case "assertInternalType":
1171  case "assertLessOrEquals":
1172  case "assertLessThan":
1173  case "assertLessThanOrEqual":
1174  case "assertNotEquals":
1175  case "assertInstanceOf":
1176  case "assertNotInstanceOf":
1177  case "assertNotRegExp":
1178  case "assertNotSame":
1179  case "assertRegExp":
1180  case "assertSame":
1181  case "assertStringStartsNotWith":
1182  case "assertStringStartsWith":
1183  case "assertArrayHasKey":
1184  case "assertArrayNotHasKey":
1185  case "assertCount":
1186  case "assertContains":
1187  case "assertNotContains":
1188  case "expectException":
1189  $testSteps .= $this->wrapFunctionCall(
1190  $actor,
1191  $actionObject,
1192  $assertExpected,
1193  $assertActual,
1194  $assertMessage,
1195  $assertDelta
1196  );
1197  break;
1198  case "assertElementContainsAttribute":
1199  // If a blank string or null is passed in we need to pass a blank string to the function.
1200  if (empty($assertExpected)) {
1201  $assertExpected = '""';
1202  }
1203 
1204  $testSteps .= $this->wrapFunctionCall(
1205  $actor,
1206  $actionObject,
1207  $selector,
1208  $this->wrapWithDoubleQuotes($attribute),
1209  $assertExpected
1210  );
1211  break;
1212  case "assertEmpty":
1213  case "assertFalse":
1214  case "assertFileExists":
1215  case "assertFileNotExists":
1216  case "assertIsEmpty":
1217  case "assertNotEmpty":
1218  case "assertNotNull":
1219  case "assertNull":
1220  case "assertTrue":
1221  $testSteps .= $this->wrapFunctionCall(
1222  $actor,
1223  $actionObject,
1224  $assertActual,
1225  $assertMessage
1226  );
1227  break;
1228  case "assertArraySubset":
1229  $testSteps .= $this->wrapFunctionCall(
1230  $actor,
1231  $actionObject,
1232  $assertExpected,
1233  $assertActual,
1234  $assertIsStrict,
1235  $assertMessage
1236  );
1237  break;
1238  case "fail":
1239  $testSteps .= $this->wrapFunctionCall(
1240  $actor,
1241  $actionObject,
1242  $assertMessage
1243  );
1244  break;
1245  case "magentoCLI":
1246  $testSteps .= $this->wrapFunctionCallWithReturnValue(
1247  $stepKey,
1248  $actor,
1249  $actionObject,
1250  $command,
1251  $arguments
1252  );
1253  $testSteps .= sprintf(
1254  "\t\t$%s->comment(\$%s);\n",
1255  $actor,
1256  $stepKey
1257  );
1258  break;
1259  case "field":
1260  $fieldKey = $actionObject->getCustomActionAttributes()['key'];
1261  $input = $this->resolveTestVariable(
1262  [$input],
1263  $actionObject->getActionOrigin()
1264  )[0];
1265  $argRef = "\t\t\$";
1266  $argRef .= str_replace(ucfirst($fieldKey), "", $stepKey) . "Fields['{$fieldKey}'] = ${input};\n";
1267  $testSteps .= $argRef;
1268  break;
1269  case "generateDate":
1270  $timezone = getenv("DEFAULT_TIMEZONE");
1271  if (isset($customActionAttributes['timezone'])) {
1272  $timezone = $customActionAttributes['timezone'];
1273  }
1274 
1275  $dateGenerateCode = "\t\t\$date = new \DateTime();\n";
1276  $dateGenerateCode .= "\t\t\$date->setTimestamp(strtotime({$input}));\n";
1277  $dateGenerateCode .= "\t\t\$date->setTimezone(new \DateTimeZone(\"{$timezone}\"));\n";
1278  $dateGenerateCode .= "\t\t\${$stepKey} = \$date->format({$format});\n";
1279 
1280  $testSteps .= $dateGenerateCode;
1281  break;
1282  case "skipReadinessCheck":
1283  $testSteps .= $this->wrapFunctionCall($actor, $actionObject, $customActionAttributes['state']);
1284  break;
1285  default:
1286  $testSteps .= $this->wrapFunctionCall(
1287  $actor,
1288  $actionObject,
1289  $selector,
1290  $input,
1291  $parameter
1292  );
1293  }
1294  }
1295 
1296  return $testSteps;
1297  }
1298 
1305  private function resolveLocatorFunctionInAttribute($attribute)
1306  {
1307  if (strpos($attribute, "Locator::") !== false) {
1308  $attribute = $this->stripWrappedQuotes($attribute);
1309  $attribute = $this->wrapFunctionArgsWithQuotes("/Locator::[\w]+\(([\s\S]+)\)/", $attribute);
1310  }
1311  return $attribute;
1312  }
1313 
1323  private function resolveTestVariable($args, $actionOrigin)
1324  {
1325  $newArgs = [];
1326  foreach ($args as $key => $arg) {
1327  if ($arg === null) {
1328  continue;
1329  }
1330  $outputArg = $arg;
1331  // Math on $data.key$ and $$data.key$$
1332  preg_match_all('/\${1,2}[\w.\[\]]+\${1,2}/', $outputArg, $matches);
1333  $this->replaceMatchesIntoArg($matches[0], $outputArg);
1334 
1335  //trim "{$variable}" into $variable
1336  $outputArg = $this->trimVariableIfNeeded($outputArg);
1337 
1338  $outputArg = $this->resolveStepKeyReferences($outputArg, $actionOrigin);
1339 
1340  $newArgs[$key] = $outputArg;
1341  }
1342 
1343  return $newArgs;
1344  }
1345 
1352  private function trimVariableIfNeeded($input)
1353  {
1354  preg_match('/"{\$[a-z][a-zA-Z\d]+}"/', $input, $match);
1355  if (isset($match[0])) {
1356  return trim($input, '{}"');
1357  } else {
1358  return $input;
1359  }
1360  }
1361 
1370  private function replaceMatchesIntoArg($matches, &$outputArg)
1371  {
1372  // Remove Duplicate $matches from array. Duplicate matches are replaced all in one go.
1373  $matches = array_unique($matches);
1374  foreach ($matches as $match) {
1375  $replacement = null;
1376  $delimiter = '$';
1377  $variable = $this->stripAndSplitReference($match, $delimiter);
1378  if (count($variable) != 2) {
1379  throw new \Exception(
1380  "Invalid Persisted Entity Reference: {$match}.
1381  Test persisted entity references must follow {$delimiter}entityStepKey.field{$delimiter} format."
1382  );
1383  }
1384 
1385  $replacement = "PersistedObjectHandler::getInstance()->retrieveEntityField";
1386  $replacement .= "('{$variable[0]}', '$variable[1]', '{$this->currentGenerationScope}')";
1387 
1388  //Determine if quoteBreak check is necessary. Assume replacement is surrounded in quotes, then override
1389  if (strpos($outputArg, "\"") !== false) {
1390  $outputArg = $this->processQuoteBreaks($match, $outputArg, $replacement);
1391  } else {
1392  $outputArg = str_replace($match, $replacement, $outputArg);
1393  }
1394  }
1395  }
1396 
1406  private function processQuoteBreaks($match, $argument, $replacement)
1407  {
1408  $outputArg = str_replace($match, '" . ' . $replacement . ' . "', $argument);
1409 
1410  //Sanitize string of any unnecessary '"" .' and '. ""'.
1411  //Regex means: Search for '"" . ' but not '\"" . ' and ' . ""'.
1412  //Matches on '"" . ' and ' . ""', but not on '\"" . ' and ' . "\"'.
1413  $outputArg = preg_replace('/(?(?<![\\\\])"" \. )| \. ""/', "", $outputArg);
1414  return $outputArg;
1415  }
1416 
1424  private function resolveStepKeyReferences($input, $actionGroupOrigin, $matchAll = false)
1425  {
1426  if ($actionGroupOrigin == null) {
1427  return $input;
1428  }
1429  $output = $input;
1430 
1431  $actionGroup = ActionGroupObjectHandler::getInstance()->getObject(
1433  );
1434  $stepKeys = $actionGroup->extractStepKeys();
1435  $testInvocationKey = ucfirst($actionGroupOrigin[ActionGroupObject::ACTION_GROUP_ORIGIN_TEST_REF]);
1436 
1437  foreach ($stepKeys as $stepKey) {
1438  // MQE-1011
1439  $stepKeyVarRef = "$" . $stepKey;
1440  $persistedVarRef = "PersistedObjectHandler::getInstance()->retrieveEntityField('{$stepKey}'"
1441  . ", 'field', 'test')";
1442  $persistedVarRefInvoked = "PersistedObjectHandler::getInstance()->retrieveEntityField('"
1443  . $stepKey . $testInvocationKey . "', 'field', 'test')";
1444 
1445  if (strpos($output, $stepKeyVarRef) !== false) {
1446  $output = str_replace($stepKeyVarRef, $stepKeyVarRef . $testInvocationKey, $output);
1447  }
1448 
1449  if (strpos($output, $persistedVarRef) !== false) {
1450  $output = str_replace($persistedVarRef, $persistedVarRefInvoked, $output);
1451  }
1452 
1453  if ($matchAll && strpos($output, $stepKey) !== false) {
1454  $output = str_replace($stepKey, $stepKey . $testInvocationKey, $output);
1455  }
1456  }
1457  return $output;
1458  }
1459 
1467  private function wrapFunctionArgsWithQuotes($functionRegex, $input)
1468  {
1469  $output = $input;
1470  preg_match_all($functionRegex, $input, $matches);
1471 
1472  //If no Arguments were passed in
1473  if (!isset($matches[1][0])) {
1474  return $input;
1475  }
1476 
1477  $allArguments = explode(',', $matches[1][0]);
1478  foreach ($allArguments as $argument) {
1479  $argument = trim($argument);
1480 
1481  if ($argument[0] == "[") {
1482  $replacement = "[" . $this->addUniquenessToParamArray($argument) . "]";
1483  } elseif (is_numeric($argument)) {
1484  $replacement = $argument;
1485  } else {
1486  $replacement = $this->addUniquenessFunctionCall($argument);
1487  }
1488 
1489  //Replace only first occurrence of argument with "argument"
1490  $pos = strpos($output, $argument);
1491  $output = substr_replace($output, $replacement, $pos, strlen($argument));
1492  }
1493 
1494  return $output;
1495  }
1496 
1504  private function stripAndSplitReference($reference, $delimiter)
1505  {
1506  $strippedReference = str_replace($delimiter, '', $reference);
1507  return explode('.', $strippedReference);
1508  }
1509 
1519  private function generateHooksPhp($hookObjects)
1520  {
1521  $hooks = "";
1522 
1523  foreach ($hookObjects as $hookObject) {
1524  $type = $hookObject->getType();
1525  $dependencies = 'AcceptanceTester $I';
1526 
1527  $hooks .= "\t/**\n";
1528  $hooks .= "\t * @param AcceptanceTester \$I\n";
1529  $hooks .= "\t * @throws \Exception\n";
1530  $hooks .= "\t */\n";
1531 
1532  try {
1533  $steps = $this->generateStepsPhp(
1534  $hookObject->getActions(),
1536  );
1537  } catch (TestReferenceException $e) {
1538  throw new TestReferenceException($e->getMessage() . " in Element \"" . $type . "\"");
1539  }
1540 
1541  $hooks .= sprintf("\tpublic function _{$type}(%s)\n", $dependencies);
1542  $hooks .= "\t{\n";
1543  $hooks .= $steps;
1544  $hooks .= "\t}\n\n";
1545  }
1546 
1547  return $hooks;
1548  }
1549 
1559  private function generateTestPhp($test)
1560  {
1561  $testPhp = "";
1562 
1563  $testName = $test->getName();
1564  $testName = str_replace(' ', '', $testName);
1565  $testAnnotations = $this->generateAnnotationsPhp($test->getAnnotations(), true);
1566  $dependencies = 'AcceptanceTester $I';
1567  if ($test->isSkipped()) {
1568  $skipString = "This test is skipped due to the following issues:\\n";
1569  $issues = $test->getAnnotations()['skip'] ?? null;
1570  if (isset($issues)) {
1571  $skipString .= implode("\\n", $issues);
1572  } else {
1573  $skipString .= "No issues have been specified.";
1574  }
1575  $steps = "\t\t" . '$scenario->skip("' . $skipString . '");' . "\n";
1576  $dependencies .= ', \Codeception\Scenario $scenario';
1577  } else {
1578  try {
1579  $steps = $this->generateStepsPhp($test->getOrderedActions());
1580  } catch (\Exception $e) {
1581  throw new TestReferenceException($e->getMessage() . " in Test \"" . $test->getName() . "\"");
1582  }
1583  }
1584 
1585  $testPhp .= $testAnnotations;
1586  $testPhp .= sprintf("\tpublic function %s(%s)\n", $testName, $dependencies);
1587  $testPhp .= "\t{\n";
1588  $testPhp .= $steps;
1589  $testPhp .= "\t}\n";
1590 
1591  return $testPhp;
1592  }
1593 
1600  private function addUniquenessToParamArray($input)
1601  {
1602  $tempInput = trim($input, "[]");
1603  $paramArray = explode(",", $tempInput);
1604  $result = [];
1605 
1606  foreach ($paramArray as $param) {
1607  // Determine if param has key/value array notation
1608  if (preg_match_all('/(.+)=>(.+)/', trim($param), $paramMatches)) {
1609  $param1 = $this->addUniquenessToParamArray($paramMatches[1][0]);
1610  $param2 = $this->addUniquenessToParamArray($paramMatches[2][0]);
1611  $result[] = trim($param1) . " => " . trim($param2);
1612  continue;
1613  }
1614 
1615  // Matches strings wrapped in ', we assume these are string literals
1616  if (preg_match('/^(["\']).*\1$/m', trim($param))) {
1617  $result[] = $param;
1618  continue;
1619  }
1620 
1621  $replacement = $this->addUniquenessFunctionCall(trim($param));
1622 
1623  $result[] = $replacement;
1624  }
1625 
1626  return implode(", ", $result);
1627  }
1628 
1635  private function processPressKey($input)
1636  {
1637  // validate the param array is in the correct format
1638  $input = trim($input);
1639  $this->validateParameterArray($input);
1640  // trim off the outer braces
1641  $input = substr($input, 1, strlen($input) - 2);
1642 
1643  $result = [];
1644  $arrayResult = [];
1645  $count = 0;
1646 
1647  // matches all arrays and replaces them with placeholder to prevent later param manipulation
1648  preg_match_all('/[\[][^\]]*?[\]]/', $input, $paramInput);
1649  if (!empty($paramInput)) {
1650  foreach ($paramInput[0] as $param) {
1651  $arrayResult[self::PRESSKEY_ARRAY_ANCHOR_KEY . $count] =
1652  '[' . trim($this->addUniquenessToParamArray($param)) . ']';
1653  $input = str_replace($param, self::PRESSKEY_ARRAY_ANCHOR_KEY . $count, $input);
1654  $count++;
1655  }
1656  }
1657 
1658  $paramArray = explode(",", $input);
1659  foreach ($paramArray as $param) {
1660  // matches strings wrapped in ', we assume these are string literals
1661  if (preg_match('/^[\s]*(\'.*?\')[\s]*$/', $param)) {
1662  $result[] = trim($param);
1663  continue;
1664  }
1665 
1666  // matches \ for Facebook WebDriverKeys classes
1667  if (substr(trim($param), 0, 1) === '\\') {
1668  $result[] = trim($param);
1669  continue;
1670  }
1671 
1672  // matches numbers
1673  if (preg_match('/^[\s]*(\d+?)[\s]*$/', $param)) {
1674  $result[] = $param;
1675  continue;
1676  }
1677 
1678  $replacement = $this->addUniquenessFunctionCall(trim($param));
1679 
1680  $result[] = $replacement;
1681  }
1682 
1683  $result = implode(',', $result);
1684  // reinsert arrays into result
1685  if (!empty($arrayResult)) {
1686  foreach ($arrayResult as $key => $value) {
1687  $result = str_replace($key, $value, $result);
1688  }
1689  }
1690  return $result;
1691  }
1692 
1699  private function addUniquenessFunctionCall($input)
1700  {
1701  $output = $this->wrapWithDoubleQuotes($input);
1702 
1703  //Match on msq(\"entityName\")
1704  preg_match_all('/' . EntityDataObject::CEST_UNIQUE_FUNCTION . '\(\\\\"[\w]+\\\\"\)/', $output, $matches);
1705  foreach (array_unique($matches[0]) as $match) {
1706  preg_match('/\\\\"([\w]+)\\\\"/', $match, $entityMatch);
1707  $entity = $entityMatch[1];
1708  $output = str_replace($match, '" . msq("' . $entity . '") . "', $output);
1709  }
1710  // trim unnecessary "" . and . ""
1711  return preg_replace('/(?(?<![\\\\])"" \. )| \. ""/', "", $output);
1712  }
1713 
1720  private function wrapWithDoubleQuotes($input)
1721  {
1722  if ($input == null) {
1723  return '';
1724  }
1725  //Only replace &quot; with \" so that it doesn't break outer string.
1726  $input = str_replace('"', '\"', $input);
1727  return sprintf('"%s"', $input);
1728  }
1729 
1736  private function stripWrappedQuotes($input)
1737  {
1738  if (empty($input)) {
1739  return '';
1740  }
1741  if (substr($input, 0, 1) === '"') {
1742  $input = substr($input, 1);
1743  }
1744  if (substr($input, -1, 1) === '"') {
1745  $input = substr($input, 0, -1);
1746  }
1747  return $input;
1748  }
1749 
1756  private function addDollarSign($input)
1757  {
1758  return sprintf("$%s", ltrim($this->stripQuotes($input), '$'));
1759  }
1760 
1761  // @codingStandardsIgnoreStart
1762 
1773  private function wrapFunctionCall($actor, $action, ...$args)
1774  {
1775  $isFirst = true;
1776  $output = sprintf("\t\t$%s->%s(", $actor, $action->getType());
1777  for ($i = 0; $i < count($args); $i++) {
1778  if (null === $args[$i]) {
1779  continue;
1780  }
1781  if ($args[$i] === "") {
1782  $args[$i] = '"' . $args[$i] . '"';
1783  }
1784  }
1785  if (!is_array($args)) {
1786  $args = [$args];
1787  }
1788  $args = $this->resolveAllRuntimeReferences($args);
1789  $args = $this->resolveTestVariable($args, $action->getActionOrigin());
1790  $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n";
1791  return $output;
1792  }
1793 
1805  private function wrapFunctionCallWithReturnValue($returnVariable, $actor, $action, ...$args)
1806  {
1807  $isFirst = true;
1808  $output = sprintf("\t\t$%s = $%s->%s(", $returnVariable, $actor, $action->getType());
1809  for ($i = 0; $i < count($args); $i++) {
1810  if (null === $args[$i]) {
1811  continue;
1812  }
1813  if ($args[$i] === "") {
1814  $args[$i] = '"' . $args[$i] . '"';
1815  }
1816  }
1817  if (!is_array($args)) {
1818  $args = [$args];
1819  }
1820  $args = $this->resolveAllRuntimeReferences($args);
1821  $args = $this->resolveTestVariable($args, $action->getActionOrigin());
1822  $output .= implode(", ", array_filter($args, function($value) { return $value !== null; })) . ");\n";
1823  return $output;
1824  }
1825  // @codingStandardsIgnoreEnd
1826 
1834  private function resolveRuntimeReference($args, $regex, $func)
1835  {
1836  $newArgs = [];
1837 
1838  foreach ($args as $key => $arg) {
1839  preg_match_all($regex, $arg, $matches);
1840  if (!empty($matches[0])) {
1841  $fullMatch = $matches[0][0];
1842  $refVariable = $matches[1][0];
1843  unset($matches);
1844  $replacement = "{$func}(\"{$refVariable}\")";
1845 
1846  $outputArg = $this->processQuoteBreaks($fullMatch, $arg, $replacement);
1847  $newArgs[$key] = $outputArg;
1848  continue;
1849  }
1850  $newArgs[$key] = $arg;
1851  }
1852 
1853  // override passed in args for use later.
1854  return $newArgs;
1855  }
1856 
1864  private function resolveAllRuntimeReferences($args)
1865  {
1866  $runtimeReferenceRegex = [
1867  "/{{_ENV\.([\w]+)}}/" => 'getenv',
1868  "/{{_CREDS\.([\w]+)}}/" => 'CredentialStore::getInstance()->getSecret'
1869  ];
1870 
1871  $argResult = $args;
1872  foreach ($runtimeReferenceRegex as $regex => $func) {
1873  $argResult = $this->resolveRuntimeReference($argResult, $regex, $func);
1874  }
1875 
1876  return $argResult;
1877  }
1878 
1886  private function validateParameterArray($paramArray)
1887  {
1888  if (substr($paramArray, 0, 1) != "[" || substr($paramArray, strlen($paramArray) - 1, 1) != "]") {
1889  throw new TestReferenceException("parameterArray must begin with `[` and end with `]");
1890  }
1891  }
1892 
1902  private function resolveValueByType($value, $type)
1903  {
1904  //TODO: Refactor to deal with PHPMD.CyclomaticComplexity, and remove @SuppressWarnings
1905  if (null === $value) {
1906  return null;
1907  }
1908  if (null === $type) {
1909  $type = 'const';
1910  }
1911  if ($type == "string") {
1912  return $this->addUniquenessFunctionCall($value);
1913  } elseif ($type == "bool") {
1914  return $this->toBoolean($value) ? "true" : "false";
1915  } elseif ($type == "int" || $type == "float") {
1916  return $this->toNumber($value);
1917  } elseif ($type == "array") {
1918  $this->validateParameterArray($value);
1919  return "[" . $this->addUniquenessToParamArray($value) . "]";
1920  } elseif ($type == "variable") {
1921  return $this->addDollarSign($value);
1922  } else {
1923  return $value;
1924  }
1925  }
1926 
1933  private function toBoolean($inStr)
1934  {
1935  return boolval($this->stripQuotes($inStr));
1936  }
1937 
1944  private function toNumber($inStr)
1945  {
1946  $outStr = $this->stripQuotes($inStr);
1947  if (strpos($outStr, localeconv()['decimal_point']) === false) {
1948  return intval($outStr);
1949  } else {
1950  return floatval($outStr);
1951  }
1952  }
1953 
1960  private function stripQuotes($inStr)
1961  {
1962  $unquoted = preg_replace('/^(\'(.*)\'|"(.*)")$/', '$2$3', $inStr);
1963  return $unquoted;
1964  }
1965 
1974  private function validateXmlAttributesMutuallyExclusive($key, $tagName, $attributes)
1975  {
1976  $rules = [
1977  [
1978  'attributes' => [
1979  'selector',
1980  'selectorArray',
1981  ]
1982  ],
1983  [
1984  'attributes' => [
1985  'url',
1986  'userInput',
1987  'variable',
1988  ],
1989  'excludes' => [
1990  'dontSeeLink',
1991  'seeLink',
1992  ],
1993  ],
1994  [
1995  'attributes' => [
1996  'userInput',
1997  'parameterArray',
1998  'variable'
1999  ],
2000  'excludes' => [
2001  'dontSeeCookie',
2002  'grabCookie',
2003  'resetCookie',
2004  'seeCookie',
2005  'setCookie',
2006  ],
2007  ],
2008  ];
2009  foreach ($rules as $rule) {
2010  if (isset($rule['excludes']) && in_array($tagName, $rule['excludes'])) {
2011  continue;
2012  }
2013  $count = 0;
2014  foreach ($rule['attributes'] as $attribute) {
2015  if (isset($attributes[$attribute])) {
2016  $count++;
2017  }
2018  }
2019  if ($count > 1) {
2020  $this->printRuleErrorToConsole($key, $tagName, $rule['attributes']);
2021  }
2022  }
2023  }
2024 
2033  private function printRuleErrorToConsole($key, $tagName, $attributes)
2034  {
2035  if (empty($tagName) || empty($attributes)) {
2036  return;
2037  }
2038  $message = 'On step with stepKey "' . $key . '", only one of the attributes: "';
2039  $message .= implode('", "', $attributes);
2040  $message .= '" can be use for action "' . $tagName . "\".\n";
2041  print $message;
2042  }
2043 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$count
Definition: recent.phtml:13
$group
Definition: sections.phtml:16
$message
$replacement
Definition: website.php:23
static getInstance($dir=null, $tests=[], $debug=false)
$storeCode
Definition: indexer.php:15
$variable
Definition: variable.php:7
$type
Definition: item.phtml:13
generateStepsPhp($actionObjects, $generationScope=TestGenerator::TEST_SCOPE, $actor="I")
$value
Definition: gender.phtml:16
$format
Definition: list.phtml:12
$pos
Definition: list.phtml:42
$entity
Definition: element.phtml:22
$attributes
Definition: matrix.phtml:13
$arguments
createAllTestFiles($testManifest=null, $testsToIgnore=null)
$i
Definition: gallery.phtml:31
$index
Definition: list.phtml:44
if($currentSelectedMethod==$_code) $className
Definition: form.phtml:31
$isFirst
Definition: tax.phtml:36
if(!isset($_GET['name'])) $name
Definition: log.php:14