4 * This file is part of the Behat MinkExtension.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
11 namespace Behat\MinkExtension\ServiceContainer;
13 use Behat\Behat\Context\ServiceContainer\ContextExtension;
14 use Behat\MinkExtension\ServiceContainer\Driver\BrowserStackFactory;
15 use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory;
16 use Behat\MinkExtension\ServiceContainer\Driver\GoutteFactory;
17 use Behat\MinkExtension\ServiceContainer\Driver\SahiFactory;
18 use Behat\MinkExtension\ServiceContainer\Driver\SauceLabsFactory;
19 use Behat\MinkExtension\ServiceContainer\Driver\Selenium2Factory;
20 use Behat\MinkExtension\ServiceContainer\Driver\SeleniumFactory;
21 use Behat\MinkExtension\ServiceContainer\Driver\ZombieFactory;
22 use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension;
23 use Behat\Testwork\ServiceContainer\Exception\ProcessingException;
24 use Behat\Testwork\ServiceContainer\Extension as ExtensionInterface;
25 use Behat\Testwork\ServiceContainer\ExtensionManager;
26 use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
27 use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
28 use Symfony\Component\DependencyInjection\ContainerBuilder;
29 use Symfony\Component\DependencyInjection\Definition;
30 use Symfony\Component\DependencyInjection\Reference;
33 * Mink extension for Behat class.
35 * @author Konstantin Kudryashov <ever.zet@gmail.com>
36 * @author Christophe Coevoet <stof@notk.org>
38 class MinkExtension implements ExtensionInterface
40 const MINK_ID = 'mink';
41 const SELECTORS_HANDLER_ID = 'mink.selectors_handler';
43 const SELECTOR_TAG = 'mink.selector';
46 * @var DriverFactory[]
48 private $driverFactories = array();
50 public function __construct()
52 $this->registerDriverFactory(new GoutteFactory());
53 $this->registerDriverFactory(new SahiFactory());
54 $this->registerDriverFactory(new SeleniumFactory());
55 $this->registerDriverFactory(new Selenium2Factory());
56 $this->registerDriverFactory(new SauceLabsFactory());
57 $this->registerDriverFactory(new BrowserStackFactory());
58 $this->registerDriverFactory(new ZombieFactory());
61 public function registerDriverFactory(DriverFactory $driverFactory)
63 $this->driverFactories[$driverFactory->getDriverName()] = $driverFactory;
69 public function load(ContainerBuilder $container, array $config)
71 if (isset($config['mink_loader'])) {
72 $basePath = $container->getParameter('paths.base');
74 if (file_exists($basePath.DIRECTORY_SEPARATOR.$config['mink_loader'])) {
75 require($basePath.DIRECTORY_SEPARATOR.$config['mink_loader']);
77 require($config['mink_loader']);
81 $this->loadMink($container);
82 $this->loadContextInitializer($container);
83 $this->loadSelectorsHandler($container);
84 $this->loadSessions($container, $config);
85 $this->loadSessionsListener($container);
87 if ($config['show_auto']) {
88 $this->loadFailureShowListener($container);
91 unset($config['sessions']);
93 $container->setParameter('mink.parameters', $config);
94 $container->setParameter('mink.base_url', $config['base_url']);
95 $container->setParameter('mink.browser_name', $config['browser_name']);
101 public function configure(ArrayNodeDefinition $builder)
103 // Rewrite keys to define a shortcut way without allowing conflicts with real keys
104 $renamedKeys = array_diff(
105 array_keys($this->driverFactories),
106 array('mink_loader', 'base_url', 'files_path', 'show_auto', 'show_cmd', 'show_tmp_dir', 'default_session', 'javascript_session', 'browser_name', 'sessions')
110 ->beforeNormalization()
112 ->then(function ($v) use ($renamedKeys) {
113 foreach ($renamedKeys as $driverType) {
114 if (!array_key_exists($driverType, $v) || isset($v['sessions'][$driverType])) {
118 $v['sessions'][$driverType][$driverType] = $v[$driverType];
119 unset($v[$driverType]);
125 ->addDefaultsIfNotSet()
127 ->scalarNode('mink_loader')->defaultNull()->end()
128 ->scalarNode('base_url')->defaultNull()->end()
129 ->scalarNode('files_path')->defaultNull()->end()
130 ->booleanNode('show_auto')->defaultFalse()->end()
131 ->scalarNode('show_cmd')->defaultNull()->end()
132 ->scalarNode('show_tmp_dir')->defaultValue(sys_get_temp_dir())->end()
133 ->scalarNode('default_session')->defaultNull()->info('Defaults to the first non-javascript session if any, or the first session otherwise')->end()
134 ->scalarNode('javascript_session')->defaultNull()->info('Defaults to the first javascript session if any')->end()
135 ->scalarNode('browser_name')->defaultValue('firefox')->end()
139 /** @var ArrayNodeDefinition $sessionsBuilder */
140 $sessionsBuilder = $builder
142 ->arrayNode('sessions')
144 ->requiresAtLeastOneElement()
145 ->useAttributeAsKey('name')
149 foreach ($this->driverFactories as $factory) {
150 $factoryNode = $sessionsBuilder->children()->arrayNode($factory->getDriverName())->canBeUnset();
152 $factory->configure($factoryNode);
157 ->ifTrue(function ($v) {return count($v) > 1;})
158 ->thenInvalid('You cannot set multiple driver types for the same session')
161 ->ifTrue(function ($v) {return count($v) === 0;})
162 ->thenInvalid('You must set a driver definition for the session.')
170 public function getConfigKey()
178 public function initialize(ExtensionManager $extensionManager)
185 public function process(ContainerBuilder $container)
187 $this->processSelectors($container);
190 private function loadMink(ContainerBuilder $container)
192 $container->setDefinition(self::MINK_ID, new Definition('Behat\Mink\Mink'));
195 private function loadContextInitializer(ContainerBuilder $container)
197 $definition = new Definition('Behat\MinkExtension\Context\Initializer\MinkAwareInitializer', array(
198 new Reference(self::MINK_ID),
201 $definition->addTag(ContextExtension::INITIALIZER_TAG, array('priority' => 0));
202 $container->setDefinition('mink.context_initializer', $definition);
205 private function loadSelectorsHandler(ContainerBuilder $container)
207 $container->setDefinition(self::SELECTORS_HANDLER_ID, new Definition('Behat\Mink\Selector\SelectorsHandler'));
209 $cssSelectorDefinition = new Definition('Behat\Mink\Selector\CssSelector');
210 $cssSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'css'));
211 $container->setDefinition(self::SELECTOR_TAG . '.css', $cssSelectorDefinition);
213 $namedSelectorDefinition = new Definition('Behat\Mink\Selector\NamedSelector');
214 $namedSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'named'));
215 $container->setDefinition(self::SELECTOR_TAG . '.named', $namedSelectorDefinition);
218 private function loadSessions(ContainerBuilder $container, array $config)
220 $defaultSession = $config['default_session'];
221 $javascriptSession = $config['javascript_session'];
222 $javascriptSessions = $nonJavascriptSessions = array();
224 $minkDefinition = $container->getDefinition(self::MINK_ID);
226 foreach ($config['sessions'] as $name => $session) {
227 $driver = key($session);
228 $factory = $this->driverFactories[$driver];
230 $definition = new Definition('Behat\Mink\Session', array(
231 $factory->buildDriver($session[$driver]),
232 new Reference(self::SELECTORS_HANDLER_ID),
234 $minkDefinition->addMethodCall('registerSession', array($name, $definition));
236 if ($factory->supportsJavascript()) {
237 $javascriptSessions[] = $name;
239 $nonJavascriptSessions[] = $name;
243 if (null === $javascriptSession && !empty($javascriptSessions)) {
244 $javascriptSession = $javascriptSessions[0];
245 } elseif (null !== $javascriptSession && !in_array($javascriptSession, $javascriptSessions)) {
246 throw new InvalidConfigurationException(sprintf(
247 'The javascript session must be one of the enabled javascript sessions (%s), but got %s',
248 json_encode($javascriptSessions),
253 if (null === $defaultSession) {
254 $defaultSession = !empty($nonJavascriptSessions) ? $nonJavascriptSessions[0] : $javascriptSessions[0];
255 } elseif (!isset($config['sessions'][$defaultSession])) {
256 throw new InvalidConfigurationException(sprintf('The default session must be one of the enabled sessions, but got %s', $defaultSession));
259 $container->setParameter('mink.default_session', $defaultSession);
260 $container->setParameter('mink.javascript_session', $javascriptSession);
261 $container->setParameter('mink.available_javascript_sessions', $javascriptSessions);
264 private function loadSessionsListener(ContainerBuilder $container)
266 $definition = new Definition('Behat\MinkExtension\Listener\SessionsListener', array(
267 new Reference(self::MINK_ID),
268 '%mink.default_session%',
269 '%mink.javascript_session%',
270 '%mink.available_javascript_sessions%',
272 $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0));
273 $container->setDefinition('mink.listener.sessions', $definition);
276 private function loadFailureShowListener(ContainerBuilder $container)
278 $definition = new Definition('Behat\MinkExtension\Listener\FailureShowListener', array(
279 new Reference(self::MINK_ID),
282 $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0));
283 $container->setDefinition('mink.listener.failure_show', $definition);
286 private function processSelectors(ContainerBuilder $container)
288 $handlerDefinition = $container->getDefinition(self::SELECTORS_HANDLER_ID);
290 foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $id => $tags) {
291 foreach ($tags as $tag) {
292 if (!isset($tag['alias'])) {
293 throw new ProcessingException(sprintf(
294 'All `%s` tags should have an `alias` attribute, but `%s` service has none.',
299 $handlerDefinition->addMethodCall(
300 'registerSelector', array($tag['alias'], new Reference($id))