Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
All Data Structures Namespaces Files Functions Variables Pages
ActionObject.php
Go to the documentation of this file.
1 <?php
7 
19 
25 {
26  const __ENV = "_ENV";
27  const __CREDS = "_CREDS";
31  ];
32 
34  "userInput",
35  "parameterArray",
36  "expected",
37  "actual",
38  "x",
39  "y",
40  "expectedResult",
41  "actualResult",
42  "command",
43  "regex",
44  "date",
45  "format"
46  ];
48  'selector',
49  'dependentSelector',
50  "selector1",
51  "selector2",
52  "function",
53  'filterSelector',
54  'optionSelector',
55  "command"
56  ];
57  const OLD_ASSERTION_ATTRIBUTES = ["expected", "expectedType", "actual", "actualType"];
58  const ASSERTION_ATTRIBUTES = ["expectedResult" => "expected", "actualResult" => "actual"];
59  const ASSERTION_TYPE_ATTRIBUTE = "type";
60  const ASSERTION_VALUE_ATTRIBUTE = "value";
61  const DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES = ["url", "createDataKey"];
62  const EXTERNAL_URL_AREA_INVALID_ACTIONS = ['amOnPage'];
63  const FUNCTION_CLOSURE_ACTIONS = ['waitForElementChange', 'performOn'];
64  const MERGE_ACTION_ORDER_AFTER = 'after';
65  const MERGE_ACTION_ORDER_BEFORE = 'before';
66  const ACTION_ATTRIBUTE_TIMEZONE = 'timezone';
67  const ACTION_ATTRIBUTE_URL = 'url';
68  const ACTION_ATTRIBUTE_SELECTOR = 'selector';
70  const ACTION_ATTRIBUTE_VARIABLE_REGEX_PATTERN = '/({{[\w]+\.[\w\[\]]+}})|({{[\w]+\.[\w]+\((?(?!}}).)+\)}})/';
71 
77  private $stepKey;
78 
84  private $type;
85 
91  private $actionAttributes = [];
92 
98  private $linkedAction;
99 
105  private $orderOffset = 0;
106 
112  private $resolvedCustomAttributes = [];
113 
119  private $timeout;
120 
126  private $actionOrigin = [];
127 
138  public function __construct(
139  $stepKey,
140  $type,
141  $actionAttributes,
142  $linkedAction = null,
144  $actionOrigin = null
145  ) {
146  $this->stepKey = $stepKey;
147  $this->type = $type;
148  $this->actionAttributes = $actionAttributes;
149  $this->linkedAction = $linkedAction;
150  $this->actionOrigin = $actionOrigin;
151 
153  $this->orderOffset = 1;
154  }
155  }
156 
162  public function getStepKey()
163  {
164  return $this->stepKey;
165  }
166 
172  public function getType()
173  {
174  return $this->type;
175  }
176 
182  public function getActionOrigin()
183  {
184  return $this->actionOrigin;
185  }
186 
197  public function getCustomActionAttributes()
198  {
199  return array_merge($this->actionAttributes, $this->resolvedCustomAttributes);
200  }
201 
207  public function getLinkedAction()
208  {
209  return $this->linkedAction;
210  }
211 
217  public function getOrderOffset()
218  {
219  return $this->orderOffset;
220  }
221 
228  public function getTimeout()
229  {
230  return $this->timeout;
231  }
232 
239  public function setTimeout($timeout)
240  {
241  $this->timeout = $timeout;
242  }
243 
254  public function resolveReferences()
255  {
256  if (empty($this->resolvedCustomAttributes)) {
257  $this->trimAssertionAttributes();
258  $this->resolveSelectorReferenceAndTimeout();
259  $this->resolveUrlReference();
260  $this->resolveDataInputReferences();
261  $this->validateTimezoneAttribute();
262  if ($this->getType() == "deleteData") {
263  $this->validateMutuallyExclusiveAttributes(self::DELETE_DATA_MUTUAL_EXCLUSIVE_ATTRIBUTES);
264  }
265  }
266  }
267 
276  public function trimAssertionAttributes()
277  {
278  $actionAttributeKeys = array_keys($this->actionAttributes);
279 
284  $oldAttributes = array_intersect($actionAttributeKeys, ActionObject::OLD_ASSERTION_ATTRIBUTES);
285  if (!empty($oldAttributes)) {
286  $appConfig = MftfApplicationConfig::getConfig();
287  if ($appConfig->getPhase() == MftfApplicationConfig::GENERATION_PHASE && $appConfig->verboseEnabled()) {
288  LoggingUtil::getInstance()->getLogger(ActionObject::class)->deprecation(
289  "use of one line Assertion actions will be deprecated in MFTF 3.0.0, please use nested syntax",
290  ["action" => $this->type, "stepKey" => $this->stepKey]
291  );
292  }
293  return;
294  }
295 
296  $relevantKeys = array_keys(ActionObject::ASSERTION_ATTRIBUTES);
297  $relevantAssertionAttributes = array_intersect($actionAttributeKeys, $relevantKeys);
298 
299  if (empty($relevantAssertionAttributes)) {
300  return;
301  }
302 
303  $this->validateAssertionSchema($relevantAssertionAttributes);
304 
305  // Flatten nested Elements's type and value into key=>value entries
306  foreach ($this->actionAttributes as $key => $subAttributes) {
307  if (in_array($key, $relevantKeys)) {
309  $this->actionAttributes[$prefix . ucfirst(ActionObject::ASSERTION_TYPE_ATTRIBUTE)] =
311  $this->actionAttributes[$prefix] =
313  unset($this->actionAttributes[$key]);
314  }
315  }
316  }
317 
324  private function validateAssertionSchema($attributes)
325  {
329  $singleChildTypes = ['assertEmpty', 'assertFalse', 'assertFileExists', 'assertFileNotExists',
330  'assertIsEmpty', 'assertNotEmpty', 'assertNotNull', 'assertNull', 'assertTrue',
331  'assertElementContainsAttribute'];
332 
333  if (!in_array($this->type, $singleChildTypes)) {
334  if (!in_array('expectedResult', $attributes)
335  || !in_array('actualResult', $attributes)) {
336  throw new TestReferenceException(
337  "{$this->type} must have both an expectedResult & actualResult defined (stepKey: {$this->stepKey})",
338  ["action" => $this->type, "stepKey" => $this->stepKey]
339  );
340  }
341  }
342  }
343 
353  private function resolveSelectorReferenceAndTimeout()
354  {
355  $actionAttributeKeys = array_keys($this->actionAttributes);
356  $relevantSelectorAttributes = array_intersect($actionAttributeKeys, ActionObject::SELECTOR_ENABLED_ATTRIBUTES);
357 
358  if (empty($relevantSelectorAttributes)) {
359  return;
360  }
361 
362  foreach ($relevantSelectorAttributes as $selectorAttribute) {
363  $selector = $this->actionAttributes[$selectorAttribute];
364 
365  $replacement = $this->findAndReplaceReferences(SectionObjectHandler::getInstance(), $selector);
366  if ($replacement) {
367  $this->resolvedCustomAttributes[$selectorAttribute] = $replacement;
368  }
369  }
370  }
371 
381  private function resolveUrlReference()
382  {
383  if (!array_key_exists(ActionObject::ACTION_ATTRIBUTE_URL, $this->actionAttributes)) {
384  return;
385  }
386 
387  $url = $this->actionAttributes[ActionObject::ACTION_ATTRIBUTE_URL];
388 
389  $replacement = $this->findAndReplaceReferences(PageObjectHandler::getInstance(), $url);
390  if ($replacement) {
391  $this->resolvedCustomAttributes[ActionObject::ACTION_ATTRIBUTE_URL] = $replacement;
392  $allPages = PageObjectHandler::getInstance()->getAllObjects();
393  if ($replacement === $url && array_key_exists(trim($url, "{}"), $allPages)
394  ) {
395  LoggingUtil::getInstance()->getLogger(ActionObject::class)->warning(
396  "page url attribute not found and is required",
397  ["action" => $this->type, "url" => $url, "stepKey" => $this->stepKey]
398  );
399  }
400  }
401  }
402 
412  private function resolveDataInputReferences()
413  {
414  $actionAttributeKeys = array_keys($this->actionAttributes);
415  $relevantDataAttributes = array_intersect($actionAttributeKeys, ActionObject::DATA_ENABLED_ATTRIBUTES);
416 
417  if (empty($relevantDataAttributes)) {
418  return;
419  }
420 
421  foreach ($relevantDataAttributes as $dataAttribute) {
422  $varInput = $this->actionAttributes[$dataAttribute];
423  $replacement = $this->findAndReplaceReferences(DataObjectHandler::getInstance(), $varInput);
424  if ($replacement !== null) {
425  $this->resolvedCustomAttributes[$dataAttribute] = $replacement;
426  }
427  }
428  }
429 
437  private function stripAndSplitReference($reference)
438  {
439  $strippedReference = str_replace('}}', '', str_replace('{{', '', $reference));
440  $strippedReference = preg_replace(
442  '',
443  $strippedReference
444  );
445  return explode('.', $strippedReference);
446  }
447 
455  private function stripAndReturnParameters($reference)
456  {
457  // 'string', or 'string,!@#$%^&*()_+, '
458  $literalParametersRegex = "/'[^']+'/";
459  $postCleanupDelimiter = "::::";
460 
461  preg_match(ActionObject::ACTION_ATTRIBUTE_VARIABLE_REGEX_PARAMETER, $reference, $matches);
462  if (!empty($matches)) {
463  $strippedReference = ltrim(rtrim($matches[0], ")"), "(");
464 
465  // Pull out all 'string' references, as they can contain 'string with , comma in it'
466  preg_match_all($literalParametersRegex, $strippedReference, $literalReferences);
467  $strippedReference = preg_replace($literalParametersRegex, '&&stringReference&&', $strippedReference);
468 
469  // Sanitize 'string, data.field,$persisted.field$' => 'string::::data.field::::$persisted.field$'
470  $strippedReference = preg_replace('/,/', ', ', $strippedReference);
471  $strippedReference = str_replace(',', $postCleanupDelimiter, $strippedReference);
472  $strippedReference = str_replace(' ', '', $strippedReference);
473 
474  // Replace string references into string, needed to keep sequence
475  foreach ($literalReferences[0] as $key => $value) {
476  $strippedReference = preg_replace('/&&stringReference&&/', $value, $strippedReference, 1);
477  }
478 
479  return explode($postCleanupDelimiter, $strippedReference);
480  }
481  return null;
482  }
483 
495  private function findAndReplaceReferences($objectHandler, $inputString)
496  {
497  //look for parameter area, if so use different regex
499 
500  preg_match_all($regex, $inputString, $matches);
501 
502  $outputString = $inputString;
503 
504  foreach ($matches[0] as $match) {
505  $replacement = null;
506  $parameterized = false;
507  list($objName) = $this->stripAndSplitReference($match);
508 
509  $obj = $objectHandler->getObject($objName);
510 
511  // Leave runtime references to be replaced in TestGenerator with getter function accessing "VARIABLE"
512  if (in_array($objName, ActionObject::RUNTIME_REFERENCES)) {
513  continue;
514  }
515 
516  if ($obj == null) {
517  // keep initial values for subsequent logic
518  $replacement = null;
519  $parameterized = false;
520  } elseif (get_class($obj) == PageObject::class) {
521  $this->validateUrlAreaAgainstActionType($obj);
522  $replacement = $obj->getUrl();
523  $parameterized = $obj->isParameterized();
524  } elseif (get_class($obj) == SectionObject::class) {
525  list(,$objField) = $this->stripAndSplitReference($match);
526  if ($obj->getElement($objField) == null) {
527  throw new TestReferenceException(
528  "Could not resolve entity reference \"{$inputString}\" "
529  . "in Action with stepKey \"{$this->getStepKey()}\"",
530  ["input" => $inputString, "stepKey" => $this->getStepKey()]
531  );
532  }
533  $parameterized = $obj->getElement($objField)->isParameterized();
534  $replacement = $obj->getElement($objField)->getPrioritizedSelector();
535  $this->setTimeout($obj->getElement($objField)->getTimeout());
536  } elseif (get_class($obj) == EntityDataObject::class) {
537  $replacement = $this->resolveEntityDataObjectReference($obj, $match);
538 
539  if (is_array($replacement)) {
540  $replacement = '["' . implode('","', array_map('addSlashes', $replacement)) . '"]';
541  }
542  }
543 
544  if ($replacement === null) {
545  if (get_class($objectHandler) != DataObjectHandler::class) {
546  return $this->findAndReplaceReferences(DataObjectHandler::getInstance(), $outputString);
547  } else {
548  throw new TestReferenceException(
549  "Could not resolve entity reference \"{$inputString}\" "
550  . "in Action with stepKey \"{$this->getStepKey()}\"",
551  ["input" => $inputString, "stepKey" => $this->getStepKey()]
552  );
553  }
554  }
555 
556  $replacement = $this->resolveParameterization($parameterized, $replacement, $match, $obj);
557 
558  $outputString = str_replace($match, $replacement, $outputString);
559  }
560  return $outputString;
561  }
562 
569  private function validateMutuallyExclusiveAttributes(array $attributes)
570  {
571  $matches = array_intersect($attributes, array_keys($this->getCustomActionAttributes()));
572  if (count($matches) > 1) {
573  throw new TestReferenceException(
574  "Actions of type '{$this->getType()}' must only contain one attribute of types '"
575  . implode("', '", $attributes) . "'",
576  ["type" => $this->getType(), "attributes" => $attributes]
577  );
578  } elseif (count($matches) == 0) {
579  throw new TestReferenceException(
580  "Actions of type '{$this->getType()}' must contain at least one attribute of types '"
581  . implode("', '", $attributes) . "'",
582  ["type" => $this->getType(), "attributes" => $attributes]
583  );
584  }
585  }
586 
594  private function validateUrlAreaAgainstActionType($obj)
595  {
596  if ($obj->getArea() == 'external' &&
597  in_array($this->getType(), self::EXTERNAL_URL_AREA_INVALID_ACTIONS)) {
598  throw new TestReferenceException(
599  "Page of type 'external' is not compatible with action type '{$this->getType()}'",
600  ["type" => $this->getType()]
601  );
602  }
603  }
604 
611  private function validateTimezoneAttribute()
612  {
614  if (isset($attributes[self::ACTION_ATTRIBUTE_TIMEZONE])) {
616  try {
617  new \DateTimeZone($timezone);
618  } catch (\Exception $e) {
619  throw new TestReferenceException(
620  "Timezone '{$timezone}' is not a valid timezone",
621  ["stepKey" => $this->getStepKey(), self::ACTION_ATTRIBUTE_TIMEZONE => $timezone]
622  );
623  }
624  }
625  }
626 
633  private function resolveEntityDataObjectReference($obj, $match)
634  {
635  list(,$objField) = $this->stripAndSplitReference($match);
636 
637  if (strpos($objField, '[') == true) {
638  // Access <array>...</array>
639  $parts = explode('[', $objField);
640  $name = $parts[0];
641  $index = str_replace(']', '', $parts[1]);
642  return $obj->getDataByName($name, EntityDataObject::CEST_UNIQUE_NOTATION)[$index];
643  } else {
644  // Access <data></data>
645  return $obj->getDataByName($objField, EntityDataObject::CEST_UNIQUE_NOTATION);
646  }
647  }
648 
658  private function resolveParameterization($isParameterized, $replacement, $match, $object)
659  {
660  if ($isParameterized) {
661  $parameterList = $this->stripAndReturnParameters($match) ?: [];
662  $resolvedReplacement = $this->matchParameterReferences($replacement, $parameterList);
663  } else {
664  $resolvedReplacement = $replacement;
665  }
666  if (get_class($object) == PageObject::class && $object->getArea() == PageObject::ADMIN_AREA) {
667  $resolvedReplacement = "/{{_ENV.MAGENTO_BACKEND_NAME}}/" . $resolvedReplacement;
668  }
669  return $resolvedReplacement;
670  }
671 
681  private function matchParameterReferences($reference, $parameters)
682  {
683  preg_match_all('/{{[\w.]+}}/', $reference, $varMatches);
684  $varMatches[0] = array_unique($varMatches[0]);
685  $this->checkParameterCount($varMatches[0], $parameters, $reference);
686 
687  //Attempt to Resolve {{data}} references to actual output. Trim parameter for whitespace before processing it.
688  //If regex matched it means that it's either a 'StringLiteral' or $key.data$/$$key.data$$ reference.
689  //Elseif regex match for {$data}
690  //Else assume it's a normal {{data.key}} reference and recurse through findAndReplace
691  $resolvedParameters = [];
692  foreach ($parameters as $parameter) {
693  $parameter = trim($parameter);
694  preg_match_all("/[$'][\w\D]+[$']/", $parameter, $stringOrPersistedMatch);
695  preg_match_all('/{\$[a-z][a-zA-Z\d]+}/', $parameter, $variableMatch);
696  if (!empty($stringOrPersistedMatch[0])) {
697  $resolvedParameters[] = ltrim(rtrim($parameter, "'"), "'");
698  } elseif (!empty($variableMatch[0])) {
699  $resolvedParameters[] = $parameter;
700  } else {
701  $resolvedParameters[] = $this->findAndReplaceReferences(
703  '{{' . $parameter . '}}'
704  );
705  }
706  }
707 
708  $resolveIndex = 0;
709  foreach ($varMatches[0] as $var) {
710  $reference = str_replace($var, $resolvedParameters[$resolveIndex++], $reference);
711  }
712  return $reference;
713  }
714 
724  private function checkParameterCount($matches, $parameters, $reference)
725  {
726  if (count($matches) > count($parameters)) {
727  if (is_array($parameters)) {
728  $parametersGiven = implode(",", $parameters);
729  } elseif ($parameters == null) {
730  $parametersGiven = "NONE";
731  } else {
732  $parametersGiven = $parameters;
733  }
734  throw new TestReferenceException(
735  "Parameter Resolution Failed: Not enough parameters given for reference " .
736  $reference . ". Parameters Given: " . $parametersGiven,
737  ["reference" => $reference, "parametersGiven" => $parametersGiven]
738  );
739  } elseif (count($matches) < count($parameters)) {
740  throw new TestReferenceException(
741  "Parameter Resolution Failed: Too many parameters given for reference " .
742  $reference . ". Parameters Given: " . implode(", ", $parameters),
743  ["reference" => $reference, "parametersGiven" => $parameters]
744  );
745  } elseif (count($matches) == 0) {
746  throw new TestReferenceException(
747  "Parameter Resolution Failed: No parameter matches found in parameterized element with selector " .
748  $reference,
749  ["reference" => $reference]
750  );
751  }
752  }
753 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$order
Definition: order.php:55
$replacement
Definition: website.php:23
$prefix
Definition: name.phtml:25
$value
Definition: gender.phtml:16
$attributes
Definition: matrix.phtml:13
__construct( $stepKey, $type, $actionAttributes, $linkedAction=null, $order=ActionObject::MERGE_ACTION_ORDER_BEFORE, $actionOrigin=null)
$index
Definition: list.phtml:44
if(!isset($_GET['name'])) $name
Definition: log.php:14