2012-11-09 17:34:24 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace SilverStripe\BehatExtension\Context;
|
|
|
|
|
|
|
|
use Behat\Behat\Context\ClosuredContextInterface,
|
|
|
|
Behat\Behat\Context\TranslatedContextInterface,
|
|
|
|
Behat\Behat\Context\BehatContext,
|
|
|
|
Behat\Behat\Context\Step,
|
|
|
|
Behat\Behat\Event\StepEvent,
|
|
|
|
Behat\Behat\Exception\PendingException;
|
|
|
|
use Behat\Mink\Driver\Selenium2Driver;
|
|
|
|
use Behat\Gherkin\Node\PyStringNode,
|
|
|
|
Behat\Gherkin\Node\TableNode;
|
|
|
|
|
|
|
|
// PHPUnit
|
|
|
|
require_once 'PHPUnit/Autoload.php';
|
|
|
|
require_once 'PHPUnit/Framework/Assert/Functions.php';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* BasicContext
|
|
|
|
*
|
|
|
|
* Context used to define generic steps like following anchors or pressing buttons.
|
|
|
|
* Handles timeouts.
|
|
|
|
* Handles redirections.
|
|
|
|
* Handles AJAX enabled links, buttons and forms - jQuery is assumed.
|
|
|
|
*/
|
|
|
|
class BasicContext extends BehatContext
|
|
|
|
{
|
|
|
|
protected $context;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes context.
|
|
|
|
* Every scenario gets it's own context object.
|
|
|
|
*
|
|
|
|
* @param array $parameters context parameters (set them up through behat.yml)
|
|
|
|
*/
|
|
|
|
public function __construct(array $parameters)
|
|
|
|
{
|
|
|
|
// Initialize your context here
|
|
|
|
$this->context = $parameters;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get Mink session from MinkContext
|
|
|
|
*/
|
|
|
|
public function getSession($name = null)
|
|
|
|
{
|
|
|
|
return $this->getMainContext()->getSession($name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @AfterStep ~@modal
|
|
|
|
*
|
|
|
|
* Excluding scenarios with @modal tag is required,
|
|
|
|
* because modal dialogs stop any JS interaction
|
|
|
|
*/
|
|
|
|
public function appendErrorHandlerBeforeStep(StepEvent $event)
|
|
|
|
{
|
|
|
|
$javascript = <<<JS
|
|
|
|
window.onerror = function(msg) {
|
|
|
|
var body = document.getElementsByTagName('body')[0];
|
|
|
|
body.setAttribute('data-jserrors', '[captured JavaScript error] ' + msg);
|
|
|
|
}
|
|
|
|
if ('undefined' !== typeof window.jQuery) {
|
|
|
|
window.jQuery('body').ajaxError(function(event, jqxhr, settings, exception) {
|
|
|
|
if ('abort' === exception) return;
|
|
|
|
window.onerror(event.type + ': ' + settings.type + ' ' + settings.url + ' ' + exception + ' ' + jqxhr.responseText);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
JS;
|
|
|
|
|
|
|
|
$this->getSession()->executeScript($javascript);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @AfterStep ~@modal
|
|
|
|
*
|
|
|
|
* Excluding scenarios with @modal tag is required,
|
|
|
|
* because modal dialogs stop any JS interaction
|
|
|
|
*/
|
|
|
|
public function readErrorHandlerAfterStep(StepEvent $event)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
|
|
|
$jserrors = $page->find('xpath', '//body[@data-jserrors]');
|
|
|
|
if (null !== $jserrors) {
|
|
|
|
$this->takeScreenshot($event);
|
|
|
|
file_put_contents('php://stderr', $jserrors->getAttribute('data-jserrors') . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
$javascript = <<<JS
|
|
|
|
(function() {
|
|
|
|
var body = document.getElementsByTagName('body')[0];
|
|
|
|
body.removeAttribute('data-jserrors');
|
|
|
|
})();
|
|
|
|
JS;
|
|
|
|
|
|
|
|
$this->getSession()->executeScript($javascript);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook into jQuery ajaxStart, ajaxSuccess and ajaxComplete events.
|
|
|
|
* Prepare __ajaxStatus() functions and attach them to these handlers.
|
|
|
|
* Event handlers are removed after one run.
|
|
|
|
*
|
|
|
|
* @BeforeStep
|
|
|
|
*/
|
|
|
|
public function handleAjaxBeforeStep(StepEvent $event)
|
|
|
|
{
|
2012-11-14 00:29:20 +01:00
|
|
|
$ajax_enabled_steps = $this->getMainContext()->getAjaxSteps();
|
2012-11-09 17:34:24 +01:00
|
|
|
$ajax_enabled_steps = implode('|', array_filter($ajax_enabled_steps));
|
|
|
|
|
|
|
|
if (empty($ajax_enabled_steps) || !preg_match('/(' . $ajax_enabled_steps . ')/i', $event->getStep()->getText())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$javascript = <<<JS
|
|
|
|
if ('undefined' !== typeof window.jQuery) {
|
|
|
|
window.jQuery(document).on('ajaxStart.ss.test.behaviour', function(){
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'waiting';
|
|
|
|
};
|
|
|
|
});
|
|
|
|
window.jQuery(document).on('ajaxComplete.ss.test.behaviour', function(e, jqXHR){
|
|
|
|
if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'no ajax';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
window.jQuery(document).on('ajaxSuccess.ss.test.behaviour', function(e, jqXHR){
|
|
|
|
if (null === jqXHR.getResponseHeader('X-ControllerURL')) {
|
|
|
|
window.__ajaxStatus = function() {
|
|
|
|
return 'success';
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
JS;
|
|
|
|
$this->getSession()->executeScript($javascript);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wait for the __ajaxStatus()to return anything but 'waiting'.
|
|
|
|
* Don't wait longer than 5 seconds.
|
|
|
|
*
|
|
|
|
* Don't unregister handler if we're dealing with modal windows
|
|
|
|
*
|
|
|
|
* @AfterStep ~@modal
|
|
|
|
*/
|
|
|
|
public function handleAjaxAfterStep(StepEvent $event)
|
|
|
|
{
|
2012-11-14 00:29:20 +01:00
|
|
|
$ajax_enabled_steps = $this->getMainContext()->getAjaxSteps();
|
2012-11-09 17:34:24 +01:00
|
|
|
$ajax_enabled_steps = implode('|', array_filter($ajax_enabled_steps));
|
|
|
|
|
|
|
|
if (empty($ajax_enabled_steps) || !preg_match('/(' . $ajax_enabled_steps . ')/i', $event->getStep()->getText())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->handleAjaxTimeout();
|
|
|
|
|
|
|
|
$javascript = <<<JS
|
|
|
|
if ('undefined' !== typeof window.jQuery) {
|
|
|
|
window.jQuery(document).off('ajaxStart.ss.test.behaviour');
|
|
|
|
window.jQuery(document).off('ajaxComplete.ss.test.behaviour');
|
|
|
|
window.jQuery(document).off('ajaxSuccess.ss.test.behaviour');
|
|
|
|
}
|
|
|
|
JS;
|
|
|
|
$this->getSession()->executeScript($javascript);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function handleAjaxTimeout()
|
|
|
|
{
|
2012-11-14 00:29:20 +01:00
|
|
|
$timeoutMs = $this->getMainContext()->getAjaxTimeout();
|
|
|
|
|
2012-11-09 20:30:56 +01:00
|
|
|
// Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks
|
2012-11-14 00:29:20 +01:00
|
|
|
$this->getSession()->wait($timeoutMs,
|
2012-11-09 17:34:24 +01:00
|
|
|
"(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'"
|
|
|
|
);
|
|
|
|
|
|
|
|
// wait additional 100ms to allow DOM to update
|
|
|
|
$this->getSession()->wait(100);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Take screenshot when step fails.
|
|
|
|
* Works only with Selenium2Driver.
|
|
|
|
*
|
|
|
|
* @AfterStep
|
|
|
|
*/
|
|
|
|
public function takeScreenshotAfterFailedStep(StepEvent $event)
|
|
|
|
{
|
|
|
|
if (4 === $event->getResult()) {
|
|
|
|
$this->takeScreenshot($event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function takeScreenshot(StepEvent $event) {
|
|
|
|
$driver = $this->getSession()->getDriver();
|
|
|
|
// quit silently when unsupported
|
|
|
|
if (!($driver instanceof Selenium2Driver)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$parent = $event->getLogicalParent();
|
|
|
|
$feature = $parent->getFeature();
|
|
|
|
$step = $event->getStep();
|
|
|
|
$screenshot_path = null;
|
|
|
|
|
2012-11-14 00:29:20 +01:00
|
|
|
$path = $this->getMainContext()->getScreenshotPath();
|
|
|
|
if(!$path) return; // quit silently when path is not set
|
2012-11-09 17:34:24 +01:00
|
|
|
|
2012-11-14 00:29:20 +01:00
|
|
|
$path = realpath($path);
|
|
|
|
if (!$path) {
|
|
|
|
\Filesystem::makeFolder($this->context['screenshot_path']);
|
|
|
|
$path = realpath($this->context['screenshot_path']);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!file_exists($path)) {
|
2012-11-09 17:34:24 +01:00
|
|
|
file_put_contents('php://stderr', sprintf('"%s" is not valid directory and failed to create it' . PHP_EOL, $this->context['screenshot_path']));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-14 00:29:20 +01:00
|
|
|
if (file_exists($path) && !is_dir($path)) {
|
2012-11-09 17:34:24 +01:00
|
|
|
file_put_contents('php://stderr', sprintf('"%s" is not valid directory' . PHP_EOL, $this->context['screenshot_path']));
|
|
|
|
return;
|
|
|
|
}
|
2012-11-14 00:29:20 +01:00
|
|
|
if (file_exists($path) && !is_writable($path)) {
|
|
|
|
file_put_contents('php://stderr', sprintf('"%s" directory is not writable' . PHP_EOL, $path));
|
2012-11-09 17:34:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-14 00:29:20 +01:00
|
|
|
$path = sprintf('%s/%s_%d.png', $path, basename($feature->getFile()), $step->getLine());
|
2012-11-09 17:34:24 +01:00
|
|
|
$screenshot = $driver->wdSession->screenshot();
|
2012-11-14 00:29:20 +01:00
|
|
|
file_put_contents($path, base64_decode($screenshot));
|
|
|
|
file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path));
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Then /^I should be redirected to "([^"]+)"/
|
|
|
|
*/
|
|
|
|
public function stepIShouldBeRedirectedTo($url)
|
|
|
|
{
|
|
|
|
if ($this->getMainContext()->canIntercept()) {
|
|
|
|
$client = $this->getSession()->getDriver()->getClient();
|
|
|
|
$client->followRedirects(true);
|
|
|
|
$client->followRedirect();
|
|
|
|
|
|
|
|
$url = $this->getMainContext()->joinUrlParts($this->context['base_url'], $url);
|
|
|
|
|
|
|
|
assertTrue($this->getMainContext()->isCurrentUrlSimilarTo($url), sprintf('Current URL is not %s', $url));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-11-09 20:30:56 +01:00
|
|
|
* @Given /^I wait for ([\d\.]+) seconds$/
|
2012-11-09 17:34:24 +01:00
|
|
|
*/
|
2012-11-09 20:30:56 +01:00
|
|
|
public function stepIWaitFor($secs)
|
2012-11-09 17:34:24 +01:00
|
|
|
{
|
2012-11-09 20:30:56 +01:00
|
|
|
$this->getSession()->wait((float)$secs*1000);
|
2012-11-09 17:34:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-11-09 20:30:56 +01:00
|
|
|
* @Given /^I press the "([^"]*)" button$/
|
2012-11-09 17:34:24 +01:00
|
|
|
*/
|
2012-11-09 20:30:56 +01:00
|
|
|
public function stepIPressTheButton($button)
|
2012-11-09 17:34:24 +01:00
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
|
|
|
$button_element = $page->find('named', array('link_or_button', "'$button'"));
|
|
|
|
assertNotNull($button_element, sprintf('%s button not found', $button));
|
|
|
|
|
|
|
|
$button_element->click();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I click "([^"]*)" in the "([^"]*)" element$/
|
|
|
|
*/
|
|
|
|
public function iClickInTheElement($text, $selector)
|
|
|
|
{
|
|
|
|
$page = $this->getSession()->getPage();
|
|
|
|
|
|
|
|
$parent_element = $page->find('css', $selector);
|
|
|
|
assertNotNull($parent_element, sprintf('"%s" element not found', $selector));
|
|
|
|
|
|
|
|
$element = $parent_element->find('xpath', sprintf('//*[count(*)=0 and contains(.,"%s")]', $text));
|
|
|
|
assertNotNull($element, sprintf('"%s" not found', $text));
|
|
|
|
|
|
|
|
$element->click();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I type "([^"]*)" into the dialog$/
|
|
|
|
*/
|
|
|
|
public function iTypeIntoTheDialog($data)
|
|
|
|
{
|
|
|
|
$data = array(
|
|
|
|
'text' => $data,
|
|
|
|
);
|
|
|
|
$this->getSession()->getDriver()->wdSession->postAlert_text($data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I confirm the dialog$/
|
|
|
|
*/
|
|
|
|
public function iConfirmTheDialog()
|
|
|
|
{
|
|
|
|
$this->getSession()->getDriver()->wdSession->accept_alert();
|
|
|
|
$this->handleAjaxTimeout();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^I dismiss the dialog$/
|
|
|
|
*/
|
|
|
|
public function iDismissTheDialog()
|
|
|
|
{
|
|
|
|
$this->getSession()->getDriver()->wdSession->dismiss_alert();
|
|
|
|
$this->handleAjaxTimeout();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @Given /^(I attach the file .*) with HTML5$/
|
|
|
|
*/
|
|
|
|
public function iAttachTheFileTo($step)
|
|
|
|
{
|
|
|
|
$this->getSession()->evaluateScript("jQuery('.ss-uploadfield-editandorganize').show()");
|
|
|
|
$this->getSession()->evaluateScript("jQuery('[name=\"AssetUploadField\"]').css({opacity:1,visibility:'visible',height:'1px',width:'1px'})");
|
|
|
|
$this->getSession()->evaluateScript("jQuery('[name=\"files[]\"]').css({opacity:1,visibility:'visible',height:'1px',width:'1px'})");
|
|
|
|
$this->getSession()->wait(1000);
|
|
|
|
|
|
|
|
return new Step\Given($step);
|
|
|
|
}
|
|
|
|
}
|