* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Behat\MinkExtension\ServiceContainer; use Behat\Behat\Context\ServiceContainer\ContextExtension; use Behat\MinkExtension\ServiceContainer\Driver\BrowserStackFactory; use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; use Behat\MinkExtension\ServiceContainer\Driver\GoutteFactory; use Behat\MinkExtension\ServiceContainer\Driver\SahiFactory; use Behat\MinkExtension\ServiceContainer\Driver\SauceLabsFactory; use Behat\MinkExtension\ServiceContainer\Driver\Selenium2Factory; use Behat\MinkExtension\ServiceContainer\Driver\SeleniumFactory; use Behat\MinkExtension\ServiceContainer\Driver\ZombieFactory; use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension; use Behat\Testwork\ServiceContainer\Exception\ProcessingException; use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface; use Behat\Testwork\ServiceContainer\ExtensionManager; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; /** * Mink extension for Behat class. * * @author Konstantin Kudryashov * @author Christophe Coevoet */ class MinkExtension implements ExtensionInterface { const MINK_ID = 'mink'; const SELECTORS_HANDLER_ID = 'mink.selectors_handler'; const SELECTOR_TAG = 'mink.selector'; /** * @var DriverFactory[] */ private $driverFactories = array(); public function __construct() { $this->registerDriverFactory(new GoutteFactory()); $this->registerDriverFactory(new SahiFactory()); $this->registerDriverFactory(new SeleniumFactory()); $this->registerDriverFactory(new Selenium2Factory()); $this->registerDriverFactory(new SauceLabsFactory()); $this->registerDriverFactory(new BrowserStackFactory()); $this->registerDriverFactory(new ZombieFactory()); } public function registerDriverFactory(DriverFactory $driverFactory) { $this->driverFactories[$driverFactory->getDriverName()] = $driverFactory; } /** * {@inheritDoc} */ public function load(ContainerBuilder $container, array $config) { if (isset($config['mink_loader'])) { $basePath = $container->getParameter('paths.base'); if (file_exists($basePath.DIRECTORY_SEPARATOR.$config['mink_loader'])) { require($basePath.DIRECTORY_SEPARATOR.$config['mink_loader']); } else { require($config['mink_loader']); } } $this->loadMink($container); $this->loadContextInitializer($container); $this->loadSelectorsHandler($container); $this->loadSessions($container, $config); $this->loadSessionsListener($container); if ($config['show_auto']) { $this->loadFailureShowListener($container); } unset($config['sessions']); $container->setParameter('mink.parameters', $config); $container->setParameter('mink.base_url', $config['base_url']); $container->setParameter('mink.browser_name', $config['browser_name']); } /** * {@inheritDoc} */ public function configure(ArrayNodeDefinition $builder) { // Rewrite keys to define a shortcut way without allowing conflicts with real keys $renamedKeys = array_diff( array_keys($this->driverFactories), array('mink_loader', 'base_url', 'files_path', 'show_auto', 'show_cmd', 'show_tmp_dir', 'default_session', 'javascript_session', 'browser_name', 'sessions') ); $builder ->beforeNormalization() ->always() ->then(function ($v) use ($renamedKeys) { foreach ($renamedKeys as $driverType) { if (!array_key_exists($driverType, $v) || isset($v['sessions'][$driverType])) { continue; } $v['sessions'][$driverType][$driverType] = $v[$driverType]; unset($v[$driverType]); } return $v; }) ->end() ->addDefaultsIfNotSet() ->children() ->scalarNode('mink_loader')->defaultNull()->end() ->scalarNode('base_url')->defaultNull()->end() ->scalarNode('files_path')->defaultNull()->end() ->booleanNode('show_auto')->defaultFalse()->end() ->scalarNode('show_cmd')->defaultNull()->end() ->scalarNode('show_tmp_dir')->defaultValue(sys_get_temp_dir())->end() ->scalarNode('default_session')->defaultNull()->info('Defaults to the first non-javascript session if any, or the first session otherwise')->end() ->scalarNode('javascript_session')->defaultNull()->info('Defaults to the first javascript session if any')->end() ->scalarNode('browser_name')->defaultValue('firefox')->end() ->end() ->end(); /** @var ArrayNodeDefinition $sessionsBuilder */ $sessionsBuilder = $builder ->children() ->arrayNode('sessions') ->isRequired() ->requiresAtLeastOneElement() ->useAttributeAsKey('name') ->prototype('array') ; foreach ($this->driverFactories as $factory) { $factoryNode = $sessionsBuilder->children()->arrayNode($factory->getDriverName())->canBeUnset(); $factory->configure($factoryNode); } $sessionsBuilder ->validate() ->ifTrue(function ($v) {return count($v) > 1;}) ->thenInvalid('You cannot set multiple driver types for the same session') ->end() ->validate() ->ifTrue(function ($v) {return count($v) === 0;}) ->thenInvalid('You must set a driver definition for the session.') ->end() ; } /** * {@inheritDoc} */ public function getConfigKey() { return 'mink'; } /** * {@inheritdoc} */ public function initialize(ExtensionManager $extensionManager) { } /** * {@inheritDoc} */ public function process(ContainerBuilder $container) { $this->processSelectors($container); } private function loadMink(ContainerBuilder $container) { $container->setDefinition(self::MINK_ID, new Definition('Behat\Mink\Mink')); } private function loadContextInitializer(ContainerBuilder $container) { $definition = new Definition('Behat\MinkExtension\Context\Initializer\MinkAwareInitializer', array( new Reference(self::MINK_ID), '%mink.parameters%', )); $definition->addTag(ContextExtension::INITIALIZER_TAG, array('priority' => 0)); $container->setDefinition('mink.context_initializer', $definition); } private function loadSelectorsHandler(ContainerBuilder $container) { $container->setDefinition(self::SELECTORS_HANDLER_ID, new Definition('Behat\Mink\Selector\SelectorsHandler')); $cssSelectorDefinition = new Definition('Behat\Mink\Selector\CssSelector'); $cssSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'css')); $container->setDefinition(self::SELECTOR_TAG . '.css', $cssSelectorDefinition); $namedSelectorDefinition = new Definition('Behat\Mink\Selector\NamedSelector'); $namedSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'named')); $container->setDefinition(self::SELECTOR_TAG . '.named', $namedSelectorDefinition); } private function loadSessions(ContainerBuilder $container, array $config) { $defaultSession = $config['default_session']; $javascriptSession = $config['javascript_session']; $javascriptSessions = $nonJavascriptSessions = array(); $minkDefinition = $container->getDefinition(self::MINK_ID); foreach ($config['sessions'] as $name => $session) { $driver = key($session); $factory = $this->driverFactories[$driver]; $definition = new Definition('Behat\Mink\Session', array( $factory->buildDriver($session[$driver]), new Reference(self::SELECTORS_HANDLER_ID), )); $minkDefinition->addMethodCall('registerSession', array($name, $definition)); if ($factory->supportsJavascript()) { $javascriptSessions[] = $name; } else { $nonJavascriptSessions[] = $name; } } if (null === $javascriptSession && !empty($javascriptSessions)) { $javascriptSession = $javascriptSessions[0]; } elseif (null !== $javascriptSession && !in_array($javascriptSession, $javascriptSessions)) { throw new InvalidConfigurationException(sprintf( 'The javascript session must be one of the enabled javascript sessions (%s), but got %s', json_encode($javascriptSessions), $javascriptSession )); } if (null === $defaultSession) { $defaultSession = !empty($nonJavascriptSessions) ? $nonJavascriptSessions[0] : $javascriptSessions[0]; } elseif (!isset($config['sessions'][$defaultSession])) { throw new InvalidConfigurationException(sprintf('The default session must be one of the enabled sessions, but got %s', $defaultSession)); } $container->setParameter('mink.default_session', $defaultSession); $container->setParameter('mink.javascript_session', $javascriptSession); $container->setParameter('mink.available_javascript_sessions', $javascriptSessions); } private function loadSessionsListener(ContainerBuilder $container) { $definition = new Definition('Behat\MinkExtension\Listener\SessionsListener', array( new Reference(self::MINK_ID), '%mink.default_session%', '%mink.javascript_session%', '%mink.available_javascript_sessions%', )); $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0)); $container->setDefinition('mink.listener.sessions', $definition); } private function loadFailureShowListener(ContainerBuilder $container) { $definition = new Definition('Behat\MinkExtension\Listener\FailureShowListener', array( new Reference(self::MINK_ID), '%mink.parameters%', )); $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0)); $container->setDefinition('mink.listener.failure_show', $definition); } private function processSelectors(ContainerBuilder $container) { $handlerDefinition = $container->getDefinition(self::SELECTORS_HANDLER_ID); foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $id => $tags) { foreach ($tags as $tag) { if (!isset($tag['alias'])) { throw new ProcessingException(sprintf( 'All `%s` tags should have an `alias` attribute, but `%s` service has none.', $tag, $id )); } $handlerDefinition->addMethodCall( 'registerSelector', array($tag['alias'], new Reference($id)) ); } } } }