NEW Reduced boilerplate configuration

- Moved all extension-specific conf into its own configuration namespace
- Described configuration via PHP, and added default values
- Removed boilerplate config from README
- Made screenshot_path optional
- Configurable ajax_timeout settings

Note: The DI system plus the initializer+context combo requires insane amounts of code duplication,
will need to be looked at more closely (very little docs on that level of Behat extension available).
This commit is contained in:
Ingo Schommer 2012-11-14 00:29:20 +01:00
parent 66a07442cf
commit 38a27d2a50
8 changed files with 254 additions and 65 deletions

View File

@ -102,46 +102,37 @@ Example: behat.yml
default:
context:
parameters:
admin_url: /admin/
login_url: /Security/login
screenshot_path: %behat.paths.features%/screenshots/
class: SilverStripe\MyModule\Test\Behaviour\FeatureContext
extensions:
SilverStripe\BehatExtension\Extension:
# Assumes path relative to vendor/silverstripe/silverstripe-behat
framework_path: ../../../framework/
ajax_steps:
- go to
- follow
- press
- click
- submit
SilverStripe\BehatExtension\Extension: ~
Behat\MinkExtension\Extension:
# Adjust this to your local environment
base_url: http://localhost/
files_path: %behat.paths.features%/files/
default_session: selenium2
javascript_session: selenium2
goutte: ~
selenium2:
browser: firefox
Here's an overview of the non-stndard settings we've added.
You'll need to customize at least the `framework_path` and `base_url` setting.
You'll need to customize at least the `base_url` setting to match the URL where
the tested SilverStripe instance is hosted locally.
Overview of settings (all in the `extensions.SilverStripe\BehatExtension\Extension` path):
* `default.context.parameters.screenshot_path`: Used to store screenshot of a last known state
of a failed step. It defaults to whatever is returned by PHP's `sys_get_temp_dir()`.
Screenshot names within that directory consist of feature file filename and line
number that failed.
* `default.extensions.SilverStripe\BehatExtension\Extension.framework_path`: Path to the SilverStripe Framework folder. It supports both absolute and relative (to `behat.yml` file) paths.
* `default.extensions.Behat\MinkExtension\Extension.base_url`: You will probably need to change the base URL that is used during the test process.
* `framework_path`: Path to the SilverStripe Framework folder. It supports both absolute and relative (to `behat.yml` file) paths.
* `extensions.Behat\MinkExtension\Extension.base_url`: You will probably need to change the base URL that is used during the test process.
It is used every time you use relative URLs in your feature descriptions.
It will also be used by [file to URL mapping](http://doc.silverstripe.org/framework/en/topics/commandline#configuration) in `SilverStripeExtension`.
* `default.extensions.Behat\MinkExtension\Extension.files_path`: Change to support file uploads in your tests. Currently only absolute paths are supported.
* `default.extensions.SilverStripe\BehatExtension\Extension.ajax_steps`: Because SilverStripe uses AJAX requests quite extensively, we had to invent a way
* `extensions.Behat\MinkExtension\Extension.files_path`: Change to support file uploads in your tests. Currently only absolute paths are supported.
* `ajax_steps`: Because SilverStripe uses AJAX requests quite extensively, we had to invent a way
to deal with them more efficiently and less verbose than just
Optional `ajax_steps` is used to match steps defined there so they can be "caught" by
[special AJAX handlers](http://blog.scur.pl/2012/06/ajax-callback-support-behat-mink/) that tweak the delays. You can either use a pipe delimited string or a list of substrings that match step definition.
* `ajax_timeout`: Milliseconds after which an Ajax request is regarded as timed out,
and the script continues with its assertions to avoid a deadlock (Default: 5000).
* `screenshot_path`: Used to store screenshot of a last known state
of a failed step. It defaults to whatever is returned by PHP's `sys_get_temp_dir()`.
Screenshot names within that directory consist of feature file filename and line
number that failed.
### Additional profiles

View File

@ -107,7 +107,7 @@ JS;
*/
public function handleAjaxBeforeStep(StepEvent $event)
{
$ajax_enabled_steps = $this->getMainContext()->getAjaxEnabledSteps();
$ajax_enabled_steps = $this->getMainContext()->getAjaxSteps();
$ajax_enabled_steps = implode('|', array_filter($ajax_enabled_steps));
if (empty($ajax_enabled_steps) || !preg_match('/(' . $ajax_enabled_steps . ')/i', $event->getStep()->getText())) {
@ -150,7 +150,7 @@ JS;
*/
public function handleAjaxAfterStep(StepEvent $event)
{
$ajax_enabled_steps = $this->getMainContext()->getAjaxEnabledSteps();
$ajax_enabled_steps = $this->getMainContext()->getAjaxSteps();
$ajax_enabled_steps = implode('|', array_filter($ajax_enabled_steps));
if (empty($ajax_enabled_steps) || !preg_match('/(' . $ajax_enabled_steps . ')/i', $event->getStep()->getText())) {
@ -171,8 +171,10 @@ JS;
public function handleAjaxTimeout()
{
$timeoutMs = $this->getMainContext()->getAjaxTimeout();
// Wait for an ajax request to complete, but only for a maximum of 5 seconds to avoid deadlocks
$this->getSession()->wait(5000,
$this->getSession()->wait($timeoutMs,
"(typeof window.__ajaxStatus !== 'undefined' ? window.__ajaxStatus() : 'no ajax') !== 'waiting'"
);
@ -205,35 +207,33 @@ JS;
$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());
}
$path = $this->getMainContext()->getScreenshotPath();
if(!$path) return; // quit silently when path is not set
if (!file_exists($screenshot_path)) {
$path = realpath($path);
if (!$path) {
\Filesystem::makeFolder($this->context['screenshot_path']);
$path = realpath($this->context['screenshot_path']);
}
if (!file_exists($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)) {
if (file_exists($path) && !is_dir($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));
if (file_exists($path) && !is_writable($path)) {
file_put_contents('php://stderr', sprintf('"%s" directory is not writable' . PHP_EOL, $path));
return;
}
$screenshot_path = sprintf('%s/%s_%d.png', $screenshot_path, basename($feature->getFile()), $step->getLine());
$path = sprintf('%s/%s_%d.png', $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));
file_put_contents($path, base64_decode($screenshot));
file_put_contents('php://stderr', sprintf('Saving screenshot into %s' . PHP_EOL, $path));
}
/**

View File

@ -24,17 +24,41 @@ use SilverStripe\BehatExtension\Context\SilverStripeAwareContextInterface;
*/
class SilverStripeAwareInitializer implements InitializerInterface
{
private $database_name;
private $ajax_steps;
/**
* @var Array
*/
protected $ajaxSteps;
/**
* @var Int Timeout in milliseconds
*/
protected $ajaxTimeout;
/**
* @var String {@link see SilverStripeContext}
*/
protected $adminUrl;
/**
* @var String {@link see SilverStripeContext}
*/
protected $loginUrl;
/**
* @var String {@link see SilverStripeContext}
*/
protected $screenshotPath;
/**
* Initializes initializer.
*/
public function __construct($framework_path, $framework_host, $ajax_steps)
public function __construct($framework_path, $framework_host)
{
$this->bootstrap($framework_path, $framework_host);
$this->database_name = $this->initializeTempDb();
$this->ajax_steps = $ajax_steps;
}
public function __destruct()
@ -62,7 +86,61 @@ class SilverStripeAwareInitializer implements InitializerInterface
public function initialize(ContextInterface $context)
{
$context->setDatabase($this->database_name);
$context->setAjaxEnabledSteps($this->ajax_steps);
$context->setAjaxSteps($this->ajaxSteps);
$context->setAjaxTimeout($this->ajaxTimeout);
$context->setScreenshotPath($this->screenshotPath);
$context->setAdminUrl($this->adminUrl);
$context->setLoginUrl($this->loginUrl);
}
public function setAjaxSteps($ajaxSteps)
{
if($ajaxSteps) $this->ajaxSteps = $ajaxSteps;
}
public function getAjaxSteps()
{
return $this->ajaxSteps;
}
public function setAjaxTimeout($ajaxTimeout)
{
$this->ajaxTimeout = $ajaxTimeout;
}
public function getAjaxTimeout()
{
return $this->ajaxTimeout;
}
public function setAdminUrl($adminUrl)
{
$this->adminUrl = $adminUrl;
}
public function getAdminUrl()
{
return $this->adminUrl;
}
public function setLoginUrl($loginUrl)
{
$this->loginUrl = $loginUrl;
}
public function getLoginUrl()
{
return $this->loginUrl;
}
public function setScreenshotPath($screenshotPath)
{
$this->screenshotPath = $screenshotPath;
}
public function getScreenshotPath()
{
return $this->screenshotPath;
}
protected function bootstrap($framework_path, $framework_host)

View File

@ -53,8 +53,9 @@ class LoginContext extends BehatContext
*/
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']);
$c = $this->getMainContext();
$admin_url = $c->joinUrlParts($c->getBaseUrl(), $c->getAdminUrl());
$login_url = $c->joinUrlParts($c->getBaseUrl(), $c->getLoginUrl());
$this->getSession()->visit($admin_url);
@ -111,7 +112,8 @@ class LoginContext extends BehatContext
*/
public function stepILogInWith($email, $password)
{
$login_url = $this->getMainContext()->joinUrlParts($this->getMainContext()->getBaseUrl(), $this->context['login_url']);
$c = $this->getMainContext();
$login_url = $c->joinUrlParts($c->getBaseUrl(), $c->getLoginUrl());
$this->getSession()->visit($login_url);

View File

@ -30,5 +30,5 @@ interface SilverStripeAwareContextInterface
*
* @param array $ajax_steps Array of step name parts to match
*/
public function setAjaxEnabledSteps($ajax_steps);
public function setAjaxSteps($ajax_steps);
}

View File

@ -26,8 +26,37 @@ require_once 'vendor/autoload.php';
*/
class SilverStripeContext extends MinkContext implements SilverStripeAwareContextInterface
{
private $database_name;
private $ajax_steps;
protected $database_name;
/**
* @var Array Partial string match for step names
* that are considered to trigger Ajax request in the CMS,
* and hence need special timeout handling.
* @see \SilverStripe\BehatExtension\Context\BasicContext->handleAjaxBeforeStep().
*/
protected $ajaxSteps;
/**
* @var Int Timeout in milliseconds, after which the interface assumes
* that an Ajax request has timed out, and continues with assertions.
*/
protected $ajaxTimeout;
/**
* @var String Relative URL to the SilverStripe admin interface.
*/
protected $adminUrl;
/**
* @var String Relative URL to the SilverStripe login form.
*/
protected $loginUrl;
/**
* @var String Relative path to a writeable folder where screenshots can be stored.
* If set to NULL, no screenshots will be stored.
*/
protected $screenshotPath;
protected $context;
protected $fixtures;
@ -52,17 +81,54 @@ class SilverStripeContext extends MinkContext implements SilverStripeAwareContex
$this->database_name = $database_name;
}
public function setAjaxEnabledSteps($ajax_steps)
public function setAjaxSteps($ajaxSteps)
{
if (empty($ajax_steps)) {
$ajax_steps = array();
}
$this->ajax_steps = $ajax_steps;
if($ajaxSteps) $this->ajaxSteps = $ajaxSteps;
}
public function getAjaxEnabledSteps()
public function getAjaxSteps()
{
return $this->ajax_steps;
return $this->ajaxSteps;
}
public function setAjaxTimeout($ajaxTimeout)
{
$this->ajaxTimeout = $ajaxTimeout;
}
public function getAjaxTimeout()
{
return $this->ajaxTimeout;
}
public function setAdminUrl($adminUrl)
{
$this->adminUrl = $adminUrl;
}
public function getAdminUrl()
{
return $this->adminUrl;
}
public function setLoginUrl($loginUrl)
{
$this->loginUrl = $loginUrl;
}
public function getLoginUrl()
{
return $this->loginUrl;
}
public function setScreenshotPath($screenshotPath)
{
$this->screenshotPath = $screenshotPath;
}
public function getScreenshotPath()
{
return $this->screenshotPath;
}
public function getFixture($data_object)

View File

@ -4,9 +4,10 @@ namespace SilverStripe\BehatExtension;
use Symfony\Component\Config\FileLocator,
Symfony\Component\DependencyInjection\ContainerBuilder,
Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
Symfony\Component\DependencyInjection\Loader\YamlFileLoader,
Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Behat\Behat\Extension\Extension as BaseExtension;
use Behat\Behat\Extension\ExtensionInterface;
/*
* This file is part of the SilverStripe\BehatExtension
@ -22,7 +23,7 @@ use Behat\Behat\Extension\Extension as BaseExtension;
*
* @author Michał Ochman <ochman.d.michal@gmail.com>
*/
class Extension extends BaseExtension
class Extension implements ExtensionInterface
{
/**
* Loads a specific configuration.
@ -50,6 +51,10 @@ class Extension extends BaseExtension
}
$container->setParameter('behat.silverstripe_extension.framework_path', $config['framework_path']);
$container->setParameter('behat.silverstripe_extension.admin_url', $config['admin_url']);
$container->setParameter('behat.silverstripe_extension.login_url', $config['login_url']);
$container->setParameter('behat.silverstripe_extension.screenshot_path', $config['screenshot_path']);
$container->setParameter('behat.silverstripe_extension.ajax_timeout', $config['ajax_timeout']);
if (isset($config['ajax_steps'])) {
$container->setParameter('behat.silverstripe_extension.ajax_steps', $config['ajax_steps']);
}
@ -66,4 +71,42 @@ class Extension extends BaseExtension
new Compiler\MinkExtensionBaseUrlPass(),
);
}
/**
* Setups configuration for current extension.
*
* @param ArrayNodeDefinition $builder
*/
function getConfig(ArrayNodeDefinition $builder)
{
$builder->
children()->
scalarNode('framework_path')->
defaultValue('../../../framework')->
end()->
scalarNode('screenshot_path')->
defaultNull()->
end()->
scalarNode('admin_url')->
defaultValue('/admin/')->
end()->
scalarNode('login_url')->
defaultValue('/Security/login')->
end()->
scalarNode('ajax_timeout')->
defaultValue(5000)->
end()->
arrayNode('ajax_steps')->
defaultValue(array(
'go to',
'follow',
'press',
'click',
'submit'
))->
prototype('scalar')->
end()->
end()->
end();
}
}

View File

@ -2,12 +2,21 @@ parameters:
behat.silverstripe_extension.context.initializer.class: SilverStripe\BehatExtension\Context\Initializer\SilverStripeAwareInitializer
behat.silverstripe_extension.framework_path: ~
behat.silverstripe_extension.ajax_steps: ~
behat.silverstripe_extension.ajax_timeout: ~
behat.silverstripe_extension.admin_url: ~
behat.silverstripe_extension.login_url: ~
behat.silverstripe_extension.screenshot_path: ~
services:
behat.silverstripe_extension.context.initializer:
class: %behat.silverstripe_extension.context.initializer.class%
arguments:
- %behat.silverstripe_extension.framework_path%
- %behat.silverstripe_extension.framework_host%
- %behat.silverstripe_extension.ajax_steps%
calls:
- [setAjaxSteps, [%behat.silverstripe_extension.ajax_steps%]]
- [setAjaxTimeout, [%behat.silverstripe_extension.ajax_timeout%]]
- [setAdminUrl, [%behat.silverstripe_extension.admin_url%]]
- [setLoginUrl, [%behat.silverstripe_extension.login_url%]]
- [setScreenshotPath, [%behat.silverstripe_extension.screenshot_path%]]
tags:
- { name: behat.context.initializer }