diff --git a/etc/config/codeception.dist.yml b/etc/config/codeception.dist.yml index 273ede0b0..a4ea0dcb5 100755 --- a/etc/config/codeception.dist.yml +++ b/etc/config/codeception.dist.yml @@ -23,5 +23,7 @@ extensions: - env - zephyrId - useCaseId + Magento\FunctionalTestingFramework\Extension\TestContextExtension: + driver: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver params: - .env \ No newline at end of file diff --git a/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php b/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php new file mode 100644 index 000000000..373c752d8 --- /dev/null +++ b/src/Magento/FunctionalTestingFramework/Extension/BaseExtension.php @@ -0,0 +1,104 @@ + 'beforeTest', + Events::STEP_BEFORE => 'beforeStep' + ]; + + /** + * The current URI of the active page + * + * @var string + */ + private $uri; + + /** + * Codeception event listener function - initialize uri before test + * + * @param \Codeception\Event\TestEvent $e + * @return void + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeTest(\Codeception\Event\TestEvent $e) + { + $this->uri = null; + } + + /** + * Codeception event listener function - check for page uri change before step + * + * @param \Codeception\Event\StepEvent $e + * @return void + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeStep(\Codeception\Event\StepEvent $e) + { + $this->pageChanged(); + } + + /** + * WebDriver instance for execution + * + * @return WebDriver + * @throws ModuleRequireException + */ + public function getDriver() + { + return $this->getModule($this->config['driver']); + } + + /** + * Gets the active page URI from the start of the most recent step + * + * @return string + */ + public function getUri() + { + return $this->uri; + } + + /** + * Check if page uri has changed + * + * @return boolean + */ + protected function pageChanged() + { + try { + $currentUri = $this->getDriver()->_getCurrentUri(); + + if ($this->uri !== $currentUri) { + $this->uri = $currentUri; + return true; + } + } catch (\Exception $e) { + // just fall through and return false + } + return false; + } +} diff --git a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php index b88482130..6350a5271 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php +++ b/src/Magento/FunctionalTestingFramework/Extension/ErrorLogger.php @@ -41,18 +41,21 @@ private function __construct() /** * Loops through stepEvent for browser log entries - * @param \Facebook\WebDriver\Remote\RemoteWebDriver $webDriver - * @param \Codeception\Event\StepEvent $stepEvent + * + * @param \Magento\FunctionalTestingFramework\Module\MagentoWebDriver $module + * @param \Codeception\Event\StepEvent $stepEvent * @return void */ - public function logErrors($webDriver, $stepEvent) + public function logErrors($module, $stepEvent) { //Types available should be "server", "browser", "driver". Only care about browser at the moment. - if (in_array("browser", $webDriver->manage()->getAvailableLogTypes())) { - $browserLogEntries = $webDriver->manage()->getLog("browser"); + if (in_array("browser", $module->$webDriver->manage()->getAvailableLogTypes())) { + $browserLogEntries = $module->$webDriver->manage()->getLog("browser"); foreach ($browserLogEntries as $entry) { if (array_key_exists("source", $entry) && $entry["source"] === "javascript") { $this->logError("javascript", $stepEvent, $entry); + //Set javascript error in MagentoWebDriver internal array + $module->setJsError("ERROR({$entry["level"]}) - " . $entry["message"]); } } } diff --git a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php b/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php index 709647096..8d49ad86c 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/PageReadinessExtension.php @@ -8,10 +8,6 @@ use Codeception\Event\StepEvent; use Codeception\Event\TestEvent; -use Codeception\Events; -use Codeception\Exception\ModuleRequireException; -use Codeception\Extension; -use Codeception\Module\WebDriver; use Codeception\Step; use Facebook\WebDriver\Exception\UnexpectedAlertOpenException; use Magento\FunctionalTestingFramework\Extension\ReadinessMetrics\AbstractMetricCheck; @@ -23,18 +19,8 @@ /** * Class PageReadinessExtension */ -class PageReadinessExtension extends Extension +class PageReadinessExtension extends BaseExtension { - /** - * Codeception Events Mapping to methods - * - * @var array - */ - public static $events = [ - Events::TEST_BEFORE => 'beforeTest', - Events::STEP_BEFORE => 'beforeStep' - ]; - /** * List of action types that should bypass metric checks * shouldSkipCheck() also checks for the 'Comment' step type, which doesn't follow the $step->getAction() pattern @@ -73,13 +59,6 @@ class PageReadinessExtension extends Extension */ private $testName; - /** - * The current URI of the active page - * - * @var string - */ - private $uri; - /** * Initialize local vars * @@ -90,27 +69,19 @@ public function _initialize() { $this->logger = LoggingUtil::getInstance()->getLogger(get_class($this)); $this->verbose = MftfApplicationConfig::getConfig()->verboseEnabled(); - } - - /** - * WebDriver instance to use to execute readiness metric checks - * - * @return WebDriver - * @throws ModuleRequireException - */ - public function getDriver() - { - return $this->getModule($this->config['driver']); + parent::_initialize(); } /** * Initialize the readiness metrics for the test * - * @param \Codeception\Event\TestEvent $e + * @param TestEvent $e * @return void + * @throws \Exception */ public function beforeTest(TestEvent $e) { + parent::beforeTest($e); if (isset($this->config['resetFailureThreshold'])) { $failThreshold = intval($this->config['resetFailureThreshold']); } else { @@ -118,7 +89,6 @@ public function beforeTest(TestEvent $e) } $this->testName = $e->getTest()->getMetadata()->getName(); - $this->uri = null; $this->getDriver()->_setConfig(['skipReadiness' => false]); @@ -136,6 +106,8 @@ public function beforeTest(TestEvent $e) * @param StepEvent $e * @return void * @throws \Exception + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function beforeStep(StepEvent $e) { @@ -145,7 +117,20 @@ public function beforeStep(StepEvent $e) return; } - $this->checkForNewPage($step); + // Check if page has changed and reset metric tracking if so + if ($this->pageChanged($step)) { + $this->logDebug( + 'Page URI changed; resetting readiness metric failure tracking', + [ + 'step' => $step->__toString(), + 'newUri' => $this->getUri() + ] + ); + /** @var AbstractMetricCheck $metric */ + foreach ($this->readinessMetrics as $metric) { + $metric->resetTracker(); + } + } // todo: Implement step parameter to override global timeout configuration if (isset($this->config['timeout'])) { @@ -182,48 +167,6 @@ function () use ($metrics) { } } - /** - * Check if the URI has changed and reset metric tracking if so - * - * @param Step $step - * @return void - */ - private function checkForNewPage($step) - { - try { - $currentUri = $this->getDriver()->_getCurrentUri(); - - if ($this->uri !== $currentUri) { - $this->logDebug( - 'Page URI changed; resetting readiness metric failure tracking', - [ - 'step' => $step->__toString(), - 'newUri' => $currentUri - ] - ); - - /** @var AbstractMetricCheck $metric */ - foreach ($this->readinessMetrics as $metric) { - $metric->resetTracker(); - } - - $this->uri = $currentUri; - } - } catch (\Exception $e) { - $this->logDebug('Could not retrieve current URI', ['step' => $step->__toString()]); - } - } - - /** - * Gets the active page URI from the start of the most recent step - * - * @return string - */ - public function getUri() - { - return $this->uri; - } - /** * Gets the name of the active test * @@ -263,7 +206,7 @@ private function logDebug($message, $context = []) if ($this->verbose) { $logContext = [ 'test' => $this->testName, - 'uri' => $this->uri + 'uri' => $this->getUri() ]; foreach ($this->readinessMetrics as $metric) { $logContext[$metric->getName()] = $metric->getStoredValue(); diff --git a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php index 417ae336f..8b611283d 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php +++ b/src/Magento/FunctionalTestingFramework/Extension/ReadinessMetrics/AbstractMetricCheck.php @@ -214,7 +214,6 @@ protected function getDriver() * @param string $script * @param array $arguments * @return mixed - * @throws UnexpectedAlertOpenException * @throws ModuleRequireException */ protected function executeJs($script, $arguments = []) diff --git a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php index 07f177dc3..3895d9efd 100644 --- a/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php +++ b/src/Magento/FunctionalTestingFramework/Extension/TestContextExtension.php @@ -6,32 +6,40 @@ namespace Magento\FunctionalTestingFramework\Extension; -use \Codeception\Events; +use Codeception\Events; use Magento\FunctionalTestingFramework\DataGenerator\Handlers\PersistedObjectHandler; -use Magento\FunctionalTestingFramework\Extension\ErrorLogger; -use Magento\FunctionalTestingFramework\Module\MagentoWebDriver; /** * Class TestContextExtension * @SuppressWarnings(PHPMD.UnusedPrivateField) */ -class TestContextExtension extends \Codeception\Extension +class TestContextExtension extends BaseExtension { const TEST_PHASE_AFTER = "_after"; - // @codingStandardsIgnoreStart - const MAGENTO_WEB_DRIVER_CLASS = "\Magento\FunctionalTestingFramework\Module\MagentoWebDriver"; - // @codingStandardsIgnoreEnd /** * Codeception Events Mapping to methods * @var array */ - public static $events = [ - Events::TEST_START => 'testStart', - Events::TEST_FAIL => 'testFail', - Events::STEP_AFTER => 'afterStep', - Events::TEST_END => 'testEnd' - ]; + public static $events; + + /** + * Initialize local vars + * + * @return void + * @throws \Exception + */ + public function _initialize() + { + $events = [ + Events::TEST_START => 'testStart', + Events::TEST_FAIL => 'testFail', + Events::STEP_AFTER => 'afterStep', + Events::TEST_END => 'testEnd' + ]; + self::$events = array_merge(parent::$events, $events); + parent::_initialize(); + } /** * Codeception event listener function, triggered on test start. @@ -64,6 +72,7 @@ public function testFail(\Codeception\Event\FailEvent $e) * Codeception event listener function, triggered on test ending (naturally or by error). * @param \Codeception\Event\TestEvent $e * @return void + * @throws \Exception */ public function testEnd(\Codeception\Event\TestEvent $e) { @@ -92,13 +101,13 @@ function () use ($cest) { } } // Reset Session and Cookies after all Test Runs, workaround due to functional.suite.yml restart: true - $this->getModule(self::MAGENTO_WEB_DRIVER_CLASS)->_runAfter($e->getTest()); + $this->getDriver()->_runAfter($e->getTest()); } /** * Runs cest's after block, if necessary. - * @param Symfony\Component\EventDispatcher\Event $e - * @param \Codeception\TestInterface $cest + * @param \Symfony\Component\EventDispatcher\Event $e + * @param \Codeception\TestInterface $cest * @return void */ private function runAfterBlock($e, $cest) @@ -135,17 +144,30 @@ public function extractContext($trace, $class) return null; } + /** + * Codeception event listener function, triggered before step. + * Check if it's a new page. + * + * @param \Codeception\Event\StepEvent $e + * @return void + * @throws \Exception + */ + public function beforeStep(\Codeception\Event\StepEvent $e) + { + if ($this->pageChanged($e->getStep())) { + $this->getDriver()->cleanJsError(); + } + } + /** * Codeception event listener function, triggered after step. * Calls ErrorLogger to log JS errors encountered. * @param \Codeception\Event\StepEvent $e * @return void + * @throws \Exception */ public function afterStep(\Codeception\Event\StepEvent $e) { - // @codingStandardsIgnoreStart - $webDriver = $this->getModule("\Magento\FunctionalTestingFramework\Module\MagentoWebDriver")->webDriver; - // @codingStandardsIgnoreEnd - ErrorLogger::getInstance()->logErrors($webDriver, $e); + ErrorLogger::getInstance()->logErrors($this->getDriver(), $e); } } diff --git a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php index 6c680784c..c64aa3721 100644 --- a/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php +++ b/src/Magento/FunctionalTestingFramework/Module/MagentoWebDriver.php @@ -105,6 +105,13 @@ class MagentoWebDriver extends WebDriver */ private $htmlReport; + /** + * Array to store Javascript errors + * + * @var string[] + */ + private $jsErrors = []; + /** * Sanitizes config, then initializes using parent. * @return void @@ -113,6 +120,7 @@ public function _initialize() { $this->config = ConfigSanitizerUtil::sanitizeWebDriverConfig($this->config); parent::_initialize(); + $this->cleanJsError(); } /** @@ -124,6 +132,7 @@ public function _resetConfig() { parent::_resetConfig(); $this->config = ConfigSanitizerUtil::sanitizeWebDriverConfig($this->config); + $this->cleanJsError(); } /** @@ -391,22 +400,6 @@ public function waitForLoadingMaskToDisappear($timeout = null) } } - /** - * Verify that there are no JavaScript errors in the console. - * - * @throws ModuleException - * @return void - */ - public function dontSeeJsError() - { - $logs = $this->webDriver->manage()->getLog('browser'); - foreach ($logs as $log) { - if ($log['level'] == 'SEVERE') { - throw new ModuleException($this, 'Errors in JavaScript: ' . json_encode($log)); - } - } - } - /** * @param float $money * @param string $locale @@ -709,4 +702,53 @@ public function skipReadinessCheck($check) { $this->config['skipReadiness'] = $check; } + + /** + * Clean Javascript errors in internal array + * + * @return void + */ + public function cleanJsError() + { + $this->jsErrors = []; + } + + /** + * Save Javascript error message to internal array + * + * @param string $errMsg + * @return void + */ + public function setJsError($errMsg) + { + $this->jsErrors[] = $errMsg; + } + + /** + * Get all Javascript errors + * + * @return string + */ + private function getJsErrors() + { + $errors = ''; + + if (!empty($this->jsErrors)) { + $errors = 'Errors in JavaScript:'; + foreach ($this->jsErrors as $jsError) { + $errors .= "\n" . $jsError; + } + } + return $errors; + } + + /** + * Verify that there is no JavaScript error in browser logs + * + * @return void + */ + public function dontSeeJsError() + { + $this->assertEmpty($this->jsErrors, $this->getJsErrors()); + } }