Skip to content

Commit 5733a1b

Browse files
authored
Merge pull request #161 from magento-borg/MAGETWO-46837-readiness-flag-develop
MAGETWO-46837: Implementing extension to wait for readiness metrics t…
2 parents 45d802b + b462aa8 commit 5733a1b

File tree

8 files changed

+913
-0
lines changed

8 files changed

+913
-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: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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\Step;
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+
];
37+
38+
/**
39+
* List of action types that should bypass metric checks
40+
* shouldSkipCheck() also checks for the 'Comment' step type, which doesn't follow the $step->getAction() pattern
41+
*
42+
* @var array
43+
*/
44+
private $ignoredActions = [
45+
'saveScreenshot',
46+
'wait'
47+
];
48+
49+
/**
50+
* @var Logger
51+
*/
52+
private $logger;
53+
54+
/**
55+
* Logger verbosity
56+
*
57+
* @var boolean
58+
*/
59+
private $verbose;
60+
61+
/**
62+
* Array of readiness metrics, initialized during beforeTest event
63+
*
64+
* @var AbstractMetricCheck[]
65+
*/
66+
private $readinessMetrics;
67+
68+
/**
69+
* The name of the active test
70+
*
71+
* @var string
72+
*/
73+
private $testName;
74+
75+
/**
76+
* The current URI of the active page
77+
*
78+
* @var string
79+
*/
80+
private $uri;
81+
82+
/**
83+
* Initialize local vars
84+
*
85+
* @return void
86+
* @throws \Exception
87+
*/
88+
public function _initialize()
89+
{
90+
$this->logger = LoggingUtil::getInstance()->getLogger(get_class($this));
91+
$this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled();
92+
}
93+
94+
/**
95+
* WebDriver instance to use to execute readiness metric checks
96+
*
97+
* @return WebDriver
98+
* @throws ModuleRequireException
99+
*/
100+
public function getDriver()
101+
{
102+
return $this->getModule($this->config['driver']);
103+
}
104+
105+
/**
106+
* Initialize the readiness metrics for the test
107+
*
108+
* @param \Codeception\Event\TestEvent $e
109+
* @return void
110+
*/
111+
public function beforeTest(TestEvent $e)
112+
{
113+
if (isset($this->config['resetFailureThreshold'])) {
114+
$failThreshold = intval($this->config['resetFailureThreshold']);
115+
} else {
116+
$failThreshold = 3;
117+
}
118+
119+
$this->testName = $e->getTest()->getMetadata()->getName();
120+
$this->uri = null;
121+
122+
$metrics = [];
123+
foreach ($this->config['readinessMetrics'] as $metricClass) {
124+
$metrics[] = new $metricClass($this, $failThreshold);
125+
}
126+
127+
$this->readinessMetrics = $metrics;
128+
}
129+
130+
/**
131+
* Waits for busy page flags to disappear before executing a step
132+
*
133+
* @param StepEvent $e
134+
* @return void
135+
* @throws \Exception
136+
*/
137+
public function beforeStep(StepEvent $e)
138+
{
139+
$step = $e->getStep();
140+
if ($this->shouldSkipCheck($step)) {
141+
return;
142+
}
143+
144+
$this->checkForNewPage($step);
145+
146+
// todo: Implement step parameter to override global timeout configuration
147+
if (isset($this->config['timeout'])) {
148+
$timeout = intval($this->config['timeout']);
149+
} else {
150+
$timeout = $this->getDriver()->_getConfig()['pageload_timeout'];
151+
}
152+
153+
$metrics = $this->readinessMetrics;
154+
155+
try {
156+
$this->getDriver()->webDriver->wait($timeout)->until(
157+
function () use ($metrics) {
158+
$passing = true;
159+
160+
/** @var AbstractMetricCheck $metric */
161+
foreach ($metrics as $metric) {
162+
try {
163+
if (!$metric->runCheck()) {
164+
$passing = false;
165+
}
166+
} catch (UnexpectedAlertOpenException $exception) {
167+
}
168+
}
169+
return $passing;
170+
}
171+
);
172+
} catch (TimeoutException $exception) {
173+
}
174+
175+
/** @var AbstractMetricCheck $metric */
176+
foreach ($metrics as $metric) {
177+
$metric->finalizeForStep($step);
178+
}
179+
}
180+
181+
/**
182+
* Check if the URI has changed and reset metric tracking if so
183+
*
184+
* @param Step $step
185+
* @return void
186+
*/
187+
private function checkForNewPage($step)
188+
{
189+
try {
190+
$currentUri = $this->getDriver()->_getCurrentUri();
191+
192+
if ($this->uri !== $currentUri) {
193+
$this->logDebug(
194+
'Page URI changed; resetting readiness metric failure tracking',
195+
[
196+
'step' => $step->__toString(),
197+
'newUri' => $currentUri
198+
]
199+
);
200+
201+
/** @var AbstractMetricCheck $metric */
202+
foreach ($this->readinessMetrics as $metric) {
203+
$metric->resetTracker();
204+
}
205+
206+
$this->uri = $currentUri;
207+
}
208+
} catch (\Exception $e) {
209+
$this->logDebug('Could not retrieve current URI', ['step' => $step->__toString()]);
210+
}
211+
}
212+
213+
/**
214+
* Gets the active page URI from the start of the most recent step
215+
*
216+
* @return string
217+
*/
218+
public function getUri()
219+
{
220+
return $this->uri;
221+
}
222+
223+
/**
224+
* Gets the name of the active test
225+
*
226+
* @return string
227+
*/
228+
public function getTestName()
229+
{
230+
return $this->testName;
231+
}
232+
233+
/**
234+
* Should the given step bypass the readiness checks
235+
* todo: Implement step parameter to bypass specific metrics (or all) instead of basing on action type
236+
*
237+
* @param Step $step
238+
* @return boolean
239+
*/
240+
private function shouldSkipCheck($step)
241+
{
242+
if ($step instanceof Step\Comment || in_array($step->getAction(), $this->ignoredActions)) {
243+
return true;
244+
}
245+
return false;
246+
}
247+
248+
/**
249+
* If verbose, log the given message to logger->debug including test context information
250+
*
251+
* @param string $message
252+
* @param array $context
253+
* @return void
254+
*/
255+
private function logDebug($message, $context = [])
256+
{
257+
if ($this->verbose) {
258+
$logContext = [
259+
'test' => $this->testName,
260+
'uri' => $this->uri
261+
];
262+
foreach ($this->readinessMetrics as $metric) {
263+
$logContext[$metric->getName()] = $metric->getStoredValue();
264+
$logContext[$metric->getName() . '.failCount'] = $metric->getFailureCount();
265+
}
266+
$context = array_merge($logContext, $context);
267+
$this->logger->info($message, $context);
268+
}
269+
}
270+
}

0 commit comments

Comments
 (0)