/ */ namespace BehatTests; use Behat\Behat\Event\StepEvent; use Behat\Mink\Element\NodeElement as MinkNodeElement; use Behat\Mink\Driver\Selenium2Driver as Selenium2Driver; use Behat\MinkExtension\Context\MinkContext; use Symfony\Component\CssSelector; class FeatureMinkContext extends MinkContext { const TYPE_CSS = 'css'; const TYPE_FIELD = 'field'; const TYPE_BUTTON = 'button'; const TYPE_LINK = 'link'; const TYPE_XPATH = 'xpath'; const ELEMENT_LOGIN = '.icon-user .icon-white'; const ELEMENT_ERROR = '.error-layout .error-code'; protected $configParams = array(); public static $stepFailed = false; public function __construct(array $parameters = null) { $this->configParams = $parameters; $contexts = isset($parameters['contexts']) ? $parameters['contexts'] : array(); if (!count($parameters['contexts'])) { $this->printDebug('WARNING: No subcontexts found'); } // load all contexts specified in the config (behat.yml) foreach ($contexts as $name => $class) { $class = 'BehatTests\\' . $class; $this->useContext($name, new $class($parameters)); } } /** * Resets the browser session. * * @AfterScenario @javascript * @Given /^(?: |I) reset browser session$/ */ public function resetSession() { $this->getSession()->restart(); } /** * Waits the specified number of seconds before executing the next step. * * @param integer $seconds number of seconds to wait * * @Given /^I wait for (\d+) seconds$/ */ public function wait($seconds) { sleep($seconds); } /** * Saves a screenshot and snapshot of swarm logs if a step fails. * In case of a scenario run with goutte driver, only swarm logs get saved * * @param StepEvent $event behat step event generated after each gherkin step * * @AfterStep */ public function afterStep(StepEvent $event) { if ($event->getResult() == StepEvent::FAILED) { static::$stepFailed = true ; if ($this->usingSelenium2()) { $this->iSaveAScreenshot(); $this->iSaveTheSwarmLog(); } else { $this->iSaveTheSwarmLog(); } } } /** * Step to explicitly summarize and save the test's swarm log. * * @Given /^I save the swarm log$/ */ public function iSaveTheSwarmLog() { $script = BASE_PATH . '/collateral/scripts/logsummarizer.php'; $uuid = $this->getP4Context()->getUUID(); $log = $this->configParams['data_dir'] . '/' . $uuid . '/log'; $summarized = $this->configParams['failures_dir'] . '/' . $uuid . '/swarm_error_log'; $raw = $this->configParams['failures_dir'] . '/' . $uuid . '/swarm_raw_log'; if (!is_readable($log)) { // no swarm log file generated $this->printDebug('Failure in p4d setup. No swarm log is generated at expected location: ' . $log); return; } // copy the raw logs over copy($log, $raw); chmod($raw, 04707); // we have a log file and access to the summarizer script if (is_readable($script)) { exec( 'php ' . implode(' ', array_map('escapeshellarg', array($script, '-f', $log, '-q'))), $output ); if (count($output)) { file_put_contents($summarized, implode($output, "\n")); chmod($summarized, 04707); } } } /** * Step to explicitly save a screenshot * * @Given /^I save a screenshot$/ */ public function iSaveAScreenshot() { if ($this->getSession()->isStarted()) { $file = $this->getMinkParameter('browser_name') . '.png'; $dir = $this->configParams['failures_dir'] . '/' . $this->getP4Context()->getUUID(); $this->createDir($dir); $this->printDebug("Screenshot saved to $dir/$file"); $this->saveScreenshot($file, $dir); } else { $this->printDebug("Browser session not started - screenshot not possible"); } } /** * Tests whether we're using the Selenium2 driver, and support JS. * * @return bool true if we're using the Selenium2 driver, false otherwise */ public function usingSelenium2() { return $this->getSession()->getDriver() instanceof Selenium2Driver; } /** * Helper method that returns the HTTP status code from the current page content. * * @return string HTTP response code, scraped from current page content */ public function getHttpStatusCode() { if ($this->usingSelenium2()) { // [If] The test is running with '@javascript' tag (selenium driver), we cannot directly use the // MinkContext::getStatusCode() method, since it is unsupported for selenium. // Hence we implement the logic based on scrapping the status code from the DOM if (!$this->findElementByType(self::ELEMENT_ERROR, 'css')) { return '200'; } return $this->findElementByType(self::ELEMENT_ERROR, 'css')->getText(); } // [Else] We use the in-built getStatusCode() method, which should be faster to invoke return $this->getSession()->getStatusCode(); } /** * Executes javascript to scroll and bring element within the window visible to the simulated user. * * @param $css string the css selector for the element to scroll to */ public function scrollPageToElement($css) { $js = "var destination = $(\"$css\").offset().top; $(document).scrollTop(destination);"; $this->getSession()->getDriver()->executeScript($js); } /** * Checks, that current page url path is equal to expected path * The function performs the equality check very 250 ms, up to a certain maximum * * @param $expectedUrl string expected page url the browser should be on * @param $seconds int max timeout required for the assertion * @return bool true when expected and actual urls match within max seconds * @throws \Exception when expected and actual urls don't match after max seconds * * @Then /^(?:|I )wait to be on "(?P[^"]+)" page$/ * @Then /^(?:|I )wait until url "(?P[^"]+)" loads$/ */ public function waitUntilPageUrlLoads($expectedUrl, $seconds = 25) { $expectedUrl = trim($expectedUrl, ' /'); $start = time(); do { $url = trim($this->getSession()->getCurrentUrl(), ' /'); try { if (preg_match('/https?:\/\//', $expectedUrl) !== false) { assertEquals($expectedUrl, $url); } else { $url = '/' . (string)end(explode('/', $url)); assertEquals($expectedUrl, $url); } return true; } catch (\Exception $e) { // quarter second sleep usleep(250 * 1000); } } while ((time() - $start) < $seconds); // max 25 seconds throw new \Exception("Failed asserting that actual URL \"$url\" equals expected URL \"$expectedUrl\""); } /** * Waits until given page element is seen on page or for a max. of 25 seconds * The element can be searched out using DOM selectors like 'css/xpath' or using named selectors * Note: Will only work with '@javascript' tag on scenario (selenium2 driver) * * @param string $locator * @param string $selector Type of selector for page element. * CSS selectors : xpath, css * Named selectors: fieldset|field|link|button|link_or_button|content| * select|checkbox|radio|file|optgroup|option|table * * @param int $seconds max timeout required for the assertion * @return bool true when expected and actual page elements match within max seconds * @throws \Exception when expected and actual elements don't match after max seconds * * @Then /^(?:|I )wait until page selector "(?P[^"]+)" with value "(?P[^"]+)" loads$/ */ public function waitUntilPageElementLoads($locator, $selector = self::TYPE_CSS, $seconds = 25) { if (!$this->usingSelenium2()) { throw new \Exception("Method waitUntilPageElementLoads() works with Selenium2 driver only"); } $start = time(); do { try { if ($selector != self::TYPE_XPATH) { $xpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($selector, $locator); } else { $xpath = $locator; } // isVisible($locator) needs $locator to be in 'xpath' format $this->getSession()->getDriver()->isVisible($xpath); return true; } catch (\Exception $e) { // half second sleep usleep(500 * 1000); } } while ((time() - $start) < $seconds); // max 25 seconds throw new \Exception("Failed asserting that element \"$locator\" is visible on page"); } /** * Waits until there are no more active Ajax calls, or until the max timeout has been reached * * @param int $mseconds the max timeout */ public function waitUntilAjaxCallsComplete($mseconds = 5000) { $this->getSession()->wait($mseconds, '(0 === jQuery.active)'); } /** * Helper function that returns a DOM element based on the locator * * @param string $locator input id, name or label of element * @param string $type type of DOM element being looked for: * self::TYPE_CSS: css selector * self::TYPE_FIELD: form field name * self::TYPE_BUTTON: for button name * self::TYPE_LINK: link anchor text * self::TYPE_XPATH: xpath selector * @return MinkNodeElement|bool the found node, or false if an unknown type was given */ public function findElementByType($locator, $type = self::TYPE_CSS) { $page = $this->getSession()->getPage(); switch ($type) { case self::TYPE_CSS: return $page->find('css', $locator); case self::TYPE_FIELD: return $page->findField($locator); case self::TYPE_BUTTON: return $page->findButton($locator); case self::TYPE_LINK: return $page->findLink($locator); case self::TYPE_XPATH: return $page->find('xpath', $locator); default: return false; } } /** * Creates directory $dir if it does not exist. * * @param string $dir directory to create */ public function createDir($dir) { if (!file_exists($dir)) { @mkdir($dir); } } /** * Helper function to access members of the P4Context class * * @return P4Context */ protected function getP4Context() { return $this->getMainContext()->getSubContext('p4'); } }