Skip to content

Commit 5c2d01a

Browse files
MAGETWO-46837: Implementing extension to wait for readiness metrics to pass before executing test steps
1 parent 95062cf commit 5c2d01a

File tree

8 files changed

+845
-0
lines changed

8 files changed

+845
-0
lines changed

etc/config/codeception.dist.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extensions:
1515
- Codeception\Extension\RunFailed
1616
- Magento\FunctionalTestingFramework\Extension\TestContextExtension
1717
- Magento\FunctionalTestingFramework\Allure\Adapter\MagentoAllureAdapter
18+
- Magento\FunctionalTestingFramework\Extension\PageReadinessExtension
1819
config:
1920
Yandex\Allure\Adapter\AllureAdapter:
2021
deletePreviousResults: true
@@ -23,5 +24,15 @@ extensions:
2324
- env
2425
- zephyrId
2526
- useCaseId
27+
Magento\FunctionalTestingFramework\Extension\PageReadinessExtension:
28+
driver: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver
29+
timeout: 30
30+
resetFailureThreshold: 3
31+
readinessMetrics:
32+
- \Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\DocumentReadyState
33+
- \Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\JQueryAjaxRequests
34+
- \Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\PrototypeAjaxRequests
35+
- \Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\RequireJsDefinitions
36+
- \Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\MagentoLoadingMasks
2637
params:
2738
- .env
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\FunctionalTestingFramework\Extension;
8+
9+
use Codeception\Event\StepEvent;
10+
use Codeception\Event\TestEvent;
11+
use Codeception\Events;
12+
use Codeception\Exception\ModuleRequireException;
13+
use Codeception\Extension;
14+
use Codeception\Module\WebDriver;
15+
use Codeception\TestInterface;
16+
use Facebook\WebDriver\Exception\UnexpectedAlertOpenException;
17+
use Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\AbstractMetricCheck;
18+
use Facebook\WebDriver\Exception\TimeOutException;
19+
use Magento\FunctionalTestingFramework\Util\Logger\LoggingUtil;
20+
use Magento\FunctionalTestingFramework\Config\MftfApplicationConfig;
21+
use Monolog\Logger;
22+
23+
/**
24+
* Class PageReadinessExtension
25+
*/
26+
class PageReadinessExtension extends Extension
27+
{
28+
/**
29+
* Codeception Events Mapping to methods
30+
*
31+
* @var array
32+
*/
33+
public static $events = [
34+
Events::TEST_BEFORE => 'beforeTest',
35+
Events::STEP_BEFORE => 'beforeStep',
36+
Events::STEP_AFTER => 'afterStep'
37+
];
38+
39+
/**
40+
* @var Logger
41+
*/
42+
private $logger;
43+
44+
/**
45+
* Logger verbosity
46+
*
47+
* @var bool
48+
*/
49+
private $verbose;
50+
51+
/**
52+
* Array of readiness metrics, initialized during beforeTest event
53+
*
54+
* @var AbstractMetricCheck[]
55+
*/
56+
private $readinessMetrics;
57+
58+
/**
59+
* Active test object
60+
*
61+
* @var TestInterface
62+
*/
63+
private $test;
64+
65+
/**
66+
* Initialize local vars
67+
*
68+
* @return void
69+
* @throws \Exception
70+
*/
71+
public function _initialize()
72+
{
73+
$this->logger = LoggingUtil::getInstance()->getLogger(get_class($this));
74+
$this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled();
75+
}
76+
77+
/**
78+
* WebDriver instance to use to execute readiness metric checks
79+
*
80+
* @return WebDriver
81+
* @throws ModuleRequireException
82+
*/
83+
public function getDriver()
84+
{
85+
return $this->getModule($this->config['driver']);
86+
}
87+
88+
/**
89+
* Initialize the readiness metrics for the test
90+
*
91+
* @param \Codeception\Event\TestEvent $e
92+
* @return void
93+
*/
94+
public function beforeTest(TestEvent $e) {
95+
$this->test = $e->getTest();
96+
97+
if (isset($this->config['resetFailureThreshold'])) {
98+
$failThreshold = intval($this->config['resetFailureThreshold']);
99+
}
100+
else {
101+
$failThreshold = 3;
102+
}
103+
104+
$metrics = [];
105+
foreach ($this->config['readinessMetrics'] as $metricClass) {
106+
$metrics[] = new $metricClass($this, $this->test, $failThreshold);
107+
}
108+
109+
$this->readinessMetrics = $metrics;
110+
}
111+
112+
/**
113+
* Waits for busy page flags to disappear before executing a step
114+
*
115+
* @param StepEvent $e
116+
* @return void
117+
* @throws \Exception
118+
*/
119+
public function beforeStep(StepEvent $e) {
120+
$step = $e->getStep();
121+
if ($step->getAction() == 'saveScreenshot') {
122+
return;
123+
}
124+
// $step->getArguments()['skipReadiness']
125+
126+
try {
127+
$this->test->getMetadata()->setCurrent(['uri', $this->getDriver()->_getCurrentUri()]);
128+
}
129+
catch (\Exception $exception) {
130+
// $this->debugLog('Could not retrieve current URI', ['action' => $e->getStep()->getAction()]);
131+
}
132+
133+
134+
if (isset($this->config['timeout'])) {
135+
$timeout = intval($this->config['timeout']);
136+
}
137+
else {
138+
$timeout = $this->getDriver()->_getConfig()['pageload_timeout'];
139+
}
140+
141+
$metrics = $this->readinessMetrics;
142+
143+
try {
144+
$this->getDriver()->webDriver->wait($timeout)->until(
145+
function () use ($metrics) {
146+
$passing = true;
147+
148+
/** @var AbstractMetricCheck $metric */
149+
foreach ($metrics as $metric) {
150+
try {
151+
if (!$metric->runCheck()) {
152+
$passing = false;
153+
}
154+
}
155+
catch (UnexpectedAlertOpenException $exception) {}
156+
}
157+
return $passing;
158+
}
159+
);
160+
}
161+
catch (TimeoutException $exception) {}
162+
163+
/** @var AbstractMetricCheck $metric */
164+
foreach ($metrics as $metric) {
165+
$metric->finalize($step);
166+
}
167+
}
168+
169+
/**
170+
* Checks to see if the step changed the uri and resets failure tracking if so
171+
*
172+
* @param StepEvent $e
173+
* @return void
174+
*/
175+
public function afterStep(StepEvent $e) {
176+
$step = $e->getStep();
177+
if ($step->getAction() == 'saveScreenshot') {
178+
return;
179+
}
180+
181+
try {
182+
$currentUri = $this->getDriver()->_getCurrentUri();
183+
}
184+
catch (\Exception $e) {
185+
// $this->debugLog('Could not retrieve current URI', ['action' => $step()->getAction()]);
186+
return;
187+
}
188+
189+
$previousUri = $this->test->getMetadata()->getCurrent('uri');
190+
191+
if ($previousUri !== $currentUri) {
192+
$this->logDebug('Page URI changed; resetting readiness metric failure tracking',
193+
[
194+
'action' => $step->getAction(),
195+
'newUri' => $currentUri
196+
]
197+
);
198+
199+
/** @var AbstractMetricCheck $metric */
200+
foreach ($this->readinessMetrics as $metric) {
201+
$metric->setTracker();
202+
}
203+
}
204+
}
205+
206+
/**
207+
* If verbose, log the given message to logger->debug including test context information
208+
*
209+
* @param string $message
210+
* @param array $context
211+
*/
212+
private function logDebug($message, $context = []) {
213+
if ($this->verbose) {
214+
$testMeta = $this->test->getMetadata();
215+
$logContext = [
216+
'test' => $testMeta->getName(),
217+
'uri' => $testMeta->getCurrent('uri')
218+
];
219+
foreach ($this->readinessMetrics as $metric) {
220+
$logContext[$metric->getName()] = $metric->getStoredValue();
221+
$logContext[$metric->getName() . '.failCount'] = $metric->getFailureCount();
222+
}
223+
$context = array_merge($logContext, $context);
224+
$this->logger->info($message, $context);
225+
}
226+
}
227+
}

0 commit comments

Comments
 (0)