FeatureMinkContext.php #1

  • //
  • guest/
  • thomas_gray/
  • jambox/
  • main/
  • swarm/
  • tests/
  • behat/
  • features/
  • bootstrap/
  • FeatureMinkContext.php
  • View
  • Commits
  • Open Download .zip Download (12 KB)
<?php
/**
 * Perforce Swarm
 *
 * @copyright   2014 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */

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<expectedUrl>[^"]+)" page$/
     * @Then /^(?:|I )wait until url "(?P<expectedUrl>[^"]+)" 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<selector>[^"]+)" with value "(?P<locator>[^"]+)" 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');
    }
}
# Change User Description Committed
#1 18730 Liz Lam clean up code and move things around