mirror of
https://github.com/silverstripe/silverstripe-behat-extension
synced 2024-10-22 17:05:32 +02:00
ENHANCEMENT Migrated generic contexts from temporary 'behat-tests' module
The CMS specific context classes will move to framework
This commit is contained in:
parent
ee4d8b7381
commit
3ac7e83dae
332
src/SilverStripe/BehatExtension/Context/BasicContext.php
Normal file
332
src/SilverStripe/BehatExtension/Context/BasicContext.php
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
$ajax_enabled_steps = $this->getMainContext()->getAjaxEnabledSteps();
|
||||||
|
$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)
|
||||||
|
{
|
||||||
|
$ajax_enabled_steps = $this->getMainContext()->getAjaxEnabledSteps();
|
||||||
|
$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()
|
||||||
|
{
|
||||||
|
$this->getSession()->wait(5000,
|
||||||
|
"(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;
|
||||||
|
|
||||||
|
if (isset($this->context['screenshot_path'])) {
|
||||||
|
$screenshot_path = realpath($this->context['screenshot_path']);
|
||||||
|
if (!$screenshot_path) {
|
||||||
|
\Filesystem::makeFolder($this->context['screenshot_path']);
|
||||||
|
$screenshot_path = realpath($this->context['screenshot_path']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$screenshot_path) {
|
||||||
|
$screenshot_path = realpath(sys_get_temp_dir());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($screenshot_path)) {
|
||||||
|
file_put_contents('php://stderr', sprintf('"%s" is not valid directory and failed to create it' . PHP_EOL, $this->context['screenshot_path']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($screenshot_path) && !is_dir($screenshot_path)) {
|
||||||
|
file_put_contents('php://stderr', sprintf('"%s" is not valid directory' . PHP_EOL, $this->context['screenshot_path']));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file_exists($screenshot_path) && !is_writable($screenshot_path)) {
|
||||||
|
file_put_contents('php://stderr', sprintf('"%s" directory is not writable' . PHP_EOL, $screenshot_path));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$screenshot_path = sprintf('%s/%s_%d.png', $screenshot_path, basename($feature->getFile()), $step->getLine());
|
||||||
|
$screenshot = $driver->wdSession->screenshot();
|
||||||
|
file_put_contents($screenshot_path, base64_decode($screenshot));
|
||||||
|
file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $screenshot_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I wait for "(\d+)"$/
|
||||||
|
*/
|
||||||
|
public function stepIWaitFor($ms)
|
||||||
|
{
|
||||||
|
$this->getSession()->wait($ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I press "([^"]*)" button$/
|
||||||
|
*/
|
||||||
|
public function stepIPressButton($button)
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
150
src/SilverStripe/BehatExtension/Context/LoginContext.php
Normal file
150
src/SilverStripe/BehatExtension/Context/LoginContext.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\BehatExtension\Context;
|
||||||
|
|
||||||
|
use Behat\Behat\Context\ClosuredContextInterface,
|
||||||
|
Behat\Behat\Context\TranslatedContextInterface,
|
||||||
|
Behat\Behat\Context\BehatContext,
|
||||||
|
Behat\Behat\Context\Step,
|
||||||
|
Behat\Behat\Exception\PendingException;
|
||||||
|
use Behat\Gherkin\Node\PyStringNode,
|
||||||
|
Behat\Gherkin\Node\TableNode;
|
||||||
|
|
||||||
|
// PHPUnit
|
||||||
|
require_once 'PHPUnit/Autoload.php';
|
||||||
|
require_once 'PHPUnit/Framework/Assert/Functions.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginContext
|
||||||
|
*
|
||||||
|
* Context used to define steps related to login and logout functionality
|
||||||
|
*/
|
||||||
|
class LoginContext extends BehatContext
|
||||||
|
{
|
||||||
|
protected $context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for logInWithPermission()
|
||||||
|
*/
|
||||||
|
protected $cache_generatedMembers = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I am logged in$/
|
||||||
|
*/
|
||||||
|
public function stepIAmLoggedIn()
|
||||||
|
{
|
||||||
|
$admin_url = $this->getMainContext()->joinUrlParts($this->getMainContext()->getBaseUrl(), $this->context['admin_url']);
|
||||||
|
$login_url = $this->getMainContext()->joinUrlParts($this->getMainContext()->getBaseUrl(), $this->context['login_url']);
|
||||||
|
|
||||||
|
$this->getSession()->visit($admin_url);
|
||||||
|
|
||||||
|
if (0 == strpos($this->getSession()->getCurrentUrl(), $login_url)) {
|
||||||
|
$this->stepILogInWith('admin', 'password');
|
||||||
|
assertStringStartsWith($admin_url, $this->getSession()->getCurrentUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I am logged in with "([^"]*)" permissions$/
|
||||||
|
*/
|
||||||
|
function iAmLoggedInWithPermissions($permCode)
|
||||||
|
{
|
||||||
|
if (!isset($this->cache_generatedMembers[$permCode])) {
|
||||||
|
$group = \Injector::inst()->create('Group');
|
||||||
|
$group->Title = "$permCode group";
|
||||||
|
$group->write();
|
||||||
|
|
||||||
|
$permission = \Injector::inst()->create('Permission');
|
||||||
|
$permission->Code = $permCode;
|
||||||
|
$permission->write();
|
||||||
|
$group->Permissions()->add($permission);
|
||||||
|
|
||||||
|
$member = \DataObject::get_one('Member', sprintf('"Email" = \'%s\'', "$permCode@example.org"));
|
||||||
|
if (!$member) {
|
||||||
|
$member = \Injector::inst()->create('Member');
|
||||||
|
}
|
||||||
|
|
||||||
|
$member->FirstName = $permCode;
|
||||||
|
$member->Surname = "User";
|
||||||
|
$member->Email = "$permCode@example.org";
|
||||||
|
$member->changePassword('secret');
|
||||||
|
$member->write();
|
||||||
|
$group->Members()->add($member);
|
||||||
|
|
||||||
|
$this->cache_generatedMembers[$permCode] = $member;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->cache_generatedMembers[$permCode]->logIn();
|
||||||
|
return new Step\Given(sprintf('I log in with "%s" and "%s"', "$permCode@example.org", 'secret'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I am not logged in$/
|
||||||
|
*/
|
||||||
|
public function stepIAmNotLoggedIn()
|
||||||
|
{
|
||||||
|
$this->getSession()->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @When /^I log in with "([^"]*)" and "([^"]*)"$/
|
||||||
|
*/
|
||||||
|
public function stepILogInWith($email, $password)
|
||||||
|
{
|
||||||
|
$login_url = $this->getMainContext()->joinUrlParts($this->getMainContext()->getBaseUrl(), $this->context['login_url']);
|
||||||
|
|
||||||
|
$this->getSession()->visit($login_url);
|
||||||
|
|
||||||
|
$page = $this->getSession()->getPage();
|
||||||
|
|
||||||
|
$email_field = $page->find('css', '[name=Email]');
|
||||||
|
$password_field = $page->find('css', '[name=Password]');
|
||||||
|
$submit_button = $page->find('css', '[type=submit]');
|
||||||
|
$email_field->setValue($email);
|
||||||
|
$password_field->setValue($password);
|
||||||
|
$submit_button->press();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^I should see a log-in form$/
|
||||||
|
*/
|
||||||
|
public function stepIShouldSeeALogInForm()
|
||||||
|
{
|
||||||
|
$page = $this->getSession()->getPage();
|
||||||
|
|
||||||
|
$login_form = $page->find('css', '#MemberLoginForm_LoginForm');
|
||||||
|
assertNotNull($login_form, 'I should see a log-in form');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Then /^I will see a bad log-in message$/
|
||||||
|
*/
|
||||||
|
public function stepIWillSeeABadLogInMessage()
|
||||||
|
{
|
||||||
|
$page = $this->getSession()->getPage();
|
||||||
|
|
||||||
|
$bad_message = $page->find('css', '.message.bad');
|
||||||
|
|
||||||
|
assertNotNull($bad_message, 'Bad message not found.');
|
||||||
|
}
|
||||||
|
}
|
444
src/SilverStripe/BehatExtension/Context/SilverStripeContext.php
Normal file
444
src/SilverStripe/BehatExtension/Context/SilverStripeContext.php
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\BehatExtension\Context;
|
||||||
|
|
||||||
|
use Behat\Behat\Context\Step,
|
||||||
|
Behat\Behat\Event\FeatureEvent,
|
||||||
|
Behat\Behat\Event\ScenarioEvent,
|
||||||
|
Behat\Behat\Event\SuiteEvent;
|
||||||
|
use Behat\Gherkin\Node\PyStringNode;
|
||||||
|
use Behat\MinkExtension\Context\MinkContext;
|
||||||
|
use Behat\Mink\Driver\GoutteDriver,
|
||||||
|
Behat\Mink\Driver\Selenium2Driver,
|
||||||
|
Behat\Mink\Exception\UnsupportedDriverActionException;
|
||||||
|
|
||||||
|
use SilverStripe\BehatExtension\Context\SilverStripeAwareContextInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
// Mink etc.
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SilverStripeContext
|
||||||
|
*
|
||||||
|
* Generic context wrapper used as a base for Behat FeatureContext.
|
||||||
|
*/
|
||||||
|
class SilverStripeContext extends MinkContext implements SilverStripeAwareContextInterface
|
||||||
|
{
|
||||||
|
private $database_name;
|
||||||
|
private $ajax_steps;
|
||||||
|
|
||||||
|
protected $context;
|
||||||
|
protected $fixtures;
|
||||||
|
protected $fixtures_lazy;
|
||||||
|
protected $files_path;
|
||||||
|
protected $created_files_paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDatabase($database_name)
|
||||||
|
{
|
||||||
|
$this->database_name = $database_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAjaxEnabledSteps($ajax_steps)
|
||||||
|
{
|
||||||
|
if (empty($ajax_steps)) {
|
||||||
|
$ajax_steps = array();
|
||||||
|
}
|
||||||
|
$this->ajax_steps = $ajax_steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAjaxEnabledSteps()
|
||||||
|
{
|
||||||
|
return $this->ajax_steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFixture($data_object)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($data_object, $this->fixtures)) {
|
||||||
|
throw new \OutOfBoundsException(sprintf('Data object `%s` does not exist!', $data_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->fixtures[$data_object];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFixtures()
|
||||||
|
{
|
||||||
|
return $this->fixtures;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeScenario
|
||||||
|
*/
|
||||||
|
public function before(ScenarioEvent $event)
|
||||||
|
{
|
||||||
|
if (!isset($this->database_name)) {
|
||||||
|
throw new \LogicException('Context\'s $database_name has to be set when implementing SilverStripeAwareContextInterface.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$setdb_url = $this->joinUrlParts($this->getBaseUrl(), '/dev/tests/setdb');
|
||||||
|
$setdb_url = sprintf('%s?database=%s', $setdb_url, $this->database_name);
|
||||||
|
$this->getSession()->visit($setdb_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeScenario @database-defaults
|
||||||
|
*/
|
||||||
|
public function beforeDatabaseDefaults(ScenarioEvent $event)
|
||||||
|
{
|
||||||
|
\SapphireTest::empty_temp_db();
|
||||||
|
global $databaseConfig;
|
||||||
|
\DB::connect($databaseConfig);
|
||||||
|
$dataClasses = \ClassInfo::subclassesFor('DataObject');
|
||||||
|
array_shift($dataClasses);
|
||||||
|
foreach ($dataClasses as $dataClass) {
|
||||||
|
\singleton($dataClass)->requireDefaultRecords();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @AfterScenario @database-defaults
|
||||||
|
*/
|
||||||
|
public function afterDatabaseDefaults(ScenarioEvent $event)
|
||||||
|
{
|
||||||
|
\SapphireTest::empty_temp_db();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @AfterScenario @assets
|
||||||
|
*/
|
||||||
|
public function afterResetAssets(ScenarioEvent $event)
|
||||||
|
{
|
||||||
|
if (is_array($this->created_files_paths)) {
|
||||||
|
$created_files = array_reverse($this->created_files_paths);
|
||||||
|
foreach ($created_files as $path) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
\Filesystem::removeFolder($path);
|
||||||
|
} else {
|
||||||
|
@unlink($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\SapphireTest::empty_temp_db();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^there are the following ([^\s]*) records$/
|
||||||
|
*/
|
||||||
|
public function thereAreTheFollowingRecords($data_object, PyStringNode $string)
|
||||||
|
{
|
||||||
|
if (!is_array($this->fixtures)) {
|
||||||
|
$this->fixtures = array();
|
||||||
|
}
|
||||||
|
if (!is_array($this->fixtures_lazy)) {
|
||||||
|
$this->fixtures_lazy = array();
|
||||||
|
}
|
||||||
|
if (!isset($this->files_path)) {
|
||||||
|
$this->files_path = realpath($this->getMinkParameter('files_path'));
|
||||||
|
}
|
||||||
|
if (!is_array($this->created_files_paths)) {
|
||||||
|
$this->created_files_paths = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($data_object, $this->fixtures)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('Data object `%s` already exists!', $data_object));
|
||||||
|
}
|
||||||
|
|
||||||
|
$fixture = array_merge(array($data_object . ':'), $string->getLines());
|
||||||
|
$fixture = implode("\n ", $fixture);
|
||||||
|
|
||||||
|
if ('Folder' === $data_object) {
|
||||||
|
$this->prepareTestAssetsDirectories($fixture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('File' === $data_object) {
|
||||||
|
$this->prepareTestAssetsFiles($fixture);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fixtures_lazy = array($data_object => array());
|
||||||
|
if (preg_match('/=>(\w+)/', $fixture)) {
|
||||||
|
$fixture_content = Yaml::parse($fixture);
|
||||||
|
foreach ($fixture_content[$data_object] as $identifier => &$fields) {
|
||||||
|
foreach ($fields as $field_val) {
|
||||||
|
if (substr($field_val, 0, 2) == '=>') {
|
||||||
|
$fixtures_lazy[$data_object][$identifier] = $fixture_content[$data_object][$identifier];
|
||||||
|
unset($fixture_content[$data_object][$identifier]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fixture = Yaml::dump($fixture_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we're dealing with split fixtures and can't join them, replace references by hand
|
||||||
|
// if (preg_match('/=>(\w+)\.([\w.]+)/', $fixture, $matches)) {
|
||||||
|
// if ($matches[1] !== $data_object) {
|
||||||
|
// $fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
$fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
|
||||||
|
// Save fixtures into database
|
||||||
|
$this->fixtures[$data_object] = new \YamlFixture($fixture);
|
||||||
|
$model = \DataModel::inst();
|
||||||
|
$this->fixtures[$data_object]->saveIntoDatabase($model);
|
||||||
|
// Lazy load fixtures into database
|
||||||
|
// Loop is required for nested lazy fixtures
|
||||||
|
foreach ($fixtures_lazy[$data_object] as $identifier => $fields) {
|
||||||
|
$fixture = array(
|
||||||
|
$data_object => array(
|
||||||
|
$identifier => $fields,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
$fixture = Yaml::dump($fixture);
|
||||||
|
$fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
|
||||||
|
$this->fixtures_lazy[$data_object][$identifier] = new \YamlFixture($fixture);
|
||||||
|
$this->fixtures_lazy[$data_object][$identifier]->saveIntoDatabase($model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareTestAssetsDirectories($fixture)
|
||||||
|
{
|
||||||
|
$folders = Yaml::parse($fixture);
|
||||||
|
foreach ($folders['Folder'] as $fields) {
|
||||||
|
foreach ($fields as $field => $value) {
|
||||||
|
if ('Filename' === $field) {
|
||||||
|
if (0 === strpos($value, 'assets/')) {
|
||||||
|
$value = substr($value, strlen('assets/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$folder_path = ASSETS_PATH . DIRECTORY_SEPARATOR . $value;
|
||||||
|
if (file_exists($folder_path) && !is_dir($folder_path)) {
|
||||||
|
throw new \Exception(sprintf('`%s` already exists and is not a directory', $this->files_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
\Filesystem::makeFolder($folder_path);
|
||||||
|
$this->created_files_paths[] = $folder_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepareTestAssetsFiles($fixture)
|
||||||
|
{
|
||||||
|
$files = Yaml::parse($fixture);
|
||||||
|
foreach ($files['File'] as $fields) {
|
||||||
|
foreach ($fields as $field => $value) {
|
||||||
|
if ('Filename' === $field) {
|
||||||
|
if (0 === strpos($value, 'assets/')) {
|
||||||
|
$value = substr($value, strlen('assets/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_path = $this->files_path . DIRECTORY_SEPARATOR . basename($value);
|
||||||
|
if (!file_exists($file_path) || !is_file($file_path)) {
|
||||||
|
throw new \Exception(sprintf('`%s` does not exist or is not a file', $this->files_path));
|
||||||
|
}
|
||||||
|
$asset_path = ASSETS_PATH . DIRECTORY_SEPARATOR . $value;
|
||||||
|
if (file_exists($asset_path) && !is_file($asset_path)) {
|
||||||
|
throw new \Exception(sprintf('`%s` already exists and is not a file', $this->files_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($asset_path)) {
|
||||||
|
if (@copy($file_path, $asset_path)) {
|
||||||
|
$this->created_files_paths[] = $asset_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceFixtureReferences($references)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($references[1], $this->fixtures)) {
|
||||||
|
throw new \OutOfBoundsException(sprintf('Data object `%s` does not exist!', $references[1]));
|
||||||
|
}
|
||||||
|
return $this->idFromFixture($references[1], $references[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function idFromFixture($class_name, $identifier)
|
||||||
|
{
|
||||||
|
if (false !== ($id = $this->fixtures[$class_name]->idFromFixture($class_name, $identifier))) {
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
if (isset($this->fixtures_lazy[$class_name], $this->fixtures_lazy[$class_name][$identifier]) &&
|
||||||
|
false !== ($id = $this->fixtures_lazy[$class_name][$identifier]->idFromFixture($class_name, $identifier))) {
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException(sprintf('`%s` identifier in Data object `%s` does not exist!', $identifier, $class_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses given URL and returns its components
|
||||||
|
*
|
||||||
|
* @param $url
|
||||||
|
* @return array|mixed Parsed URL
|
||||||
|
*/
|
||||||
|
public function parseUrl($url)
|
||||||
|
{
|
||||||
|
$url = parse_url($url);
|
||||||
|
$url['vars'] = array();
|
||||||
|
if (!isset($url['fragment'])) {
|
||||||
|
$url['fragment'] = null;
|
||||||
|
}
|
||||||
|
if (isset($url['query'])) {
|
||||||
|
parse_str($url['query'], $url['vars']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether current URL is close enough to the given URL.
|
||||||
|
* Unless specified in $url, get vars will be ignored
|
||||||
|
* Unless specified in $url, fragment identifiers will be ignored
|
||||||
|
*
|
||||||
|
* @param $url string URL to compare to current URL
|
||||||
|
* @return boolean Returns true if the current URL is close enough to the given URL, false otherwise.
|
||||||
|
*/
|
||||||
|
public function isCurrentUrlSimilarTo($url)
|
||||||
|
{
|
||||||
|
$current = $this->parseUrl($this->getSession()->getCurrentUrl());
|
||||||
|
$test = $this->parseUrl($url);
|
||||||
|
|
||||||
|
if ($current['path'] !== $test['path']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($test['fragment']) && $current['fragment'] !== $test['fragment']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($test['vars'] as $name => $value) {
|
||||||
|
if (!isset($current['vars'][$name]) || $current['vars'][$name] !== $value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns base URL parameter set in MinkExtension.
|
||||||
|
* It simplifies configuration by allowing to specify this parameter
|
||||||
|
* once but makes code dependent on MinkExtension.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBaseUrl()
|
||||||
|
{
|
||||||
|
return $this->getMinkParameter('base_url') ?: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins URL parts into an URL using forward slash.
|
||||||
|
* Forward slash usages are normalised to one between parts.
|
||||||
|
* This method takes variable number of parameters.
|
||||||
|
*
|
||||||
|
* @param $...
|
||||||
|
* @return string
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function joinUrlParts()
|
||||||
|
{
|
||||||
|
if (0 === func_num_args()) {
|
||||||
|
throw new \InvalidArgumentException('Need at least one argument');
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = func_get_args();
|
||||||
|
$trim_slashes = function(&$part) {
|
||||||
|
$part = trim($part, '/');
|
||||||
|
};
|
||||||
|
array_walk($parts, $trim_slashes);
|
||||||
|
|
||||||
|
return implode('/', $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canIntercept()
|
||||||
|
{
|
||||||
|
$driver = $this->getSession()->getDriver();
|
||||||
|
if ($driver instanceof GoutteDriver) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($driver instanceof Selenium2Driver) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedDriverActionException('You need to tag the scenario with "@mink:goutte" or "@mink:symfony". Intercepting the redirections is not supported by %s', $driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^(.*) without redirection$/
|
||||||
|
*/
|
||||||
|
public function theRedirectionsAreIntercepted($step)
|
||||||
|
{
|
||||||
|
if ($this->canIntercept()) {
|
||||||
|
$this->getSession()->getDriver()->getClient()->followRedirects(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Step\Given($step);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^((?:I )fill in =>(.+?) for "([^"]*)")$/
|
||||||
|
*/
|
||||||
|
public function iFillInFor($step, $reference, $field)
|
||||||
|
{
|
||||||
|
if (false === strpos($reference, '.')) {
|
||||||
|
throw new \Exception('Fixture reference should be in following format: =>ClassName.identifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
list($class_name, $identifier) = explode('.', $reference);
|
||||||
|
$id = $this->idFromFixture($class_name, $identifier);
|
||||||
|
//$step = preg_replace('#=>(.+?) for "([^"]*)"#', '"'.$id.'" for "'.$field.'"', $step);
|
||||||
|
|
||||||
|
// below is not working, because Selenium can't interact with hidden inputs
|
||||||
|
// return new Step\Given($step);
|
||||||
|
|
||||||
|
// TODO: investigate how to simplify this and make universal
|
||||||
|
$javascript = <<<JAVASCRIPT
|
||||||
|
if ('undefined' !== typeof window.jQuery) {
|
||||||
|
window.jQuery('input[name="$field"]').val($id);
|
||||||
|
}
|
||||||
|
JAVASCRIPT;
|
||||||
|
$this->getSession()->executeScript($javascript);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Given /^((?:I )fill in "([^"]*)" with =>(.+))$/
|
||||||
|
*/
|
||||||
|
public function iFillInWith($step, $field, $reference)
|
||||||
|
{
|
||||||
|
if (false === strpos($reference, '.')) {
|
||||||
|
throw new \Exception('Fixture reference should be in following format: =>ClassName.identifier');
|
||||||
|
}
|
||||||
|
|
||||||
|
list($class_name, $identifier) = explode('.', $reference);
|
||||||
|
$id = $this->idFromFixture($class_name, $identifier);
|
||||||
|
//$step = preg_replace('#"([^"]*)" with =>(.+)#', '"'.$field.'" with "'.$id.'"', $step);
|
||||||
|
|
||||||
|
// below is not working, because Selenium can't interact with hidden inputs
|
||||||
|
// return new Step\Given($step);
|
||||||
|
|
||||||
|
// TODO: investigate how to simplify this and make universal
|
||||||
|
$javascript = <<<JAVASCRIPT
|
||||||
|
if ('undefined' !== typeof window.jQuery) {
|
||||||
|
window.jQuery('input[name="$field"]').val($id);
|
||||||
|
}
|
||||||
|
JAVASCRIPT;
|
||||||
|
$this->getSession()->executeScript($javascript);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user