silverstripe-behat-extension/src/Extension.php

229 lines
8.4 KiB
PHP

<?php
namespace SilverStripe\BehatExtension;
use Behat\Testwork\Call\ServiceContainer\CallExtension;
use Behat\Testwork\Cli\ServiceContainer\CliExtension;
use Behat\Testwork\Suite\Cli\InitializationController;
use Behat\Testwork\Suite\ServiceContainer\SuiteExtension;
use SilverStripe\BehatExtension\Controllers\ModuleInitialisationController;
use SilverStripe\BehatExtension\Controllers\ModuleSuiteLocator;
use SilverStripe\BehatExtension\Utility\RetryableCallHandler;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Behat\Testwork\ServiceContainer\ExtensionManager;
use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface;
use RuntimeException;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Behat\Behat\Tester\ServiceContainer\TesterExtension;
use SilverStripe\BehatExtension\Utility\RerunTotalStatistics;
use SilverStripe\BehatExtension\Utility\RerunRuntimeSuiteTester;
/*
* This file is part of the SilverStripe\BehatExtension
*
* (c) Michał Ochman <ochman.d.michal@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
/**
* SilverStripe extension for Behat class.
*
* Configured by adding `SilverStripe\BehatExtension\Extension` to your behat.yml
*
* @author Michał Ochman <ochman.d.michal@gmail.com>
*/
class Extension implements ExtensionInterface
{
/**
* Extension configuration ID.
*/
const SILVERSTRIPE_ID = 'silverstripe_extension';
/**
* {@inheritDoc}
*/
public function getConfigKey()
{
return self::SILVERSTRIPE_ID;
}
public function initialize(ExtensionManager $extensionManager)
{
// Find PHPUnit assertion implementations
$found = false;
$options = [
BASE_PATH . '/vendor/sminnee/phpunit/src/Framework/Assert/Functions.php',
BASE_PATH . '/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php'
];
foreach ($options as $file) {
if (file_exists($file ?? '')) {
require_once $file;
$found = true;
break;
}
}
if (!$found) {
throw new RuntimeException('Could not find PHPUnit installation');
}
}
public function load(ContainerBuilder $container, array $config)
{
// Load yml config
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('silverstripe.yml');
// Add CLI substitutions
$this->loadSuiteLocator($container);
$this->loadBootstrapController($container);
$this->loadCallHandlers($container, $config['error_reporting'], $config['retry_seconds']);
// Set various paths
$container->setParameter('silverstripe_extension.admin_url', $config['admin_url']);
$container->setParameter('silverstripe_extension.login_url', $config['login_url']);
$container->setParameter('silverstripe_extension.screenshot_path', $config['screenshot_path']);
$container->setParameter('silverstripe_extension.ajax_timeout', $config['ajax_timeout']);
if (isset($config['ajax_steps'])) {
$container->setParameter('silverstripe_extension.ajax_steps', $config['ajax_steps']);
}
if (isset($config['region_map'])) {
$container->setParameter('silverstripe_extension.region_map', $config['region_map']);
}
$container->setParameter('silverstripe_extension.bootstrap_file', $config['bootstrap_file']);
$container->setParameter('silverstripe_extension.is_ci', $config['is_ci']);
// When running in CI, behat scenarios will occasionally sporadically fail
// Replaces services with custom implementations that will rerun failed features
// Note that features rather than scenarios need to be rerun to ensure that
// everything is setup and torn down correctly and that "Background" bits of
// feature fits are rerun
if ($config['is_ci']) {
$definition = new Definition(RerunRuntimeSuiteTester::class, array(
new Reference(TesterExtension::SPECIFICATION_TESTER_ID)
));
$container->setDefinition(TesterExtension::SUITE_TESTER_ID, $definition);
$definition = new Definition(RerunTotalStatistics::class);
$container->setDefinition('output.pretty.statistics', $definition);
}
}
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container)
{
$corePass = new Compiler\CoreInitializationPass();
$corePass->process($container);
}
public function configure(ArrayNodeDefinition $builder)
{
$builder->
children()->
scalarNode('screenshot_path')->
defaultNull()->
end()->
arrayNode('region_map')->
useAttributeAsKey('key')->
prototype('variable')->end()->
end()->
scalarNode('admin_url')->
defaultValue('/admin/')->
end()->
scalarNode('login_url')->
defaultValue('/Security/login')->
end()->
scalarNode('ajax_timeout')->
defaultValue(5000)->
end()->
scalarNode('bootstrap_file')->
defaultNull()->
end()->
scalarNode('error_reporting')->
info('Call executor will catch exceptions matching this level')->
defaultValue(E_ALL | E_STRICT)->
end()->
scalarNode('retry_seconds')->
info('Number of seconds that @retry tags will retry for')->
defaultValue(2)->
end()->
scalarNode('is_ci')->
defaultValue(false)->
end()->
arrayNode('ajax_steps')->
defaultValue(array(
'go to',
'follow',
'press',
'click',
'submit'
))->
prototype('scalar')->
end()->
end()->
end();
}
/**
* Loads module suite locator.
* This is responsible for bootstrapping the module config
* for running tests.
*
* @param ContainerBuilder $container
*/
protected function loadSuiteLocator(ContainerBuilder $container)
{
$definition = new Definition(ModuleSuiteLocator::class, [
$container,
new Reference(SuiteExtension::REGISTRY_ID)
]);
$definition->addTag(CliExtension::CONTROLLER_TAG, ['priority' => 9999]);
$container->setDefinition(CliExtension::CONTROLLER_TAG . '.suite', $definition);
}
/**
* Loads suite bootstrap controller.
* This is responsible for invoking --init commands for modules.
* Replaces the core behat InitializationController
*
* @see InitializationController
* @param ContainerBuilder $container
*/
protected function loadBootstrapController(ContainerBuilder $container)
{
$definition = new Definition(ModuleInitialisationController::class, [
$container,
new Reference(SuiteExtension::REGISTRY_ID),
new Reference(SuiteExtension::BOOTSTRAPPER_ID)
]);
$definition->addTag(CliExtension::CONTROLLER_TAG, ['priority' => 900]);
$container->setDefinition(CliExtension::CONTROLLER_TAG . '.initialization', $definition);
}
/**
* Shivs in custom call handler
*
* @param ContainerBuilder $container
* @param integer $errorReporting
* @param int $retrySeconds
*/
protected function loadCallHandlers(ContainerBuilder $container, $errorReporting, $retrySeconds)
{
$definition = new Definition(RetryableCallHandler::class, [$errorReporting, $retrySeconds]);
$definition->addTag(CallExtension::CALL_HANDLER_TAG, ['priority' => 50]);
$container->setDefinition(CallExtension::CALL_HANDLER_TAG . '.runtime', $definition);
}
}