Skip to content

Commit 88e6ac5

Browse files
committed
MQE-879: Add support for tests actions in suite pre/post conditions
- update mustahche template to use new actions - update Magento Webdriver to account for no test context - update suiteSchema.xml and di.xml
1 parent 2a0ec6c commit 88e6ac5

File tree

11 files changed

+206
-46
lines changed

11 files changed

+206
-46
lines changed

etc/di.xml

+7-1
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@
358358
<argument name="schemaLocator" xsi:type="object">Magento\FunctionalTestingFramework\Config\SchemaLocator\SuiteData</argument>
359359
<argument name="idAttributes" xsi:type="array">
360360
<item name="/suites/suite" xsi:type="string">name</item>
361+
<item name="/suites/suite/(before|after)/remove" xsi:type="string">keyForRemoval</item>
362+
<item name="/suites/suite/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item>
363+
<item name="/suites/suite/(before|after)/createData/requiredEntity" xsi:type="string">createDataKey</item>
364+
<item name="/suites/suite/(before|after)/createData/field" xsi:type="string">key</item>
361365
<item name="/suites/suite/include/(group|test|module)" xsi:type="string">name</item>
362366
<item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item>
363367
</argument>
@@ -370,8 +374,10 @@
370374
<arguments>
371375
<argument name="assocArrayAttributes" xsi:type="array">
372376
<item name="/suites/suite" xsi:type="string">name</item>
373-
<item name="/suites/suite/(before|after)/(createData|deleteData)" xsi:type="string">stepKey</item>
377+
<item name="/suites/suite/(before|after)/remove" xsi:type="string">keyForRemoval</item>
378+
<item name="/suites/suite/(before|after)/(actionGroup|&commonTestActions;)" xsi:type="string">stepKey</item>
374379
<item name="/suites/suite/(before|after)/createData/requiredEntity" xsi:type="string">createDataKey</item>
380+
<item name="/suites/suite/(before|after)/createData/field" xsi:type="string">key</item>
375381
<item name="/suites/suite/include/(group|test|module)" xsi:type="string">name</item>
376382
<item name="/suites/suite/exclude/(group|test|module)" xsi:type="string">name</item>
377383
</argument>

src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ public function scrollToTopOfPage()
435435
* @param string $command
436436
* @returns string
437437
*/
438-
public function executeMagentoCLICommand($command)
438+
public function magentoCLI($command)
439439
{
440440

441441
$apiURL = $this->config['url'] . getenv('MAGENTO_CLI_COMMAND_PATH');
@@ -527,6 +527,10 @@ public function _failed(TestInterface $test, $fail)
527527
$this->saveScreenshot();
528528
}
529529

530+
if ($this->current_test == null) {
531+
throw new \RuntimeException("Suite condition failure: \n" . $fail->getMessage());
532+
}
533+
530534
$this->addAttachment($this->pngReport, $test->getMetadata()->getName() . '.png', 'image/png');
531535
$this->addAttachment($this->htmlReport, $test->getMetadata()->getName() . '.html', 'text/html');
532536

@@ -540,8 +544,12 @@ public function _failed(TestInterface $test, $fail)
540544
*/
541545
public function saveScreenshot()
542546
{
543-
$test = $this->current_test;
544-
$filename = preg_replace('~\W~', '.', Descriptor::getTestSignature($test));
547+
$testDescription = "unknown." . uniqid();
548+
if ($this->current_test != null) {
549+
$testDescription = Descriptor::getTestSignature($this->current_test);
550+
}
551+
552+
$filename = preg_replace('~\W~', '.', $testDescription);
545553
$outputDir = codecept_output_dir();
546554
$this->_saveScreenshot($this->pngReport = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png');
547555
$this->_savePageSource($this->htmlReport = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html');

src/Magento/FunctionalTestingFramework/Suite/Generators/GroupClassGenerator.php

+120-14
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ class GroupClassGenerator
2626
const REQUIRED_ENTITY_KEY = 'requiredEntities';
2727
const LAST_REQUIRED_ENTITY_TAG = 'last';
2828
const MUSTACHE_VAR_TAG = 'var';
29-
29+
const MAGENTO_CLI_COMMAND_COMMAND = 'command';
30+
const DATA_PERSISTENCE_ACTIONS = ["createData", "deleteData"];
31+
const REPLACEMENT_ACTIONS = [
32+
'comment' => 'print'
33+
];
3034
const GROUP_DIR_NAME = 'Group';
3135

3236
/**
@@ -83,14 +87,30 @@ private function createClassContent($suiteObject)
8387

8488
$mustacheData[self::BEFORE_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getBeforeHook());
8589
$mustacheData[self::AFTER_MUSTACHE_KEY] = $this->buildHookMustacheArray($suiteObject->getAfterHook());
86-
$mustacheData[self::MUSTACHE_VAR_TAG] = array_merge(
87-
$mustacheData[self::BEFORE_MUSTACHE_KEY]['createData'] ?? [],
88-
$mustacheData[self::AFTER_MUSTACHE_KEY]['createData'] ?? []
90+
$mustacheData[self::MUSTACHE_VAR_TAG] = $this->extractClassVar(
91+
$mustacheData[self::BEFORE_MUSTACHE_KEY],
92+
$mustacheData[self::AFTER_MUSTACHE_KEY]
8993
);
9094

9195
return $this->mustacheEngine->render(self::MUSTACHE_TEMPLATE_NAME, $mustacheData);
9296
}
9397

98+
/**
99+
* Function which takes the before and after arrays containing the steps for the hook objects and extracts
100+
* any variables names needed by the class template.
101+
*
102+
* @param array $beforeArray
103+
* @param array $afterArray
104+
* @return array
105+
*/
106+
private function extractClassVar($beforeArray, $afterArray)
107+
{
108+
$beforeVar = $beforeArray[self::MUSTACHE_VAR_TAG] ?? [];
109+
$afterVar = $afterArray[self::MUSTACHE_VAR_TAG] ?? [];
110+
111+
return array_merge($beforeVar, $afterVar);
112+
}
113+
94114
/**
95115
* Function which takes hook objects and transforms data into array for mustache template engine.
96116
*
@@ -100,26 +120,112 @@ private function createClassContent($suiteObject)
100120
private function buildHookMustacheArray($hookObj)
101121
{
102122
$mustacheHookArray = [];
123+
$actions = [];
124+
$hasWebDriverActions = false;
103125
foreach ($hookObj->getActions() as $action) {
104126
/** @var ActionObject $action */
127+
$index = count($actions);
128+
if (!in_array($action->getType(), self::DATA_PERSISTENCE_ACTIONS)) {
129+
if (!$hasWebDriverActions) {
130+
$hasWebDriverActions = true;
131+
}
132+
133+
$actions = $this->buildWebDriverActionsMustacheArray($action, $actions, $index);
134+
continue;
135+
}
136+
137+
// add these as vars to be created a class level in the template
138+
if ($action->getType() == 'createData') {
139+
$mustacheHookArray[self::MUSTACHE_VAR_TAG][] = [self::ENTITY_MERGE_KEY => $action->getStepKey()];
140+
}
141+
105142
$entityArray = [];
106143
$entityArray[self::ENTITY_MERGE_KEY] = $action->getStepKey();
107-
$entityArray[self::ENTITY_NAME_TAG] =
108-
$action->getCustomActionAttributes()['entity'] ??
109-
$action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE];
110-
111-
// if there is more than 1 custom attribute, we can assume there are required entities
112-
if (count($action->getCustomActionAttributes()) > 1) {
113-
$entityArray[self::REQUIRED_ENTITY_KEY] =
114-
$this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes());
115-
}
144+
$entityArray[$action->getType()] = $action->getStepKey();
116145

117-
$mustacheHookArray[$action->getType()][] = $entityArray;
146+
$entityArray = $this->buildPersistenceMustacheArray($action, $entityArray);
147+
$actions[$index] = $entityArray;
148+
}
149+
$mustacheHookArray['actions'] = $actions;
150+
if ($hasWebDriverActions) {
151+
array_unshift($mustacheHookArray['actions'], ['webDriverInit' => true]);
152+
$mustacheHookArray['actions'][] = ['webDriverReset' => true];
118153
}
119154

120155
return $mustacheHookArray;
121156
}
122157

158+
/**
159+
* Takes an action object and array of generated action steps. Converst the action object into generated php and
160+
* appends the entry to the given array. The result is returned by the function.
161+
*
162+
* @param ActionObject $action
163+
* @param array $actionEntries
164+
* @return array
165+
*/
166+
private function buildWebDriverActionsMustacheArray($action, $actionEntries)
167+
{
168+
$step = TestGenerator::getInstance()->generateStepsPhp([$action], false, 'webDriver');
169+
$rawPhp = str_replace(["\t", "\n"], "", $step);
170+
$multipleCommands = explode(";", $rawPhp, -1);
171+
foreach ($multipleCommands as $command) {
172+
$actionEntries = $this->replaceReservedTesterFunctions($command . ";", $actionEntries, 'webDriver');
173+
}
174+
175+
return $actionEntries;
176+
}
177+
178+
/**
179+
* Takes a generated php step, an array containing generated php entries for the template, and the actor name
180+
* for the generated step.
181+
*
182+
* @param string $formattedStep
183+
* @param array $actionEntries
184+
* @param string $actor
185+
* @return array
186+
*/
187+
private function replaceReservedTesterFunctions($formattedStep, $actionEntries, $actor)
188+
{
189+
foreach (self::REPLACEMENT_ACTIONS as $testAction => $replacement) {
190+
$testActionCall = "\${$actor}->{$testAction}";
191+
if (substr($formattedStep, 0, strlen($testActionCall)) == $testActionCall) {
192+
$resultingStep = str_replace($testActionCall, $replacement, $formattedStep);
193+
$actionEntries[] = ['action' => $resultingStep];
194+
} else {
195+
$actionEntries[] = ['action' => $formattedStep];
196+
}
197+
}
198+
199+
return $actionEntries;
200+
}
201+
202+
/**
203+
* Takes an action object of persistence type and formats an array entiry for mustache template interpretation.
204+
*
205+
* @param ActionObject $action
206+
* @param array $entityArray
207+
* @return array
208+
*/
209+
private function buildPersistenceMustacheArray($action, $entityArray)
210+
{
211+
$entityArray[self::ENTITY_NAME_TAG] =
212+
$action->getCustomActionAttributes()['entity'] ??
213+
$action->getCustomActionAttributes()[TestGenerator::REQUIRED_ENTITY_REFERENCE];
214+
215+
// append entries for any required entities to this entry
216+
if (array_key_exists('requiredEntities', $action->getCustomActionAttributes())) {
217+
$entityArray[self::REQUIRED_ENTITY_KEY] =
218+
$this->buildReqEntitiesMustacheArray($action->getCustomActionAttributes());
219+
}
220+
221+
// append entries for customFields if specified by the user.
222+
if (array_key_exists('customFields', $action->getCustomActionAttributes())) {
223+
$entityArray['customFields'] = $action->getStepKey() . 'Fields';
224+
}
225+
226+
return $entityArray;
227+
}
228+
123229
/**
124230
* Function which takes any required entities under a 'createData' tag and transforms data into array to be consumed
125231
* by mustache template.

src/Magento/FunctionalTestingFramework/Suite/SuiteGenerator.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ private function appendEntriesToConfig($suiteName, $suitePath, $groupNamespace)
119119

120120
$ymlArray[self::YAML_GROUPS_TAG][$suiteName] = [$relativeSuitePath];
121121

122-
if ($groupNamespace) {
122+
if ($groupNamespace &&
123+
!in_array($groupNamespace, $ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG])) {
123124
$ymlArray[self::YAML_EXTENSIONS_TAG][self::YAML_ENABLED_TAG][] = $groupNamespace;
124125
}
125126

src/Magento/FunctionalTestingFramework/Suite/Util/SuiteObjectExtractor.php

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\FunctionalTestingFramework\Suite\Util;
77

88
use Exception;
9+
use Magento\FunctionalTestingFramework\Exceptions\XmlException;
910
use Magento\FunctionalTestingFramework\Suite\Objects\SuiteObject;
1011
use Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler;
1112
use Magento\FunctionalTestingFramework\Test\Objects\TestObject;
@@ -37,6 +38,7 @@ public function __construct()
3738
*
3839
* @param array $parsedSuiteData
3940
* @return array
41+
* @throws XmlException
4042
*/
4143
public function parseSuiteDataIntoObjects($parsedSuiteData)
4244
{
@@ -78,6 +80,12 @@ public function parseSuiteDataIntoObjects($parsedSuiteData)
7880
$parsedSuite[TestObjectExtractor::TEST_AFTER_HOOK]
7981
);
8082
}
83+
if (count($suiteHooks) == 1) {
84+
throw new XmlException(sprintf(
85+
"Suites that contain hooks must contain both a 'before' and an 'after' hook. Suite: \"%s\"",
86+
$parsedSuite[self::NAME]
87+
));
88+
}
8189

8290
// create the new suite object
8391
$suiteObjects[$parsedSuite[self::NAME]] = new SuiteObject(

src/Magento/FunctionalTestingFramework/Suite/etc/suiteSchema.xsd

+7-7
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@
4848
<xs:element type="moduleSuiteOptionType" name="module" minOccurs="0"/>
4949
</xs:choice>
5050
</xs:complexType>
51-
<xs:complexType name="suiteHookType">
52-
<xs:choice minOccurs="0" maxOccurs="unbounded">
53-
<xs:group ref="dataOperationTags" maxOccurs="unbounded" minOccurs="0"/>
54-
</xs:choice>
55-
</xs:complexType>
51+
<!--<xs:complexType name="suiteHookType">-->
52+
<!--<xs:choice minOccurs="0" maxOccurs="unbounded">-->
53+
<!--<xs:group ref="dataOperationTags" maxOccurs="unbounded" minOccurs="0"/>-->
54+
<!--</xs:choice>-->
55+
<!--</xs:complexType>-->
5656
<xs:complexType name="suiteType">
5757
<xs:choice minOccurs="0" maxOccurs="unbounded">
5858
<xs:element type="includeType" name="include" maxOccurs="1"/>
5959
<xs:element type="excludeType" name="exclude" maxOccurs="1"/>
60-
<xs:element type="suiteHookType" name="before" maxOccurs="1"/>
61-
<xs:element type="suiteHookType" name="after" maxOccurs="1"/>
60+
<xs:element type="hookType" name="before" maxOccurs="1"/>
61+
<xs:element type="hookType" name="after" maxOccurs="1"/>
6262
</xs:choice>
6363
<xs:attribute type="xs:string" name="name"/>
6464
</xs:complexType>

src/Magento/FunctionalTestingFramework/Suite/views/SuiteClass.mustache

+11-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class {{suiteName}} extends \Codeception\GroupObject
1919
public static $group = '{{suiteName}}';
2020
private static $TEST_COUNT = {{testCount}};
2121
private static $CURRENT_TEST_RUN = 0;
22-
22+
private static $HOOK_EXECUTION_INIT = "\n/******** Beginning execution of {{suiteName}} suite %s block ********/\n";
23+
private static $HOOK_EXECUTION_END = "\n/******** Execution of {{suiteName}} suite %s block complete ********/\n";
2324
{{#var}}
2425
private ${{stepKey}};
2526
{{/var}}
@@ -31,7 +32,11 @@ class {{suiteName}} extends \Codeception\GroupObject
3132
self::$CURRENT_TEST_RUN++;
3233
3334
if (self::$CURRENT_TEST_RUN == 1) {
34-
{{> dataPersistence}}
35+
print sprintf(self::$HOOK_EXECUTION_INIT, "before");
36+
37+
{{> testActions}}
38+
39+
print sprintf(self::$HOOK_EXECUTION_END, "before");
3540
}
3641
}
3742
{{/before}}
@@ -40,14 +45,11 @@ class {{suiteName}} extends \Codeception\GroupObject
4045
public function _after(\Codeception\Event\TestEvent $e)
4146
{
4247
if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) {
43-
{{> dataPersistence}}
44-
}
45-
}
48+
print sprintf(self::$HOOK_EXECUTION_INIT, "after");
4649
47-
public function _failed(\Codeception\Event\TestEvent $e)
48-
{
49-
if (self::$CURRENT_TEST_RUN == self::$TEST_COUNT) {
50-
{{> dataPersistence}}
50+
{{> testActions}}
51+
52+
print sprintf(self::$HOOK_EXECUTION_END, "after");
5153
}
5254
}
5355
{{/after}}

src/Magento/FunctionalTestingFramework/Suite/views/partials/dataPersistence.mustache

-8
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{{#actions}}
2+
{{#webDriverInit}}
3+
$webDriver = $this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver');
4+
5+
// close any open sessions
6+
if ($webDriver->webDriver != null) {
7+
$webDriver->webDriver->close();
8+
$webDriver->webDriver = null;
9+
}
10+
11+
// initialize the webdriver session
12+
$webDriver->_initializeSession();
13+
14+
// execute user specified actions
15+
{{/webDriverInit}}
16+
{{#webDriverReset}}
17+
// reset configuration and close session
18+
$this->getModule('\Magento\FunctionalTestingFramework\Module\MagentoWebDriver')->_resetConfig();
19+
$webDriver->webDriver->close();
20+
$webDriver->webDriver = null;
21+
{{/webDriverReset}}
22+
{{#action}}
23+
{{{action}}}
24+
{{/action}}
25+
{{#createData}}
26+
${{entityName}} = DataObjectHandler::getInstance()->getObject("{{entityName}}");
27+
$this->{{stepKey}} = new DataPersistenceHandler(${{entityName}}, [{{#requiredEntities}}$this->{{entityName}}{{^last}}, {{/last}}{{/requiredEntities}}]{{#customFields}}, ${{customFields}}{{/customFields}});
28+
$this->{{stepKey}}->createEntity();
29+
{{/createData}}
30+
{{#deleteData}}
31+
$this->{{entityName}}->deleteEntity();
32+
{{/deleteData}}
33+
{{/actions}}

src/Magento/FunctionalTestingFramework/Test/Util/ActionObjectExtractor.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private function extractFieldReferences($actionData, $actionAttributes)
178178
return $actionAttributes;
179179
}
180180

181-
$attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][self::NODE_NAME] = 'fields';
181+
$attributes = [];
182182
foreach ($actionAttributes as $attributeName => $attributeValue) {
183183
if (!is_array($attributeValue) || $attributeValue[self::NODE_NAME] != self::DATA_PERSISTENCE_CUSTOM_FIELD) {
184184
$attributes[$attributeName] = $attributeValue;
@@ -188,6 +188,10 @@ private function extractFieldReferences($actionData, $actionAttributes)
188188
$attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][] = $attributeName;
189189
}
190190

191+
if (array_key_exists(self::ACTION_OBJECT_PERSISTENCE_FIELDS, $attributes)) {
192+
$attributes[self::ACTION_OBJECT_PERSISTENCE_FIELDS][self::NODE_NAME] = 'fields';
193+
}
194+
191195
return $attributes;
192196
}
193197
}

0 commit comments

Comments
 (0)