--- /dev/null
+<?php
+
+/*
+ * This file is part of the Behat MinkExtension.
+ * (c) Konstantin Kudryashov <ever.zet@gmail.com>
+ *
+ * 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 <ever.zet@gmail.com>
+ * @author Christophe Coevoet <stof@notk.org>
+ */
+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))
+ );
+ }
+ }
+ }
+}