mirror of
https://github.com/silverstripe/silverstripe-behat-extension
synced 2024-10-22 17:05:32 +02:00
Initializer and custom namespace support
This commit is contained in:
parent
6b1b4d3d94
commit
5ca904ae12
29
README.md
29
README.md
@ -131,7 +131,34 @@ Example: behat.yml
|
||||
selenium2:
|
||||
browser: firefox
|
||||
|
||||
### Available Step Definitions
|
||||
## Module Initialization
|
||||
|
||||
You're all set to start writing features now! Simply create `*.feature` files
|
||||
anywhere in your codebase, and run them as shown above. We recommend the folder
|
||||
structure of `tests/behat/features`, since its consistent with the common location
|
||||
of SilverStripe's PHPUnit tests.
|
||||
|
||||
Behat tests rely on a `FeatureContext` class which contains step definitions,
|
||||
and can be composed of other subcontexts, e.g. for SilverStripe-specific CMS steps
|
||||
(details on [behat.org](http://docs.behat.org/quick_intro.html#the-context-class-featurecontext)).
|
||||
Since step definitions are quite domain specific, its likely that you'll need your own context.
|
||||
The SilverStripe Behat extension provides an initializer script which generates a template
|
||||
in the recommended folder structure:
|
||||
|
||||
vendor/bin/behat --init @mymodule
|
||||
|
||||
You'll now have a class located in `mymodule/tests/behat/features/bootstrap/Context/FeatureContext.php`,
|
||||
as well as a folder for your features with `mymodule/tests/behat/features`.
|
||||
The class is namespaced, and defaults to the module name. You can customize this:
|
||||
|
||||
vendor/bin/behat --namespace='MyVendor\MyModule' --init @mymodule
|
||||
|
||||
In this case, you'll need to pass in the namespace when running the features as well
|
||||
(at least until SilverStripe modules allow declaring a namespace).
|
||||
|
||||
vendor/bin/behat --namespace='MyVendor\MyModule' @mymodule
|
||||
|
||||
## Available Step Definitions
|
||||
|
||||
The extension comes with several `BehatContext` subclasses come with some extra step defintions.
|
||||
Some of them are just helpful in general website testing, other's are specific to SilverStripe.
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\BehatExtension\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder,
|
||||
Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Loads SilverStripe core. Required to initialize autoloading.
|
||||
*/
|
||||
class CoreInitializationPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* Loads kernel file.
|
||||
*
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
// Connect to database and build manifest
|
||||
$frameworkPath = $container->getParameter('behat.silverstripe_extension.framework_path');
|
||||
$_GET['flush'] = 1;
|
||||
require_once $frameworkPath . '/core/Core.php';
|
||||
\TestRunner::use_test_manifest();
|
||||
unset($_GET['flush']);
|
||||
|
||||
// Remove the error handler so that PHPUnit can add its own
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\BehatExtension\Console\Processor;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface,
|
||||
Symfony\Component\Console\Command\Command,
|
||||
Symfony\Component\Console\Input\InputArgument,
|
||||
Symfony\Component\Console\Input\InputInterface,
|
||||
Symfony\Component\Console\Output\OutputInterface,
|
||||
Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
use Behat\Behat\Console\Processor\InitProcessor as BaseProcessor;
|
||||
|
||||
/**
|
||||
* Initializes a project for Behat usage, creating context files.
|
||||
*/
|
||||
class InitProcessor extends BaseProcessor
|
||||
{
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container Container instance
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Command $command
|
||||
*/
|
||||
public function configure(Command $command)
|
||||
{
|
||||
parent::configure($command);
|
||||
|
||||
$command->addOption('--namespace', null, InputOption::VALUE_OPTIONAL,
|
||||
"Optional namespace for FeatureContext, defaults to <foldername>\\Test\\Behaviour.\n"
|
||||
);
|
||||
}
|
||||
|
||||
public function process(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// throw exception if no features argument provided
|
||||
if (!$input->getArgument('features') && $input->getOption('init')) {
|
||||
throw new \InvalidArgumentException('Provide features argument in order to init suite.');
|
||||
}
|
||||
|
||||
// initialize bundle structure and exit
|
||||
if ($input->getOption('init')) {
|
||||
$this->initBundleDirectoryStructure($input, $output);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits bundle directory structure
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
protected function initBundleDirectoryStructure(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Bootstrap SS so we can use module listing
|
||||
$frameworkPath = $this->container->getParameter('behat.silverstripe_extension.framework_path');
|
||||
$_GET['flush'] = 1;
|
||||
require_once $frameworkPath . '/core/Core.php';
|
||||
unset($_GET['flush']);
|
||||
|
||||
$featuresPath = $input->getArgument('features');
|
||||
if(!$featuresPath) {
|
||||
throw new \InvalidArgumentException('Please specify a module name (e.g. "@mymodule")');
|
||||
}
|
||||
|
||||
// Can't use 'behat.paths.base' since that's locked at this point to base folder (not module)
|
||||
$pathSuffix = $this->container->getParameter('behat.silverstripe_extension.context.path_suffix');
|
||||
$currentModuleName = null;
|
||||
$modules = \SS_ClassLoader::instance()->getManifest()->getModules();
|
||||
$currentModuleName = $this->container->getParameter('behat.silverstripe_extension.module');
|
||||
|
||||
// get module from short notation if path starts from @
|
||||
if (preg_match('/^\@([^\/\\\\]+)(.*)$/', $featuresPath, $matches)) {
|
||||
$currentModuleName = $matches[1];
|
||||
// TODO Replace with proper module loader once AJShort's changes are merged into core
|
||||
if (!array_key_exists($currentModuleName, $modules)) {
|
||||
throw new \InvalidArgumentException(sprintf('Module "%s" not found', $currentModuleName));
|
||||
}
|
||||
$currentModulePath = $modules[$currentModuleName];
|
||||
}
|
||||
|
||||
if (!$currentModuleName) {
|
||||
throw new \InvalidArgumentException('Can not find module to initialize suite.');
|
||||
}
|
||||
|
||||
// TODO Retrieve from module definition once that's implemented
|
||||
if($input->getOption('namespace')) {
|
||||
$namespace = $input->getOption('namespace');
|
||||
} else {
|
||||
$namespace = ucfirst($currentModuleName);
|
||||
}
|
||||
$namespace .= '\\' . $this->container->getParameter('behat.silverstripe_extension.context.namespace_suffix');
|
||||
|
||||
$featuresPath = rtrim($currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix,DIRECTORY_SEPARATOR);
|
||||
$basePath = $this->container->getParameter('behat.paths.base').DIRECTORY_SEPARATOR;
|
||||
$bootstrapPath = $featuresPath.DIRECTORY_SEPARATOR.'bootstrap';
|
||||
$contextPath = $bootstrapPath.DIRECTORY_SEPARATOR.'Context';
|
||||
|
||||
if (!is_dir($featuresPath)) {
|
||||
mkdir($featuresPath, 0777, true);
|
||||
mkdir($bootstrapPath, 0777, true);
|
||||
// touch($bootstrapPath.DIRECTORY_SEPARATOR.'_manifest_exclude');
|
||||
$output->writeln(
|
||||
'<info>+d</info> ' .
|
||||
str_replace($basePath, '', realpath($featuresPath)) .
|
||||
' <comment>- place your *.feature files here</comment>'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_dir($contextPath)) {
|
||||
mkdir($contextPath, 0777, true);
|
||||
|
||||
$className = $this->container->getParameter('behat.context.class');
|
||||
file_put_contents(
|
||||
$contextPath . DIRECTORY_SEPARATOR . $className . '.php',
|
||||
strtr($this->getFeatureContextSkelet(), array(
|
||||
'%NAMESPACE%' => $namespace
|
||||
))
|
||||
);
|
||||
|
||||
$output->writeln(
|
||||
'<info>+f</info> ' .
|
||||
str_replace($basePath, '', realpath($contextPath)) . DIRECTORY_SEPARATOR .
|
||||
'FeatureContext.php <comment>- place your feature related code here</comment>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getFeatureContextSkelet()
|
||||
{
|
||||
return <<<'PHP'
|
||||
<?php
|
||||
|
||||
namespace %NAMESPACE%;
|
||||
|
||||
use SilverStripe\BehatExtension\Context\SilverStripeContext,
|
||||
SilverStripe\BehatExtension\Context\BasicContext,
|
||||
SilverStripe\BehatExtension\Context\LoginContext,
|
||||
SilverStripe\BehatExtension\Context\FixtureContext,
|
||||
SilverStripe\Framework\Test\Behaviour\CmsFormsContext,
|
||||
SilverStripe\Framework\Test\Behaviour\CmsUiContext,
|
||||
SilverStripe\Cms\Test\Behaviour;
|
||||
|
||||
// PHPUnit
|
||||
require_once 'PHPUnit/Autoload.php';
|
||||
require_once 'PHPUnit/Framework/Assert/Functions.php';
|
||||
|
||||
/**
|
||||
* Features context
|
||||
*
|
||||
* Context automatically loaded by Behat.
|
||||
* Uses subcontexts to extend functionality.
|
||||
*/
|
||||
class FeatureContext extends SilverStripeContext {
|
||||
|
||||
/**
|
||||
* @var FixtureFactory
|
||||
*/
|
||||
protected $fixtureFactory;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
parent::__construct($parameters);
|
||||
|
||||
$this->useContext('BasicContext', new BasicContext($parameters));
|
||||
$this->useContext('LoginContext', new LoginContext($parameters));
|
||||
$this->useContext('CmsFormsContext', new CmsFormsContext($parameters));
|
||||
$this->useContext('CmsUiContext', new CmsUiContext($parameters));
|
||||
|
||||
$fixtureContext = new FixtureContext($parameters);
|
||||
$fixtureContext->setFixtureFactory($this->getFixtureFactory());
|
||||
$this->useContext('FixtureContext', $fixtureContext);
|
||||
|
||||
// Use blueprints to set user name from identifier
|
||||
$factory = $fixtureContext->getFixtureFactory();
|
||||
$blueprint = \Injector::inst()->create('FixtureBlueprint', 'Member');
|
||||
$blueprint->addCallback('beforeCreate', function($identifier, &$data, &$fixtures) {
|
||||
if(!isset($data['FirstName'])) $data['FirstName'] = $identifier;
|
||||
});
|
||||
$factory->define('Member', $blueprint);
|
||||
|
||||
// Auto-publish pages
|
||||
foreach(\ClassInfo::subclassesFor('SiteTree') as $id => $class) {
|
||||
$blueprint = \Injector::inst()->create('FixtureBlueprint', $class);
|
||||
$blueprint->addCallback('afterCreate', function($obj, $identifier, &$data, &$fixtures) {
|
||||
$obj->publish('Stage', 'Live');
|
||||
});
|
||||
$factory->define($class, $blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
public function setMinkParameters(array $parameters) {
|
||||
parent::setMinkParameters($parameters);
|
||||
|
||||
if(isset($parameters['files_path'])) {
|
||||
$this->getSubcontext('FixtureContext')->setFilesPath($parameters['files_path']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FixtureFactory
|
||||
*/
|
||||
public function getFixtureFactory() {
|
||||
if(!$this->fixtureFactory) {
|
||||
$this->fixtureFactory = \Injector::inst()->create('BehatFixtureFactory');
|
||||
}
|
||||
|
||||
return $this->fixtureFactory;
|
||||
}
|
||||
|
||||
public function setFixtureFactory(FixtureFactory $factory) {
|
||||
$this->fixtureFactory = $factory;
|
||||
}
|
||||
|
||||
//
|
||||
// Place your definition and hook methods here:
|
||||
//
|
||||
// /**
|
||||
// * @Given /^I have done something with "([^"]*)"$/
|
||||
// */
|
||||
// public function iHaveDoneSomethingWith($argument) {
|
||||
// $container = $this->kernel->getContainer();
|
||||
// $container->get('some_service')->doSomethingWith($argument);
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
PHP;
|
||||
}
|
||||
}
|
@ -53,13 +53,9 @@ class LocatorProcessor extends BaseProcessor
|
||||
*/
|
||||
public function process(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// Bootstrap SS so we can use module listing
|
||||
$frameworkPath = $this->container->getParameter('behat.silverstripe_extension.framework_path');
|
||||
$_GET['flush'] = 1;
|
||||
require_once $frameworkPath . '/core/Core.php';
|
||||
unset($_GET['flush']);
|
||||
|
||||
$featuresPath = $input->getArgument('features');
|
||||
|
||||
// Can't use 'behat.paths.base' since that's locked at this point to base folder (not module)
|
||||
$pathSuffix = $this->container->getParameter('behat.silverstripe_extension.context.path_suffix');
|
||||
|
||||
$currentModuleName = null;
|
||||
@ -84,23 +80,28 @@ class LocatorProcessor extends BaseProcessor
|
||||
foreach ($modules as $moduleName => $modulePath) {
|
||||
if (false !== strpos($path, realpath($modulePath))) {
|
||||
$currentModuleName = $moduleName;
|
||||
$currentModulePath = realpath($modulePath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$featuresPath = $currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix.DIRECTORY_SEPARATOR.$featuresPath;
|
||||
// if module is configured for profile and feature provided
|
||||
} elseif ($currentModuleName && $featuresPath) {
|
||||
$currentModulePath = $modules[$currentModuleName];
|
||||
$featuresPath = $currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix.DIRECTORY_SEPARATOR.$featuresPath;
|
||||
}
|
||||
|
||||
if($input->getOption('namespace')) {
|
||||
$namespace = $input->getOption('namespace');
|
||||
} else {
|
||||
$namespace = ucfirst($currentModuleName);
|
||||
}
|
||||
|
||||
if ($currentModuleName) {
|
||||
$this->container
|
||||
->get('behat.silverstripe_extension.context.class_guesser')
|
||||
->setModuleNamespace(ucfirst($currentModuleName));
|
||||
}
|
||||
|
||||
if (!$featuresPath) {
|
||||
$featuresPath = $this->container->getParameter('behat.paths.features');
|
||||
// TODO Improve once modules can declare their own namespaces consistently
|
||||
->setNamespaceBase($namespace);
|
||||
}
|
||||
|
||||
$this->container
|
||||
|
@ -10,17 +10,19 @@ use Behat\Behat\Context\ClassGuesser\ClassGuesserInterface;
|
||||
*/
|
||||
class ModuleContextClassGuesser implements ClassGuesserInterface
|
||||
{
|
||||
private $classSuffix;
|
||||
private $namespace;
|
||||
private $namespaceSuffix;
|
||||
private $namespaceBase;
|
||||
private $contextClass;
|
||||
|
||||
/**
|
||||
* Initializes guesser.
|
||||
*
|
||||
* @param string $classSuffix
|
||||
* @param string $namespaceSuffix
|
||||
*/
|
||||
public function __construct($classSuffix = 'Test\\Behaviour\\FeatureContext')
|
||||
public function __construct($namespaceSuffix, $contextClass)
|
||||
{
|
||||
$this->classSuffix = $classSuffix;
|
||||
$this->namespaceSuffix = $namespaceSuffix;
|
||||
$this->contextClass = $contextClass;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,9 +30,10 @@ class ModuleContextClassGuesser implements ClassGuesserInterface
|
||||
*
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function setModuleNamespace($namespace)
|
||||
public function setNamespaceBase($namespaceBase)
|
||||
{
|
||||
$this->namespace = $namespace;
|
||||
$this->namespaceBase = $namespaceBase;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,12 +44,12 @@ class ModuleContextClassGuesser implements ClassGuesserInterface
|
||||
public function guess()
|
||||
{
|
||||
// Try fully qualified namespace
|
||||
if (class_exists($class = $this->namespace.'\\'.$this->classSuffix)) {
|
||||
if (class_exists($class = $this->namespaceBase.'\\'.$this->namespaceSuffix.'\\'.$this->contextClass)) {
|
||||
return $class;
|
||||
}
|
||||
// Fall back to namespace with SilverStripe prefix
|
||||
// TODO Remove once core has namespace capabilities for modules
|
||||
if (class_exists($class = 'SilverStripe\\'.$this->namespace.'\\'.$this->classSuffix)) {
|
||||
if (class_exists($class = 'SilverStripe\\'.$this->namespaceBase.'\\'.$this->namespaceSuffix.'\\'.$this->contextClass)) {
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,9 @@ class Extension implements ExtensionInterface
|
||||
*/
|
||||
public function getCompilerPasses()
|
||||
{
|
||||
return array();
|
||||
return array(
|
||||
new Compiler\CoreInitializationPass()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,7 +2,8 @@ parameters:
|
||||
behat.silverstripe_extension.context.initializer.class: SilverStripe\BehatExtension\Context\Initializer\SilverStripeAwareInitializer
|
||||
behat.silverstripe_extension.context.class_guesser.class: SilverStripe\BehatExtension\Context\ClassGuesser\ModuleContextClassGuesser
|
||||
behat.console.processor.locator.class: SilverStripe\BehatExtension\Console\Processor\LocatorProcessor
|
||||
behat.silverstripe_extension.context.class_suffix: Test\Behaviour\FeatureContext
|
||||
behat.console.processor.init.class: SilverStripe\BehatExtension\Console\Processor\InitProcessor
|
||||
behat.silverstripe_extension.context.namespace_suffix: Test\Behaviour
|
||||
behat.silverstripe_extension.framework_path: framework
|
||||
behat.silverstripe_extension.ajax_steps: ~
|
||||
behat.silverstripe_extension.ajax_timeout: ~
|
||||
@ -27,6 +28,7 @@ services:
|
||||
behat.silverstripe_extension.context.class_guesser:
|
||||
class: %behat.silverstripe_extension.context.class_guesser.class%
|
||||
arguments:
|
||||
- %behat.silverstripe_extension.context.class_suffix%
|
||||
- %behat.silverstripe_extension.context.namespace_suffix%
|
||||
- %behat.context.class%
|
||||
tags:
|
||||
- { name: behat.context.class_guesser, priority: 10 }
|
Loading…
Reference in New Issue
Block a user