Add PHP CS Fixer and PHPStan max to CI; remove abandoned GoutteFactory

- Add friendsofphp/php-cs-fixer ^3.75 and phpstan/phpstan ^2.0 to
  require-dev; add .php-cs-fixer.dist.php (@Symfony ruleset with
  phpdoc_to_comment ignored_tags) and phpstan.neon (level max,
  treatPhpDocTypesAsCertain: false)
- Run CS Fixer and PHPStan in every CI matrix job alongside tests
- Add composer scripts: cs, cs-check, phpstan
- Add .php-cs-fixer.cache to .gitignore
- Fix all PHPStan max violations across src/: add return/param types,
  narrow mixed config values with is_string()/is_array() guards,
  use TaggedNodeInterface for scenario tag access in SessionsListener
- Remove GoutteFactory and its spec — the goutte driver and its
  underlying client library are abandoned

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kamil Kokot
2026-06-12 17:43:01 +02:00
parent dff5cbd479
commit 47a9d45cd1
38 changed files with 436 additions and 720 deletions

View File

@@ -67,6 +67,12 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress run: composer install --prefer-dist --no-progress
- name: CS Fixer
run: vendor/bin/php-cs-fixer fix --dry-run
- name: PHPStan
run: vendor/bin/phpstan analyse --memory-limit=512M
- name: PHPSpec - name: PHPSpec
run: vendor/bin/phpspec run -f pretty run: vendor/bin/phpspec run -f pretty

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
*.phar *.phar
composer.lock composer.lock
vendor vendor
.php-cs-fixer.cache

19
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;
$finder = Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/spec')
;
return (new Config())
->setRules([
'@Symfony' => true,
'phpdoc_to_comment' => ['ignored_tags' => ['psalm-suppress', 'phpstan-ignore']],
])
->setFinder($finder)
;

View File

@@ -30,7 +30,9 @@
"behat/mink-browserkit-driver": "^2.0", "behat/mink-browserkit-driver": "^2.0",
"phpspec/phpspec": "^8.0", "phpspec/phpspec": "^8.0",
"symfony/browser-kit": "^7.4 || ^8.0", "symfony/browser-kit": "^7.4 || ^8.0",
"symfony/http-client": "^7.4 || ^8.0" "symfony/http-client": "^7.4 || ^8.0",
"friendsofphp/php-cs-fixer": "^3.75",
"phpstan/phpstan": "^2.0"
}, },
"replace": { "replace": {
"behat/mink-extension": "self.version" "behat/mink-extension": "self.version"
@@ -46,6 +48,9 @@
} }
}, },
"scripts": { "scripts": {
"cs": "vendor/bin/php-cs-fixer fix",
"cs-check": "vendor/bin/php-cs-fixer fix --dry-run",
"phpstan": "vendor/bin/phpstan analyse --memory-limit=512M",
"test": [ "test": [
"vendor/bin/phpspec run -f pretty", "vendor/bin/phpspec run -f pretty",
"vendor/bin/behat --config behat.dist.php -fprogress --strict" "vendor/bin/behat --config behat.dist.php -fprogress --strict"

5
phpstan.neon Normal file
View File

@@ -0,0 +1,5 @@
parameters:
level: max
paths:
- src
treatPhpDocTypesAsCertain: false

View File

@@ -9,25 +9,25 @@ use PhpSpec\ObjectBehavior;
class MinkAwareInitializerSpec extends ObjectBehavior class MinkAwareInitializerSpec extends ObjectBehavior
{ {
function let(Mink $mink) public function let(Mink $mink)
{ {
$this->beConstructedWith($mink, array('base_url' => 'foo')); $this->beConstructedWith($mink, ['base_url' => 'foo']);
} }
function it_is_a_context_initializer() public function it_is_a_context_initializer()
{ {
$this->shouldHaveType('Behat\Behat\Context\Initializer\ContextInitializer'); $this->shouldHaveType('Behat\Behat\Context\Initializer\ContextInitializer');
} }
function it_does_nothing_for_basic_contexts(Context $context) public function it_does_nothing_for_basic_contexts(Context $context)
{ {
$this->initializeContext($context); $this->initializeContext($context);
} }
function it_injects_mink_and_parameters_in_mink_aware_contexts(MinkAwareContext $context, $mink) public function it_injects_mink_and_parameters_in_mink_aware_contexts(MinkAwareContext $context, $mink)
{ {
$context->setMink($mink)->shouldBeCalled(); $context->setMink($mink)->shouldBeCalled();
$context->setMinkParameters(array('base_url' => 'foo'))->shouldBeCalled(); $context->setMinkParameters(['base_url' => 'foo'])->shouldBeCalled();
$this->initializeContext($context); $this->initializeContext($context);
} }
} }

View File

@@ -13,9 +13,9 @@ use PhpSpec\ObjectBehavior;
class SessionsListenerSpec extends ObjectBehavior class SessionsListenerSpec extends ObjectBehavior
{ {
function let(Mink $mink, ScenarioTested $event, FeatureNode $feature, ScenarioNode $scenario, Suite $suite) public function let(Mink $mink, ScenarioTested $event, FeatureNode $feature, ScenarioNode $scenario, Suite $suite)
{ {
$this->beConstructedWith($mink, 'goutte', 'selenium2', array('selenium2', 'sahi')); $this->beConstructedWith($mink, 'goutte', 'selenium2', ['selenium2', 'sahi']);
$event->getSuite()->willReturn($suite); $event->getSuite()->willReturn($suite);
$event->getFeature()->willReturn($feature); $event->getFeature()->willReturn($feature);
@@ -25,17 +25,17 @@ class SessionsListenerSpec extends ObjectBehavior
$suite->getName()->willReturn('default'); $suite->getName()->willReturn('default');
$feature->hasTag('insulated')->willReturn(false); $feature->hasTag('insulated')->willReturn(false);
$feature->getTags()->willReturn(array()); $feature->getTags()->willReturn([]);
$scenario->hasTag('insulated')->willReturn(false); $scenario->hasTag('insulated')->willReturn(false);
$scenario->getTags()->willReturn(array()); $scenario->getTags()->willReturn([]);
} }
function it_is_an_event_subscriber() public function it_is_an_event_subscriber()
{ {
$this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface'); $this->shouldHaveType('Symfony\Component\EventDispatcher\EventSubscriberInterface');
} }
function it_resets_the_default_session_before_scenarios($event, $mink) public function it_resets_the_default_session_before_scenarios($event, $mink)
{ {
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('goutte')->shouldBeCalled(); $mink->setDefaultSessionName('goutte')->shouldBeCalled();
@@ -43,7 +43,7 @@ class SessionsListenerSpec extends ObjectBehavior
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_supports_changing_the_default_session_per_suite($event, $mink, $suite) public function it_supports_changing_the_default_session_per_suite($event, $mink, $suite)
{ {
$suite->hasSetting('mink_session')->willReturn(true); $suite->hasSetting('mink_session')->willReturn(true);
$suite->getSetting('mink_session')->willReturn('test'); $suite->getSetting('mink_session')->willReturn('test');
@@ -54,100 +54,100 @@ class SessionsListenerSpec extends ObjectBehavior
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_fails_for_non_string_default_suite_session($event, $suite) public function it_fails_for_non_string_default_suite_session($event, $suite)
{ {
$suite->hasSetting('mink_session')->willReturn(true); $suite->hasSetting('mink_session')->willReturn(true);
$suite->getSetting('mink_session')->willReturn(array()); $suite->getSetting('mink_session')->willReturn([]);
$this->shouldThrow(new SuiteConfigurationException('`mink_session` setting of the "default" suite is expected to be a string, array given.', 'default')) $this->shouldThrow(new SuiteConfigurationException('`mink_session` setting of the "default" suite is expected to be a string, array given.', 'default'))
->duringPrepareDefaultMinkSession($event); ->duringPrepareDefaultMinkSession($event);
} }
function it_switches_to_the_javascript_session_for_tagged_scenarios($event, $mink, $scenario, $suite) public function it_switches_to_the_javascript_session_for_tagged_scenarios($event, $mink, $scenario, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(false); $suite->hasSetting('mink_javascript_session')->willReturn(false);
$scenario->getTags()->willReturn(array('javascript')); $scenario->getTags()->willReturn(['javascript']);
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('selenium2')->shouldBeCalled(); $mink->setDefaultSessionName('selenium2')->shouldBeCalled();
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_switches_to_the_javascript_session_for_tagged_features($event, $mink, $feature, $suite) public function it_switches_to_the_javascript_session_for_tagged_features($event, $mink, $feature, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(false); $suite->hasSetting('mink_javascript_session')->willReturn(false);
$feature->getTags()->willReturn(array('javascript')); $feature->getTags()->willReturn(['javascript']);
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('selenium2')->shouldBeCalled(); $mink->setDefaultSessionName('selenium2')->shouldBeCalled();
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_supports_changing_the_default_javascript_session_per_suite($event, $mink, $scenario, $suite) public function it_supports_changing_the_default_javascript_session_per_suite($event, $mink, $scenario, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(true); $suite->hasSetting('mink_javascript_session')->willReturn(true);
$suite->getSetting('mink_javascript_session')->willReturn('sahi'); $suite->getSetting('mink_javascript_session')->willReturn('sahi');
$scenario->getTags()->willReturn(array('javascript')); $scenario->getTags()->willReturn(['javascript']);
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('sahi')->shouldBeCalled(); $mink->setDefaultSessionName('sahi')->shouldBeCalled();
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_fails_for_non_string_javascript_suite_session($event, $scenario, $suite) public function it_fails_for_non_string_javascript_suite_session($event, $scenario, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(true); $suite->hasSetting('mink_javascript_session')->willReturn(true);
$suite->getSetting('mink_javascript_session')->willReturn(array()); $suite->getSetting('mink_javascript_session')->willReturn([]);
$scenario->getTags()->willReturn(array('javascript')); $scenario->getTags()->willReturn(['javascript']);
$this->shouldThrow(new SuiteConfigurationException('`mink_javascript_session` setting of the "default" suite is expected to be a string, array given.', 'default')) $this->shouldThrow(new SuiteConfigurationException('`mink_javascript_session` setting of the "default" suite is expected to be a string, array given.', 'default'))
->duringPrepareDefaultMinkSession($event); ->duringPrepareDefaultMinkSession($event);
} }
function it_fails_for_invalid_javascript_suite_session($event, $scenario, $suite) public function it_fails_for_invalid_javascript_suite_session($event, $scenario, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(true); $suite->hasSetting('mink_javascript_session')->willReturn(true);
$suite->getSetting('mink_javascript_session')->willReturn('test'); $suite->getSetting('mink_javascript_session')->willReturn('test');
$scenario->getTags()->willReturn(array('javascript')); $scenario->getTags()->willReturn(['javascript']);
$this->shouldThrow(new SuiteConfigurationException('`mink_javascript_session` setting of the "default" suite is not a javascript session. test given but expected one of selenium2, sahi.', 'default')) $this->shouldThrow(new SuiteConfigurationException('`mink_javascript_session` setting of the "default" suite is not a javascript session. test given but expected one of selenium2, sahi.', 'default'))
->duringPrepareDefaultMinkSession($event); ->duringPrepareDefaultMinkSession($event);
} }
function it_fails_when_the_javascript_session_is_used_but_not_defined($event, $mink, $feature, $suite) public function it_fails_when_the_javascript_session_is_used_but_not_defined($event, $mink, $feature, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(false); $suite->hasSetting('mink_javascript_session')->willReturn(false);
$this->beConstructedWith($mink, 'goutte', null); $this->beConstructedWith($mink, 'goutte', null);
$feature->getTags()->willReturn(array('javascript')); $feature->getTags()->willReturn(['javascript']);
$this->shouldThrow(new ProcessingException('The @javascript tag cannot be used without enabling a javascript session')) $this->shouldThrow(new ProcessingException('The @javascript tag cannot be used without enabling a javascript session'))
->duringPrepareDefaultMinkSession($event); ->duringPrepareDefaultMinkSession($event);
} }
function it_switches_to_a_named_session($event, $mink, $scenario) public function it_switches_to_a_named_session($event, $mink, $scenario)
{ {
$scenario->getTags()->willReturn(array('mink:test')); $scenario->getTags()->willReturn(['mink:test']);
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('test')->shouldBeCalled(); $mink->setDefaultSessionName('test')->shouldBeCalled();
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_prefers_the_scenario_over_the_feature($event, $mink, $scenario, $feature, $suite) public function it_prefers_the_scenario_over_the_feature($event, $mink, $scenario, $feature, $suite)
{ {
$suite->hasSetting('mink_javascript_session')->willReturn(false); $suite->hasSetting('mink_javascript_session')->willReturn(false);
$scenario->getTags()->willReturn(array('mink:test')); $scenario->getTags()->willReturn(['mink:test']);
$feature->getTags()->willReturn(array('javascript')); $feature->getTags()->willReturn(['javascript']);
$mink->resetSessions()->shouldBeCalled(); $mink->resetSessions()->shouldBeCalled();
$mink->setDefaultSessionName('test')->shouldBeCalled(); $mink->setDefaultSessionName('test')->shouldBeCalled();
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_stops_the_sessions_for_insulated_scenarios($event, $mink, $scenario) public function it_stops_the_sessions_for_insulated_scenarios($event, $mink, $scenario)
{ {
$scenario->hasTag('insulated')->willReturn(true); $scenario->hasTag('insulated')->willReturn(true);
$mink->stopSessions()->shouldBeCalled(); $mink->stopSessions()->shouldBeCalled();
@@ -156,7 +156,7 @@ class SessionsListenerSpec extends ObjectBehavior
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_stops_the_sessions_for_insulated_features($event, $mink, $feature) public function it_stops_the_sessions_for_insulated_features($event, $mink, $feature)
{ {
$feature->hasTag('insulated')->willReturn(true); $feature->hasTag('insulated')->willReturn(true);
$mink->stopSessions()->shouldBeCalled(); $mink->stopSessions()->shouldBeCalled();
@@ -165,7 +165,7 @@ class SessionsListenerSpec extends ObjectBehavior
$this->prepareDefaultMinkSession($event); $this->prepareDefaultMinkSession($event);
} }
function it_stops_the_sessions_at_the_end_of_the_exercise($mink) public function it_stops_the_sessions_at_the_end_of_the_exercise($mink)
{ {
$mink->stopSessions()->shouldBeCalled(); $mink->stopSessions()->shouldBeCalled();

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class AppiumFactorySpec extends ObjectBehavior class AppiumFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_appium() public function it_is_named_appium()
{ {
$this->getDriverName()->shouldReturn('appium'); $this->getDriverName()->shouldReturn('appium');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -2,22 +2,22 @@
namespace spec\Behat\MinkExtension\ServiceContainer\Driver; namespace spec\Behat\MinkExtension\ServiceContainer\Driver;
use PhpSpec\ObjectBehavior;
use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory;
use PhpSpec\ObjectBehavior;
class BrowserKitFactorySpec extends ObjectBehavior class BrowserKitFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType(DriverFactory::class); $this->shouldHaveType(DriverFactory::class);
} }
function it_is_named_browserkit() public function it_is_named_browserkit()
{ {
$this->getDriverName()->shouldReturn('browserkit_http'); $this->getDriverName()->shouldReturn('browserkit_http');
} }
function it_does_not_support_javascript() public function it_does_not_support_javascript()
{ {
$this->supportsJavascript()->shouldBe(false); $this->supportsJavascript()->shouldBe(false);
} }

View File

@@ -3,21 +3,20 @@
namespace spec\Behat\MinkExtension\ServiceContainer\Driver; namespace spec\Behat\MinkExtension\ServiceContainer\Driver;
use PhpSpec\ObjectBehavior; use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class BrowserStackFactorySpec extends ObjectBehavior class BrowserStackFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_browser_stack() public function it_is_named_browser_stack()
{ {
$this->getDriverName()->shouldReturn('browser_stack'); $this->getDriverName()->shouldReturn('browser_stack');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -1,23 +0,0 @@
<?php
namespace spec\Behat\MinkExtension\ServiceContainer\Driver;
use PhpSpec\ObjectBehavior;
class GoutteFactorySpec extends ObjectBehavior
{
function it_is_a_driver_factory()
{
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
}
function it_is_named_goutte()
{
$this->getDriverName()->shouldReturn('goutte');
}
function it_does_not_support_javascript()
{
$this->supportsJavascript()->shouldBe(false);
}
}

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class SahiFactorySpec extends ObjectBehavior class SahiFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_sahi() public function it_is_named_sahi()
{ {
$this->getDriverName()->shouldReturn('sahi'); $this->getDriverName()->shouldReturn('sahi');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class SauceLabsFactorySpec extends ObjectBehavior class SauceLabsFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_sauce_labs() public function it_is_named_sauce_labs()
{ {
$this->getDriverName()->shouldReturn('sauce_labs'); $this->getDriverName()->shouldReturn('sauce_labs');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class Selenium2FactorySpec extends ObjectBehavior class Selenium2FactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_selenium2() public function it_is_named_selenium2()
{ {
$this->getDriverName()->shouldReturn('selenium2'); $this->getDriverName()->shouldReturn('selenium2');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class SeleniumFactorySpec extends ObjectBehavior class SeleniumFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_selenium() public function it_is_named_selenium()
{ {
$this->getDriverName()->shouldReturn('selenium'); $this->getDriverName()->shouldReturn('selenium');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -2,8 +2,8 @@
namespace spec\Behat\MinkExtension\ServiceContainer\Driver; namespace spec\Behat\MinkExtension\ServiceContainer\Driver;
use PhpSpec\ObjectBehavior;
use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory;
use PhpSpec\ObjectBehavior;
class WebdriverClassicFactorySpec extends ObjectBehavior class WebdriverClassicFactorySpec extends ObjectBehavior
{ {

View File

@@ -6,17 +6,17 @@ use PhpSpec\ObjectBehavior;
class ZombieFactorySpec extends ObjectBehavior class ZombieFactorySpec extends ObjectBehavior
{ {
function it_is_a_driver_factory() public function it_is_a_driver_factory()
{ {
$this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory'); $this->shouldHaveType('Behat\MinkExtension\ServiceContainer\Driver\DriverFactory');
} }
function it_is_named_zombie() public function it_is_named_zombie()
{ {
$this->getDriverName()->shouldReturn('zombie'); $this->getDriverName()->shouldReturn('zombie');
} }
function it_supports_javascript() public function it_supports_javascript()
{ {
$this->supportsJavascript()->shouldBe(true); $this->supportsJavascript()->shouldBe(true);
} }

View File

@@ -6,12 +6,12 @@ use PhpSpec\ObjectBehavior;
class MinkExtensionSpec extends ObjectBehavior class MinkExtensionSpec extends ObjectBehavior
{ {
function it_is_a_testwork_extension() public function it_is_a_testwork_extension()
{ {
$this->shouldHaveType('Behat\Testwork\ServiceContainer\Extension'); $this->shouldHaveType('Behat\Testwork\ServiceContainer\Extension');
} }
function it_is_named_mink() public function it_is_named_mink()
{ {
$this->getConfigKey()->shouldReturn('mink'); $this->getConfigKey()->shouldReturn('mink');
} }

View File

@@ -12,7 +12,6 @@ namespace Behat\MinkExtension\Context\Initializer;
use Behat\Behat\Context\Context; use Behat\Behat\Context\Context;
use Behat\Behat\Context\Initializer\ContextInitializer; use Behat\Behat\Context\Initializer\ContextInitializer;
use Behat\Mink\Mink; use Behat\Mink\Mink;
use Behat\MinkExtension\Context\MinkAwareContext; use Behat\MinkExtension\Context\MinkAwareContext;
@@ -26,6 +25,7 @@ class MinkAwareInitializer implements ContextInitializer
{ {
public function __construct( public function __construct(
private readonly Mink $mink, private readonly Mink $mink,
/** @var array<string, mixed> */
private readonly array $parameters, private readonly array $parameters,
) { ) {
} }

View File

@@ -25,12 +25,12 @@ interface MinkAwareContext extends Context
* *
* @param Mink $mink Mink session manager * @param Mink $mink Mink session manager
*/ */
public function setMink(Mink $mink); public function setMink(Mink $mink): void;
/** /**
* Sets parameters provided for Mink. * Sets parameters provided for Mink.
* *
* @param array $parameters * @param array<string, mixed> $parameters
*/ */
public function setMinkParameters(array $parameters); public function setMinkParameters(array $parameters): void;
} }

View File

@@ -23,45 +23,45 @@ class MinkContext extends RawMinkContext implements TranslatableContext
{ {
#[\Behat\Step\Given('/^(?:|I )am on (?:|the )homepage$/')] #[\Behat\Step\Given('/^(?:|I )am on (?:|the )homepage$/')]
#[\Behat\Step\When('/^(?:|I )go to (?:|the )homepage$/')] #[\Behat\Step\When('/^(?:|I )go to (?:|the )homepage$/')]
public function iAmOnHomepage() public function iAmOnHomepage(): void
{ {
$this->visitPath('/'); $this->visitPath('/');
} }
#[\Behat\Step\Given('/^(?:|I )am on "(?P<page>[^"]+)"$/')] #[\Behat\Step\Given('/^(?:|I )am on "(?P<page>[^"]+)"$/')]
#[\Behat\Step\When('/^(?:|I )go to "(?P<page>[^"]+)"$/')] #[\Behat\Step\When('/^(?:|I )go to "(?P<page>[^"]+)"$/')]
public function visit($page) public function visit(string $page): void
{ {
$this->visitPath($page); $this->visitPath($page);
} }
#[\Behat\Step\When('/^(?:|I )reload the page$/')] #[\Behat\Step\When('/^(?:|I )reload the page$/')]
public function reload() public function reload(): void
{ {
$this->getSession()->reload(); $this->getSession()->reload();
} }
#[\Behat\Step\When('/^(?:|I )move backward one page$/')] #[\Behat\Step\When('/^(?:|I )move backward one page$/')]
public function back() public function back(): void
{ {
$this->getSession()->back(); $this->getSession()->back();
} }
#[\Behat\Step\When('/^(?:|I )move forward one page$/')] #[\Behat\Step\When('/^(?:|I )move forward one page$/')]
public function forward() public function forward(): void
{ {
$this->getSession()->forward(); $this->getSession()->forward();
} }
#[\Behat\Step\When('/^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/')]
public function pressButton($button) public function pressButton(string $button): void
{ {
$button = $this->fixStepArgument($button); $button = $this->fixStepArgument($button);
$this->getSession()->getPage()->pressButton($button); $this->getSession()->getPage()->pressButton($button);
} }
#[\Behat\Step\When('/^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/')]
public function clickLink($link) public function clickLink(string $link): void
{ {
$link = $this->fixStepArgument($link); $link = $this->fixStepArgument($link);
$this->getSession()->getPage()->clickLink($link); $this->getSession()->getPage()->clickLink($link);
@@ -70,7 +70,7 @@ class MinkContext extends RawMinkContext implements TranslatableContext
#[\Behat\Step\When('/^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/')]
#[\Behat\Step\When('/^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/')] #[\Behat\Step\When('/^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/')]
#[\Behat\Step\When('/^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/')]
public function fillField($field, $value) public function fillField(string $field, string $value): void
{ {
$field = $this->fixStepArgument($field); $field = $this->fixStepArgument($field);
$value = $this->fixStepArgument($value); $value = $this->fixStepArgument($value);
@@ -78,15 +78,15 @@ class MinkContext extends RawMinkContext implements TranslatableContext
} }
#[\Behat\Step\When('/^(?:|I )fill in the following:$/')] #[\Behat\Step\When('/^(?:|I )fill in the following:$/')]
public function fillFields(TableNode $fields) public function fillFields(TableNode $fields): void
{ {
foreach ($fields->getRowsHash() as $field => $value) { foreach ($fields->getRowsHash() as $field => $value) {
$this->fillField($field, $value); $this->fillField((string) $field, is_array($value) ? implode(',', $value) : $value);
} }
} }
#[\Behat\Step\When('/^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/')]
public function selectOption($select, $option) public function selectOption(string $select, string $option): void
{ {
$select = $this->fixStepArgument($select); $select = $this->fixStepArgument($select);
$option = $this->fixStepArgument($option); $option = $this->fixStepArgument($option);
@@ -94,7 +94,7 @@ class MinkContext extends RawMinkContext implements TranslatableContext
} }
#[\Behat\Step\When('/^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/')]
public function additionallySelectOption($select, $option) public function additionallySelectOption(string $select, string $option): void
{ {
$select = $this->fixStepArgument($select); $select = $this->fixStepArgument($select);
$option = $this->fixStepArgument($option); $option = $this->fixStepArgument($option);
@@ -102,138 +102,142 @@ class MinkContext extends RawMinkContext implements TranslatableContext
} }
#[\Behat\Step\When('/^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/')]
public function checkOption($option) public function checkOption(string $option): void
{ {
$option = $this->fixStepArgument($option); $option = $this->fixStepArgument($option);
$this->getSession()->getPage()->checkField($option); $this->getSession()->getPage()->checkField($option);
} }
#[\Behat\Step\When('/^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/')]
public function uncheckOption($option) public function uncheckOption(string $option): void
{ {
$option = $this->fixStepArgument($option); $option = $this->fixStepArgument($option);
$this->getSession()->getPage()->uncheckField($option); $this->getSession()->getPage()->uncheckField($option);
} }
#[\Behat\Step\When('/^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/')] #[\Behat\Step\When('/^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/')]
public function attachFileToField($field, $path) public function attachFileToField(string $field, string $path): void
{ {
$field = $this->fixStepArgument($field); $field = $this->fixStepArgument($field);
if ($this->getMinkParameter('files_path')) { $filesPath = $this->getMinkParameter('files_path');
$fullPath = rtrim(realpath($this->getMinkParameter('files_path')), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$path; if (is_string($filesPath) && '' !== $filesPath) {
$realFilesPath = realpath($filesPath);
if (false !== $realFilesPath) {
$fullPath = rtrim($realFilesPath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.$path;
if (is_file($fullPath)) { if (is_file($fullPath)) {
$path = $fullPath; $path = $fullPath;
} }
} }
}
$this->getSession()->getPage()->attachFileToField($field, $path); $this->getSession()->getPage()->attachFileToField($field, $path);
} }
#[\Behat\Step\Then('/^(?:|I )should be on "(?P<page>[^"]+)"$/')] #[\Behat\Step\Then('/^(?:|I )should be on "(?P<page>[^"]+)"$/')]
public function assertPageAddress($page) public function assertPageAddress(string $page): void
{ {
$this->assertSession()->addressEquals($this->locatePath($page)); $this->assertSession()->addressEquals($this->locatePath($page));
} }
#[\Behat\Step\Then('/^(?:|I )should be on (?:|the )homepage$/')] #[\Behat\Step\Then('/^(?:|I )should be on (?:|the )homepage$/')]
public function assertHomepage() public function assertHomepage(): void
{ {
$this->assertSession()->addressEquals($this->locatePath('/')); $this->assertSession()->addressEquals($this->locatePath('/'));
} }
#[\Behat\Step\Then('/^the (?i)url(?-i) should match (?P<pattern>"(?:[^"]|\\")*")$/')] #[\Behat\Step\Then('/^the (?i)url(?-i) should match (?P<pattern>"(?:[^"]|\\")*")$/')]
public function assertUrlRegExp($pattern) public function assertUrlRegExp(string $pattern): void
{ {
$this->assertSession()->addressMatches($this->fixStepArgument($pattern)); $this->assertSession()->addressMatches($this->fixStepArgument($pattern));
} }
#[\Behat\Step\Then('/^the response status code should be (?P<code>\d+)$/')] #[\Behat\Step\Then('/^the response status code should be (?P<code>\d+)$/')]
public function assertResponseStatus($code) public function assertResponseStatus(string $code): void
{ {
$this->assertSession()->statusCodeEquals($code); $this->assertSession()->statusCodeEquals((int) $code);
} }
#[\Behat\Step\Then('/^the response status code should not be (?P<code>\d+)$/')] #[\Behat\Step\Then('/^the response status code should not be (?P<code>\d+)$/')]
public function assertResponseStatusIsNot($code) public function assertResponseStatusIsNot(string $code): void
{ {
$this->assertSession()->statusCodeNotEquals($code); $this->assertSession()->statusCodeNotEquals((int) $code);
} }
#[\Behat\Step\Then('/^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/')]
public function assertPageContainsText($text) public function assertPageContainsText(string $text): void
{ {
$this->assertSession()->pageTextContains($this->fixStepArgument($text)); $this->assertSession()->pageTextContains($this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/')]
public function assertPageNotContainsText($text) public function assertPageNotContainsText(string $text): void
{ {
$this->assertSession()->pageTextNotContains($this->fixStepArgument($text)); $this->assertSession()->pageTextNotContains($this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/')] #[\Behat\Step\Then('/^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/')]
public function assertPageMatchesText($pattern) public function assertPageMatchesText(string $pattern): void
{ {
$this->assertSession()->pageTextMatches($this->fixStepArgument($pattern)); $this->assertSession()->pageTextMatches($this->fixStepArgument($pattern));
} }
#[\Behat\Step\Then('/^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/')] #[\Behat\Step\Then('/^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/')]
public function assertPageNotMatchesText($pattern) public function assertPageNotMatchesText(string $pattern): void
{ {
$this->assertSession()->pageTextNotMatches($this->fixStepArgument($pattern)); $this->assertSession()->pageTextNotMatches($this->fixStepArgument($pattern));
} }
#[\Behat\Step\Then('/^the response should contain "(?P<text>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the response should contain "(?P<text>(?:[^"]|\\")*)"$/')]
public function assertResponseContains($text) public function assertResponseContains(string $text): void
{ {
$this->assertSession()->responseContains($this->fixStepArgument($text)); $this->assertSession()->responseContains($this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/')]
public function assertResponseNotContains($text) public function assertResponseNotContains(string $text): void
{ {
$this->assertSession()->responseNotContains($this->fixStepArgument($text)); $this->assertSession()->responseNotContains($this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/')] #[\Behat\Step\Then('/^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/')]
public function assertElementContainsText($element, $text) public function assertElementContainsText(string $element, string $text): void
{ {
$this->assertSession()->elementTextContains('css', $element, $this->fixStepArgument($text)); $this->assertSession()->elementTextContains('css', $element, $this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/')] #[\Behat\Step\Then('/^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/')]
public function assertElementNotContainsText($element, $text) public function assertElementNotContainsText(string $element, string $text): void
{ {
$this->assertSession()->elementTextNotContains('css', $element, $this->fixStepArgument($text)); $this->assertSession()->elementTextNotContains('css', $element, $this->fixStepArgument($text));
} }
#[\Behat\Step\Then('/^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/')]
public function assertElementContains($element, $value) public function assertElementContains(string $element, string $value): void
{ {
$this->assertSession()->elementContains('css', $element, $this->fixStepArgument($value)); $this->assertSession()->elementContains('css', $element, $this->fixStepArgument($value));
} }
#[\Behat\Step\Then('/^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/')]
public function assertElementNotContains($element, $value) public function assertElementNotContains(string $element, string $value): void
{ {
$this->assertSession()->elementNotContains('css', $element, $this->fixStepArgument($value)); $this->assertSession()->elementNotContains('css', $element, $this->fixStepArgument($value));
} }
#[\Behat\Step\Then('/^(?:|I )should see an? "(?P<element>[^"]*)" element$/')] #[\Behat\Step\Then('/^(?:|I )should see an? "(?P<element>[^"]*)" element$/')]
public function assertElementOnPage($element) public function assertElementOnPage(string $element): void
{ {
$this->assertSession()->elementExists('css', $element); $this->assertSession()->elementExists('css', $element);
} }
#[\Behat\Step\Then('/^(?:|I )should not see an? "(?P<element>[^"]*)" element$/')] #[\Behat\Step\Then('/^(?:|I )should not see an? "(?P<element>[^"]*)" element$/')]
public function assertElementNotOnPage($element) public function assertElementNotOnPage(string $element): void
{ {
$this->assertSession()->elementNotExists('css', $element); $this->assertSession()->elementNotExists('css', $element);
} }
#[\Behat\Step\Then('/^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/')]
public function assertFieldContains($field, $value) public function assertFieldContains(string $field, string $value): void
{ {
$field = $this->fixStepArgument($field); $field = $this->fixStepArgument($field);
$value = $this->fixStepArgument($value); $value = $this->fixStepArgument($value);
@@ -241,7 +245,7 @@ class MinkContext extends RawMinkContext implements TranslatableContext
} }
#[\Behat\Step\Then('/^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/')] #[\Behat\Step\Then('/^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/')]
public function assertFieldNotContains($field, $value) public function assertFieldNotContains(string $field, string $value): void
{ {
$field = $this->fixStepArgument($field); $field = $this->fixStepArgument($field);
$value = $this->fixStepArgument($value); $value = $this->fixStepArgument($value);
@@ -249,7 +253,7 @@ class MinkContext extends RawMinkContext implements TranslatableContext
} }
#[\Behat\Step\Then('/^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/')] #[\Behat\Step\Then('/^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/')]
public function assertNumElements($num, $element) public function assertNumElements(string $num, string $element): void
{ {
$this->assertSession()->elementsCount('css', $element, intval($num)); $this->assertSession()->elementsCount('css', $element, intval($num));
} }
@@ -257,7 +261,7 @@ class MinkContext extends RawMinkContext implements TranslatableContext
#[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/')] #[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/')]
#[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox is checked$/')] #[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox is checked$/')]
#[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/')] #[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/')]
public function assertCheckboxChecked($checkbox) public function assertCheckboxChecked(string $checkbox): void
{ {
$this->assertSession()->checkboxChecked($this->fixStepArgument($checkbox)); $this->assertSession()->checkboxChecked($this->fixStepArgument($checkbox));
} }
@@ -266,49 +270,56 @@ class MinkContext extends RawMinkContext implements TranslatableContext
#[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox is (?:unchecked|not checked)$/')] #[\Behat\Step\Then('/^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox is (?:unchecked|not checked)$/')]
#[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/')] #[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/')]
#[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/')] #[\Behat\Step\Then('/^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/')]
public function assertCheckboxNotChecked($checkbox) public function assertCheckboxNotChecked(string $checkbox): void
{ {
$this->assertSession()->checkboxNotChecked($this->fixStepArgument($checkbox)); $this->assertSession()->checkboxNotChecked($this->fixStepArgument($checkbox));
} }
#[\Behat\Step\Then('/^print current URL$/')] #[\Behat\Step\Then('/^print current URL$/')]
public function printCurrentUrl() public function printCurrentUrl(): void
{ {
echo $this->getSession()->getCurrentUrl(); echo $this->getSession()->getCurrentUrl();
} }
#[\Behat\Step\Then('/^print last response$/')] #[\Behat\Step\Then('/^print last response$/')]
public function printLastResponse() public function printLastResponse(): void
{ {
echo ( echo $this->getSession()->getCurrentUrl()."\n\n".
$this->getSession()->getCurrentUrl()."\n\n".
$this->getSession()->getPage()->getContent() $this->getSession()->getPage()->getContent()
); ;
} }
#[\Behat\Step\Then('/^show last response$/')] #[\Behat\Step\Then('/^show last response$/')]
public function showLastResponse() public function showLastResponse(): void
{ {
if (null === $this->getMinkParameter('show_cmd')) { $showCmd = $this->getMinkParameter('show_cmd');
if (null === $showCmd) {
throw new \RuntimeException('Set "show_cmd" parameter in behat.yml to be able to open page in browser (ex.: "show_cmd: firefox %s")'); throw new \RuntimeException('Set "show_cmd" parameter in behat.yml to be able to open page in browser (ex.: "show_cmd: firefox %s")');
} }
$filename = rtrim($this->getMinkParameter('show_tmp_dir'), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.uniqid().'.html'; $showTmpDir = $this->getMinkParameter('show_tmp_dir');
$filename = rtrim(is_string($showTmpDir) ? $showTmpDir : sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.uniqid().'.html';
file_put_contents($filename, $this->getSession()->getPage()->getContent()); file_put_contents($filename, $this->getSession()->getPage()->getContent());
system(sprintf($this->getMinkParameter('show_cmd'), escapeshellarg($filename))); system(sprintf(is_string($showCmd) ? $showCmd : '', escapeshellarg($filename)));
} }
/**
* @return string[]
*/
public static function getTranslationResources(): array public static function getTranslationResources(): array
{ {
return self::getMinkTranslationResources(); return self::getMinkTranslationResources();
} }
/**
* @return string[]
*/
public static function getMinkTranslationResources(): array public static function getMinkTranslationResources(): array
{ {
return glob(__DIR__.'/../../../../i18n/*.xliff') ?: []; return glob(__DIR__.'/../../../../i18n/*.xliff') ?: [];
} }
protected function fixStepArgument($argument) protected function fixStepArgument(string $argument): string
{ {
return str_replace('\\"', '"', $argument); return str_replace('\\"', '"', $argument);
} }

View File

@@ -11,8 +11,8 @@
namespace Behat\MinkExtension\Context; namespace Behat\MinkExtension\Context;
use Behat\Mink\Mink; use Behat\Mink\Mink;
use Behat\Mink\WebAssert;
use Behat\Mink\Session; use Behat\Mink\Session;
use Behat\Mink\WebAssert;
/** /**
* Raw Mink context for Behat BDD tool. * Raw Mink context for Behat BDD tool.
@@ -22,31 +22,28 @@ use Behat\Mink\Session;
*/ */
class RawMinkContext implements MinkAwareContext class RawMinkContext implements MinkAwareContext
{ {
private $mink; private ?Mink $mink = null;
private $minkParameters;
/** @var array<string, mixed> */
private array $minkParameters = [];
/** /**
* Sets Mink instance. * Sets Mink instance.
* *
* @param Mink $mink Mink session manager * @param Mink $mink Mink session manager
*/ */
public function setMink(Mink $mink) public function setMink(Mink $mink): void
{ {
$this->mink = $mink; $this->mink = $mink;
} }
/** /**
* Returns Mink instance. * Returns Mink instance.
*
* @return Mink
*/ */
public function getMink() public function getMink(): Mink
{ {
if (null === $this->mink) { if (null === $this->mink) {
throw new \RuntimeException( throw new \RuntimeException('Mink instance has not been set on Mink context class. Have you enabled the Mink Extension?');
'Mink instance has not been set on Mink context class. ' .
'Have you enabled the Mink Extension?'
);
} }
return $this->mink; return $this->mink;
@@ -55,9 +52,9 @@ class RawMinkContext implements MinkAwareContext
/** /**
* Returns the parameters provided for Mink. * Returns the parameters provided for Mink.
* *
* @return array * @return array<string, mixed>
*/ */
public function getMinkParameters() public function getMinkParameters(): array
{ {
return $this->minkParameters; return $this->minkParameters;
} }
@@ -65,21 +62,17 @@ class RawMinkContext implements MinkAwareContext
/** /**
* Sets parameters provided for Mink. * Sets parameters provided for Mink.
* *
* @param array $parameters * @param array<string, mixed> $parameters
*/ */
public function setMinkParameters(array $parameters) public function setMinkParameters(array $parameters): void
{ {
$this->minkParameters = $parameters; $this->minkParameters = $parameters;
} }
/** /**
* Returns specific mink parameter. * Returns specific mink parameter.
*
* @param string $name
*
* @return mixed
*/ */
public function getMinkParameter($name) public function getMinkParameter(string $name): mixed
{ {
return isset($this->minkParameters[$name]) ? $this->minkParameters[$name] : null; return isset($this->minkParameters[$name]) ? $this->minkParameters[$name] : null;
} }
@@ -89,9 +82,9 @@ class RawMinkContext implements MinkAwareContext
* feature context. * feature context.
* *
* @param string $name The key of the parameter * @param string $name The key of the parameter
* @param string $value The value of the parameter * @param mixed $value The value of the parameter
*/ */
public function setMinkParameter($name, $value) public function setMinkParameter(string $name, mixed $value): void
{ {
$this->minkParameters[$name] = $value; $this->minkParameters[$name] = $value;
} }
@@ -100,10 +93,8 @@ class RawMinkContext implements MinkAwareContext
* Returns Mink session. * Returns Mink session.
* *
* @param string|null $name name of the session OR active session will be used * @param string|null $name name of the session OR active session will be used
*
* @return Session
*/ */
public function getSession($name = null) public function getSession(?string $name = null): Session
{ {
return $this->getMink()->getSession($name); return $this->getMink()->getSession($name);
} }
@@ -112,21 +103,16 @@ class RawMinkContext implements MinkAwareContext
* Returns Mink session assertion tool. * Returns Mink session assertion tool.
* *
* @param string|null $name name of the session OR active session will be used * @param string|null $name name of the session OR active session will be used
*
* @return WebAssert
*/ */
public function assertSession($name = null) public function assertSession(?string $name = null): WebAssert
{ {
return $this->getMink()->assertSession($name); return $this->getMink()->assertSession($name);
} }
/** /**
* Visits provided relative path using provided or default session. * Visits provided relative path using provided or default session.
*
* @param string $path
* @param string|null $sessionName
*/ */
public function visitPath($path, $sessionName = null) public function visitPath(string $path, ?string $sessionName = null): void
{ {
$this->getSession($sessionName)->visit($this->locatePath($path)); $this->getSession($sessionName)->visit($this->locatePath($path));
} }
@@ -134,16 +120,13 @@ class RawMinkContext implements MinkAwareContext
/** /**
* Locates url, based on provided path. * Locates url, based on provided path.
* Override to provide custom routing mechanism. * Override to provide custom routing mechanism.
*
* @param string $path
*
* @return string
*/ */
public function locatePath($path) public function locatePath(string $path): string
{ {
$startUrl = rtrim($this->getMinkParameter('base_url') ?? '', '/') . '/'; $baseUrl = $this->getMinkParameter('base_url');
$startUrl = rtrim(is_string($baseUrl) ? $baseUrl : '', '/').'/';
return 0 !== strpos($path, 'http') ? $startUrl . ltrim($path, '/') : $path; return 0 !== strpos($path, 'http') ? $startUrl.ltrim($path, '/') : $path;
} }
/** /**
@@ -154,12 +137,13 @@ class RawMinkContext implements MinkAwareContext
* @param string $filepath Desired filepath, defaults to * @param string $filepath Desired filepath, defaults to
* upload_tmp_dir, falls back to sys_get_temp_dir() * upload_tmp_dir, falls back to sys_get_temp_dir()
*/ */
public function saveScreenshot($filename = null, $filepath = null) public function saveScreenshot(?string $filename = null, ?string $filepath = null): void
{ {
// Under Cygwin, uniqid with more_entropy must be set to true. // Under Cygwin, uniqid with more_entropy must be set to true.
// No effect in other environments. // No effect in other environments.
$filename = $filename ?: sprintf('%s_%s_%s.%s', $this->getMinkParameter('browser_name'), date('c'), uniqid('', true), 'png'); $browserName = $this->getMinkParameter('browser_name');
$filepath = $filepath ?: (ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir()); $filename = $filename ?: sprintf('%s_%s_%s.%s', is_string($browserName) ? $browserName : '', date('c'), uniqid('', true), 'png');
file_put_contents($filepath . '/' . $filename, $this->getSession()->getScreenshot()); $filepath = $filepath ?: (ini_get('upload_tmp_dir') ?: sys_get_temp_dir());
file_put_contents($filepath.'/'.$filename, $this->getSession()->getScreenshot());
} }
} }

View File

@@ -12,10 +12,10 @@ namespace Behat\MinkExtension\Listener;
use Behat\Behat\EventDispatcher\Event\AfterStepTested; use Behat\Behat\EventDispatcher\Event\AfterStepTested;
use Behat\Behat\EventDispatcher\Event\StepTested; use Behat\Behat\EventDispatcher\Event\StepTested;
use Behat\Mink\Exception\Exception as MinkException;
use Behat\Mink\Mink;
use Behat\Testwork\Tester\Result\ExceptionResult; use Behat\Testwork\Tester\Result\ExceptionResult;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Behat\Mink\Mink;
use Behat\Mink\Exception\Exception as MinkException;
/** /**
* Failed step response show listener. * Failed step response show listener.
@@ -24,18 +24,20 @@ use Behat\Mink\Exception\Exception as MinkException;
* @author Konstantin Kudryashov <ever.zet@gmail.com> * @author Konstantin Kudryashov <ever.zet@gmail.com>
* *
* @final since 2.8.0 * @final since 2.8.0
*
* @internal since 2.8.0 * @internal since 2.8.0
*/ */
class FailureShowListener implements EventSubscriberInterface class FailureShowListener implements EventSubscriberInterface
{ {
private $mink; private Mink $mink;
private $parameters;
/** @var array<string, mixed> */
private array $parameters;
/** /**
* Initializes initializer. * Initializes initializer.
* *
* @param Mink $mink * @param array<string, mixed> $parameters
* @param array $parameters
*/ */
public function __construct(Mink $mink, array $parameters) public function __construct(Mink $mink, array $parameters)
{ {
@@ -43,14 +45,11 @@ class FailureShowListener implements EventSubscriberInterface
$this->parameters = $parameters; $this->parameters = $parameters;
} }
/** public static function getSubscribedEvents(): array
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{ {
return array( return [
StepTested::AFTER => array('showFailedStepResponse', -10) StepTested::AFTER => ['showFailedStepResponse', -10],
); ];
} }
/** /**
@@ -62,11 +61,9 @@ class FailureShowListener implements EventSubscriberInterface
* `show_cmd` command to run (`open %s` to open default browser on Mac) * `show_cmd` command to run (`open %s` to open default browser on Mac)
* `show_tmp_dir` folder where to store temp files (default is system temp) * `show_tmp_dir` folder where to store temp files (default is system temp)
* *
* @param AfterStepTested $event
*
* @throws \RuntimeException if show_cmd is not configured * @throws \RuntimeException if show_cmd is not configured
*/ */
public function showFailedStepResponse(AfterStepTested $event) public function showFailedStepResponse(AfterStepTested $event): void
{ {
$testResult = $event->getTestResult(); $testResult = $event->getTestResult();
@@ -78,12 +75,14 @@ class FailureShowListener implements EventSubscriberInterface
return; return;
} }
if (null === $this->parameters['show_cmd']) { $showCmd = $this->parameters['show_cmd'];
if (null === $showCmd) {
throw new \RuntimeException('Set "show_cmd" parameter in behat.yml to be able to open page in browser (ex.: "show_cmd: open %s")'); throw new \RuntimeException('Set "show_cmd" parameter in behat.yml to be able to open page in browser (ex.: "show_cmd: open %s")');
} }
$filename = rtrim($this->parameters['show_tmp_dir'], DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.uniqid().'.html'; $showTmpDir = $this->parameters['show_tmp_dir'];
$filename = rtrim(is_string($showTmpDir) ? $showTmpDir : '', DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.uniqid().'.html';
file_put_contents($filename, $this->mink->getSession()->getPage()->getContent()); file_put_contents($filename, $this->mink->getSession()->getPage()->getContent());
system(sprintf($this->parameters['show_cmd'], escapeshellarg($filename))); system(sprintf(is_string($showCmd) ? $showCmd : '', escapeshellarg($filename)));
} }
} }

View File

@@ -12,6 +12,7 @@ namespace Behat\MinkExtension\Listener;
use Behat\Behat\EventDispatcher\Event\ExampleTested; use Behat\Behat\EventDispatcher\Event\ExampleTested;
use Behat\Behat\EventDispatcher\Event\ScenarioTested; use Behat\Behat\EventDispatcher\Event\ScenarioTested;
use Behat\Gherkin\Node\TaggedNodeInterface;
use Behat\Mink\Mink; use Behat\Mink\Mink;
use Behat\Testwork\EventDispatcher\Event\ExerciseCompleted; use Behat\Testwork\EventDispatcher\Event\ExerciseCompleted;
use Behat\Testwork\ServiceContainer\Exception\ProcessingException; use Behat\Testwork\ServiceContainer\Exception\ProcessingException;
@@ -26,28 +27,26 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
* @author Konstantin Kudryashov <ever.zet@gmail.com> * @author Konstantin Kudryashov <ever.zet@gmail.com>
* *
* @final since 2.8.0 * @final since 2.8.0
*
* @internal since 2.8.0 * @internal since 2.8.0
*/ */
class SessionsListener implements EventSubscriberInterface class SessionsListener implements EventSubscriberInterface
{ {
private $mink; private Mink $mink;
private $defaultSession; private string $defaultSession;
private $javascriptSession; private ?string $javascriptSession;
/** /**
* @var string[] The available javascript sessions * @var string[] The available javascript sessions
*/ */
private $availableJavascriptSessions; private array $availableJavascriptSessions;
/** /**
* Initializes initializer. * Initializes initializer.
* *
* @param Mink $mink
* @param string $defaultSession
* @param string|null $javascriptSession
* @param string[] $availableJavascriptSessions * @param string[] $availableJavascriptSessions
*/ */
public function __construct(Mink $mink, $defaultSession, $javascriptSession, array $availableJavascriptSessions = array()) public function __construct(Mink $mink, string $defaultSession, ?string $javascriptSession, array $availableJavascriptSessions = [])
{ {
$this->mink = $mink; $this->mink = $mink;
$this->defaultSession = $defaultSession; $this->defaultSession = $defaultSession;
@@ -55,16 +54,13 @@ class SessionsListener implements EventSubscriberInterface
$this->availableJavascriptSessions = $availableJavascriptSessions; $this->availableJavascriptSessions = $availableJavascriptSessions;
} }
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array public static function getSubscribedEvents(): array
{ {
return array( return [
ScenarioTested::BEFORE => array('prepareDefaultMinkSession', 10), ScenarioTested::BEFORE => ['prepareDefaultMinkSession', 10],
ExampleTested::BEFORE => array('prepareDefaultMinkSession', 10), ExampleTested::BEFORE => ['prepareDefaultMinkSession', 10],
ExerciseCompleted::AFTER => array('tearDownMinkSessions', -10) ExerciseCompleted::AFTER => ['tearDownMinkSessions', -10],
); ];
} }
/** /**
@@ -78,17 +74,16 @@ class SessionsListener implements EventSubscriberInterface
* `@insulated` tag will cause Mink to stop current sessions before scenario * `@insulated` tag will cause Mink to stop current sessions before scenario
* instead of just soft-resetting them * instead of just soft-resetting them
* *
* @param ScenarioTested $event
*
* @throws ProcessingException when the @javascript tag is used without a javascript session * @throws ProcessingException when the @javascript tag is used without a javascript session
*/ */
public function prepareDefaultMinkSession(ScenarioTested $event) public function prepareDefaultMinkSession(ScenarioTested $event): void
{ {
$scenario = $event->getScenario(); $scenario = $event->getScenario();
$feature = $event->getFeature(); $feature = $event->getFeature();
$session = null; $session = null;
foreach (array_merge($feature->getTags(), $scenario->getTags()) as $tag) { $scenarioTags = $scenario instanceof TaggedNodeInterface ? $scenario->getTags() : [];
foreach (array_merge($feature->getTags(), $scenarioTags) as $tag) {
if ('javascript' === $tag) { if ('javascript' === $tag) {
$session = $this->getJavascriptSession($event->getSuite()); $session = $this->getJavascriptSession($event->getSuite());
} elseif (preg_match('/^mink\:(.+)/', $tag, $matches)) { } elseif (preg_match('/^mink\:(.+)/', $tag, $matches)) {
@@ -100,7 +95,9 @@ class SessionsListener implements EventSubscriberInterface
$session = $this->getDefaultSession($event->getSuite()); $session = $this->getDefaultSession($event->getSuite());
} }
if ($scenario->hasTag('insulated') || $feature->hasTag('insulated')) { $isInsulated = ($scenario instanceof TaggedNodeInterface && $scenario->hasTag('insulated'))
|| $feature->hasTag('insulated');
if ($isInsulated) {
$this->mink->stopSessions(); $this->mink->stopSessions();
} else { } else {
$this->mink->resetSessions(); $this->mink->resetSessions();
@@ -112,12 +109,12 @@ class SessionsListener implements EventSubscriberInterface
/** /**
* Stops all started Mink sessions. * Stops all started Mink sessions.
*/ */
public function tearDownMinkSessions() public function tearDownMinkSessions(): void
{ {
$this->mink->stopSessions(); $this->mink->stopSessions();
} }
private function getDefaultSession(Suite $suite) private function getDefaultSession(Suite $suite): string
{ {
if (!$suite->hasSetting('mink_session')) { if (!$suite->hasSetting('mink_session')) {
return $this->defaultSession; return $this->defaultSession;
@@ -126,20 +123,13 @@ class SessionsListener implements EventSubscriberInterface
$session = $suite->getSetting('mink_session'); $session = $suite->getSetting('mink_session');
if (!is_string($session)) { if (!is_string($session)) {
throw new SuiteConfigurationException( throw new SuiteConfigurationException(sprintf('`mink_session` setting of the "%s" suite is expected to be a string, %s given.', $suite->getName(), gettype($session)), $suite->getName());
sprintf(
'`mink_session` setting of the "%s" suite is expected to be a string, %s given.',
$suite->getName(),
gettype($session)
),
$suite->getName()
);
} }
return $session; return $session;
} }
private function getJavascriptSession(Suite $suite) private function getJavascriptSession(Suite $suite): string
{ {
if (!$suite->hasSetting('mink_javascript_session')) { if (!$suite->hasSetting('mink_javascript_session')) {
if (null === $this->javascriptSession) { if (null === $this->javascriptSession) {
@@ -152,26 +142,11 @@ class SessionsListener implements EventSubscriberInterface
$session = $suite->getSetting('mink_javascript_session'); $session = $suite->getSetting('mink_javascript_session');
if (!is_string($session)) { if (!is_string($session)) {
throw new SuiteConfigurationException( throw new SuiteConfigurationException(sprintf('`mink_javascript_session` setting of the "%s" suite is expected to be a string, %s given.', $suite->getName(), gettype($session)), $suite->getName());
sprintf(
'`mink_javascript_session` setting of the "%s" suite is expected to be a string, %s given.',
$suite->getName(),
gettype($session)
),
$suite->getName()
);
} }
if (!in_array($session, $this->availableJavascriptSessions)) { if (!in_array($session, $this->availableJavascriptSessions)) {
throw new SuiteConfigurationException( throw new SuiteConfigurationException(sprintf('`mink_javascript_session` setting of the "%s" suite is not a javascript session. %s given but expected one of %s.', $suite->getName(), $session, implode(', ', $this->availableJavascriptSessions)), $suite->getName());
sprintf(
'`mink_javascript_session` setting of the "%s" suite is not a javascript session. %s given but expected one of %s.',
$suite->getName(),
$session,
implode(', ', $this->availableJavascriptSessions)
),
$suite->getName()
);
} }
return $session; return $session;

View File

@@ -1,24 +1,18 @@
<?php <?php
namespace Behat\MinkExtension\ServiceContainer\Driver; namespace Behat\MinkExtension\ServiceContainer\Driver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
class AppiumFactory extends Selenium2Factory class AppiumFactory extends Selenium2Factory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'appium'; return 'appium';
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -31,18 +25,18 @@ class AppiumFactory extends Selenium2Factory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
$host = $config['appium_host'].":".$config['appium_port']; $host = (is_string($config['appium_host']) ? $config['appium_host'] : '').':'.(is_string($config['appium_port']) ? $config['appium_port'] : '');
$config['wd_host'] = sprintf('%s/wd/hub', $host); $config['wd_host'] = sprintf('%s/wd/hub', $host);
return parent::buildDriver($config); return parent::buildDriver($config);
} }
protected function getCapabilitiesNode() protected function getCapabilitiesNode(): ArrayNodeDefinition
{ {
$node = parent::getCapabilitiesNode(); $node = parent::getCapabilitiesNode();
@@ -63,7 +57,7 @@ class AppiumFactory extends Selenium2Factory
->booleanNode('autoWebview')->end() ->booleanNode('autoWebview')->end()
->booleanNode('noReset')->end() ->booleanNode('noReset')->end()
->booleanNode('fullReset')->end() ->booleanNode('fullReset')->end()
//ANDROID ONLY // ANDROID ONLY
->scalarNode('appActivity')->end() ->scalarNode('appActivity')->end()
->scalarNode('appPackage')->end() ->scalarNode('appPackage')->end()
->scalarNode('appWaitActivity')->end() ->scalarNode('appWaitActivity')->end()

View File

@@ -10,34 +10,24 @@
namespace Behat\MinkExtension\ServiceContainer\Driver; namespace Behat\MinkExtension\ServiceContainer\Driver;
use Behat\Mink\Driver\BrowserKitDriver; use Behat\Mink\Driver\BrowserKitDriver;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\BrowserKit\HttpBrowser;
use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\HttpClient\HttpClient;
class BrowserKitFactory implements DriverFactory class BrowserKitFactory implements DriverFactory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'browserkit_http'; return 'browserkit_http';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return false; return false;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -51,9 +41,9 @@ class BrowserKitFactory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
if (!class_exists(BrowserKitDriver::class)) { if (!class_exists(BrowserKitDriver::class)) {
throw new \RuntimeException('Install behat/mink-browserkit-driver in order to use the browserkit_http driver.'); throw new \RuntimeException('Install behat/mink-browserkit-driver in order to use the browserkit_http driver.');
@@ -77,5 +67,4 @@ class BrowserKitFactory implements DriverFactory
'%mink.base_url%', '%mink.base_url%',
]); ]);
} }
} }

View File

@@ -11,21 +11,16 @@
namespace Behat\MinkExtension\ServiceContainer\Driver; namespace Behat\MinkExtension\ServiceContainer\Driver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
class BrowserStackFactory extends Selenium2Factory class BrowserStackFactory extends Selenium2Factory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'browser_stack'; return 'browser_stack';
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -38,16 +33,16 @@ class BrowserStackFactory extends Selenium2Factory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
$config['wd_host'] = sprintf('%s:%s@hub.browserstack.com/wd/hub', $config['username'], $config['access_key']); $config['wd_host'] = sprintf('%s:%s@hub.browserstack.com/wd/hub', is_string($config['username']) ? $config['username'] : '', is_string($config['access_key']) ? $config['access_key'] : '');
return parent::buildDriver($config); return parent::buildDriver($config);
} }
protected function getCapabilitiesNode() protected function getCapabilitiesNode(): ArrayNodeDefinition
{ {
$node = parent::getCapabilitiesNode(); $node = parent::getCapabilitiesNode();

View File

@@ -18,35 +18,14 @@ use Symfony\Component\DependencyInjection\Definition;
*/ */
interface DriverFactory interface DriverFactory
{ {
/** public function getDriverName(): string;
* Gets the name of the driver being configured.
* public function supportsJavascript(): bool;
* This will be the key of the configuration for the driver.
* public function configure(ArrayNodeDefinition $builder): void;
* @return string
*/
public function getDriverName();
/** /**
* Defines whether a session using this driver is eligible as default javascript session * @param array<string, mixed> $config
*
* @return boolean
*/ */
public function supportsJavascript(); public function buildDriver(array $config): Definition;
/**
* Setups configuration for the driver factory.
*
* @param ArrayNodeDefinition $builder
*/
public function configure(ArrayNodeDefinition $builder);
/**
* Builds the service definition for the driver.
*
* @param array $config
*
* @return Definition
*/
public function buildDriver(array $config);
} }

View File

@@ -7,26 +7,29 @@ namespace Behat\MinkExtension\ServiceContainer\Driver;
*/ */
trait EnvironmentCapabilities trait EnvironmentCapabilities
{ {
/**
* @return array<string, mixed>
*/
private function guessEnvironmentCapabilities(): array private function guessEnvironmentCapabilities(): array
{ {
switch (true) { switch (true) {
case (bool)getenv('TRAVIS_JOB_NUMBER'): case (bool) getenv('TRAVIS_JOB_NUMBER'):
return [ return [
'tunnel-identifier' => getenv('TRAVIS_JOB_NUMBER'), 'tunnel-identifier' => getenv('TRAVIS_JOB_NUMBER'),
'build' => getenv('TRAVIS_BUILD_NUMBER'), 'build' => getenv('TRAVIS_BUILD_NUMBER'),
'tags' => [ 'tags' => [
'Travis-CI', 'Travis-CI',
'PHP ' . PHP_VERSION, 'PHP '.PHP_VERSION,
], ],
]; ];
case (bool)getenv('JENKINS_HOME'): case (bool) getenv('JENKINS_HOME'):
return [ return [
'tunnel-identifier' => getenv('JOB_NAME'), 'tunnel-identifier' => getenv('JOB_NAME'),
'build' => getenv('BUILD_NUMBER'), 'build' => getenv('BUILD_NUMBER'),
'tags' => [ 'tags' => [
'Jenkins', 'Jenkins',
'PHP ' . PHP_VERSION, 'PHP '.PHP_VERSION,
getenv('BUILD_TAG'), getenv('BUILD_TAG'),
], ],
]; ];
@@ -35,7 +38,7 @@ trait EnvironmentCapabilities
return [ return [
'tags' => [ 'tags' => [
php_uname('n'), php_uname('n'),
'PHP ' . PHP_VERSION, 'PHP '.PHP_VERSION,
], ],
]; ];
} }

View File

@@ -1,156 +0,0 @@
<?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\Driver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Christophe Coevoet <stof@notk.org>
*/
class GoutteFactory implements DriverFactory
{
/**
* {@inheritdoc}
*/
public function getDriverName()
{
return 'goutte';
}
/**
* {@inheritdoc}
*/
public function supportsJavascript()
{
return false;
}
/**
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{
$builder
->children()
->arrayNode('server_parameters')
->useAttributeAsKey('key')
->prototype('variable')->end()
->end()
->arrayNode('guzzle_parameters')
->useAttributeAsKey('key')
->prototype('variable')->end()
->info(
"For Goutte 1.x, these are the second argument of the Guzzle3 client constructor.\n".
'For Goutte 2.x, these are the elements passed in the "defaults" key of the Guzzle4 config.'
)
->end()
->end()
;
}
/**
* {@inheritdoc}
*/
public function buildDriver(array $config)
{
trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "goutte" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.');
if (!class_exists('Behat\Mink\Driver\GoutteDriver')) {
throw new \RuntimeException(
'Install MinkGoutteDriver in order to use goutte driver.'
);
}
$clientArguments = array(
$config['server_parameters'],
);
$guzzleClient = null;
if ($this->isGoutte4()) {
$clientArguments = array();
if (class_exists('Symfony\Component\HttpClient\HttpClient')) {
$httpClient = new Definition('Symfony\Component\HttpClient\HttpClient');
$httpClient->setFactory('Symfony\Component\HttpClient\HttpClient::create');
$httpClient->setArgument(0, $config['server_parameters']);
$clientArguments = array($httpClient);
}
} elseif ($this->isGoutte1()) {
$guzzleClient = $this->buildGuzzle3Client($config['guzzle_parameters']);
} elseif ($this->isGuzzle6()) {
$guzzleClient = $this->buildGuzzle6Client($config['guzzle_parameters']);
} else {
$guzzleClient = $this->buildGuzzle4Client($config['guzzle_parameters']);
}
$clientDefinition = new Definition('Behat\Mink\Driver\Goutte\Client', $clientArguments);
if (null !== $guzzleClient) {
$clientDefinition->addMethodCall('setClient', array($guzzleClient));
}
return new Definition('Behat\Mink\Driver\GoutteDriver', array(
$clientDefinition,
));
}
private function buildGuzzle6Client(array $parameters)
{
// Force the parameters set by default in Goutte to reproduce its behavior
$parameters['allow_redirects'] = false;
$parameters['cookies'] = true;
return new Definition('GuzzleHttp\Client', array($parameters));
}
private function buildGuzzle4Client(array $parameters)
{
// Force the parameters set by default in Goutte to reproduce its behavior
$parameters['allow_redirects'] = false;
$parameters['cookies'] = true;
return new Definition('GuzzleHttp\Client', array(array('defaults' => $parameters)));
}
private function buildGuzzle3Client(array $parameters)
{
// Force the parameters set by default in Goutte to reproduce its behavior
$parameters['redirect.disable'] = true;
return new Definition('Guzzle\Http\Client', array(null, $parameters));
}
private function isGoutte4()
{
$client = 'Goutte\Client';
return class_exists($client) && is_a($client, 'Symfony\Component\BrowserKit\HttpBrowser', true);
}
private function isGoutte1()
{
$refl = new \ReflectionParameter(array('Goutte\Client', 'setClient'), 0);
$type = $refl->getType();
if ($type instanceof \ReflectionNamedType && 'Guzzle\Http\ClientInterface' === $type->getName()) {
return true;
}
return false;
}
private function isGuzzle6()
{
return interface_exists('GuzzleHttp\ClientInterface') &&
version_compare(\GuzzleHttp\ClientInterface::VERSION, '6.0.0', '>=');
}
}

View File

@@ -15,26 +15,17 @@ use Symfony\Component\DependencyInjection\Definition;
class SahiFactory implements DriverFactory class SahiFactory implements DriverFactory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'sahi'; return 'sahi';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return true; return true;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -48,29 +39,27 @@ class SahiFactory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "sahi" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.'); trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "sahi" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.');
if (!class_exists('Behat\Mink\Driver\SahiDriver')) { if (!class_exists('Behat\Mink\Driver\SahiDriver')) {
throw new \RuntimeException( throw new \RuntimeException('Install MinkSahiDriver in order to use sahi driver.');
'Install MinkSahiDriver in order to use sahi driver.'
);
} }
return new Definition('Behat\Mink\Driver\SahiDriver', array( return new Definition('Behat\Mink\Driver\SahiDriver', [
'%mink.browser_name%', '%mink.browser_name%',
new Definition('Behat\SahiClient\Client', array( new Definition('Behat\SahiClient\Client', [
new Definition('Behat\SahiClient\Connection', array( new Definition('Behat\SahiClient\Connection', [
$config['sid'], $config['sid'],
$config['host'], $config['host'],
$config['port'], $config['port'],
$config['browser'], $config['browser'],
$config['limit'], $config['limit'],
)), ]),
)), ]),
)); ]);
} }
} }

View File

@@ -11,21 +11,16 @@
namespace Behat\MinkExtension\ServiceContainer\Driver; namespace Behat\MinkExtension\ServiceContainer\Driver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\DependencyInjection\Definition;
class SauceLabsFactory extends Selenium2Factory class SauceLabsFactory extends Selenium2Factory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'sauce_labs'; return 'sauce_labs';
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -39,21 +34,21 @@ class SauceLabsFactory extends Selenium2Factory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
$host = 'ondemand.saucelabs.com'; $host = 'ondemand.saucelabs.com';
if ($config['connect']) { if (is_bool($config['connect']) ? $config['connect'] : (bool) $config['connect']) {
$host = 'localhost:4445'; $host = 'localhost:4445';
} }
$config['wd_host'] = sprintf('%s:%s@%s/wd/hub', $config['username'], $config['access_key'], $host); $config['wd_host'] = sprintf('%s:%s@%s/wd/hub', is_string($config['username']) ? $config['username'] : '', is_string($config['access_key']) ? $config['access_key'] : '', $host);
return parent::buildDriver($config); return parent::buildDriver($config);
} }
protected function getCapabilitiesNode() protected function getCapabilitiesNode(): ArrayNodeDefinition
{ {
$node = parent::getCapabilitiesNode(); $node = parent::getCapabilitiesNode();
@@ -84,9 +79,11 @@ class SauceLabsFactory extends Selenium2Factory
->booleanNode('disable-popup-handler')->end() ->booleanNode('disable-popup-handler')->end()
->end() ->end()
->validate() ->validate()
->ifTrue(function ($v) {return empty($v['custom-data']);}) ->ifTrue(function (mixed $v): bool {return !is_array($v) || empty($v['custom-data']); })
->then(function ($v) { ->then(function (mixed $v): mixed {
unset ($v['custom-data']); if (is_array($v)) {
unset($v['custom-data']);
}
return $v; return $v;
}) })

View File

@@ -17,26 +17,17 @@ class Selenium2Factory implements DriverFactory
{ {
use EnvironmentCapabilities; use EnvironmentCapabilities;
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'selenium2'; return 'selenium2';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return true; return true;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -48,28 +39,28 @@ class Selenium2Factory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
if (!class_exists('Behat\Mink\Driver\Selenium2Driver')) { if (!class_exists('Behat\Mink\Driver\Selenium2Driver')) {
throw new \RuntimeException(sprintf( throw new \RuntimeException(sprintf('Install MinkSelenium2Driver in order to use %s driver.', $this->getDriverName()));
'Install MinkSelenium2Driver in order to use %s driver.',
$this->getDriverName()
));
} }
$extraCapabilities = $config['capabilities']['extra_capabilities']; /** @var array<string, mixed> $capabilities */
unset($config['capabilities']['extra_capabilities']); $capabilities = is_array($config['capabilities']) ? $config['capabilities'] : [];
/** @var array<string, mixed> $extraCapabilities */
$extraCapabilities = is_array($capabilities['extra_capabilities']) ? $capabilities['extra_capabilities'] : [];
unset($capabilities['extra_capabilities']);
return new Definition('Behat\Mink\Driver\Selenium2Driver', array( return new Definition('Behat\Mink\Driver\Selenium2Driver', [
$config['browser'], $config['browser'],
array_replace($this->guessEnvironmentCapabilities(), $extraCapabilities, $config['capabilities']), array_replace($this->guessEnvironmentCapabilities(), $extraCapabilities, $capabilities),
$config['wd_host'], $config['wd_host'],
)); ]);
} }
protected function getCapabilitiesNode() protected function getCapabilitiesNode(): ArrayNodeDefinition
{ {
$node = new ArrayNodeDefinition('capabilities'); $node = new ArrayNodeDefinition('capabilities');
@@ -106,7 +97,7 @@ class Selenium2Factory implements DriverFactory
->scalarNode('sslProxy')->end() ->scalarNode('sslProxy')->end()
->end() ->end()
->validate() ->validate()
->ifTrue(function ($v) { ->ifTrue(function (mixed $v): bool {
return empty($v); return empty($v);
}) })
->thenUnset() ->thenUnset()
@@ -116,8 +107,8 @@ class Selenium2Factory implements DriverFactory
->children() ->children()
->scalarNode('profile') ->scalarNode('profile')
->validate() ->validate()
->ifTrue(function ($v) { ->ifTrue(function (mixed $v): bool {
return !file_exists($v); return !is_string($v) || !file_exists($v);
}) })
->thenInvalid('Cannot find profile zip file %s') ->thenInvalid('Cannot find profile zip file %s')
->end() ->end()
@@ -137,11 +128,14 @@ class Selenium2Factory implements DriverFactory
->end() ->end()
->end() ->end()
->validate() ->validate()
->ifTrue(function ($v) { ->ifTrue(function (mixed $v): bool {
return empty($v['prefs']); return !is_array($v) || empty($v['prefs']);
}) })
->then(function ($v) { ->then(function (mixed $v): mixed {
if (is_array($v)) {
unset($v['prefs']); unset($v['prefs']);
}
return $v; return $v;
}) })
->end() ->end()

View File

@@ -17,26 +17,17 @@ class Selenium4Factory implements DriverFactory
{ {
use EnvironmentCapabilities; use EnvironmentCapabilities;
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'selenium4'; return 'selenium4';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return true; return true;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -49,28 +40,25 @@ class Selenium4Factory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
if (!class_exists('Behat\Mink\Driver\Selenium4Driver')) { if (!class_exists('Behat\Mink\Driver\Selenium4Driver')) {
throw new \RuntimeException(sprintf( throw new \RuntimeException(sprintf('Install MinkSelenium4Driver in order to use %s driver.', $this->getDriverName()));
'Install MinkSelenium4Driver in order to use %s driver.',
$this->getDriverName()
));
} }
return new Definition('Behat\Mink\Driver\Selenium4Driver', array( return new Definition('Behat\Mink\Driver\Selenium4Driver', [
$config['browser'], $config['browser'],
array_merge( array_merge(
$this->guessEnvironmentCapabilities(), $this->guessEnvironmentCapabilities(),
$config['capabilities'] is_array($config['capabilities']) ? $config['capabilities'] : []
), ),
$config['wd_host'], $config['wd_host'],
)); ]);
} }
protected function getCapabilitiesNode() protected function getCapabilitiesNode(): ArrayNodeDefinition
{ {
$node = new ArrayNodeDefinition('capabilities'); $node = new ArrayNodeDefinition('capabilities');

View File

@@ -15,26 +15,17 @@ use Symfony\Component\DependencyInjection\Definition;
class SeleniumFactory implements DriverFactory class SeleniumFactory implements DriverFactory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'selenium'; return 'selenium';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return true; return true;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -46,25 +37,23 @@ class SeleniumFactory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "selenium" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.'); trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "selenium" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.');
if (!class_exists('Behat\Mink\Driver\SeleniumDriver')) { if (!class_exists('Behat\Mink\Driver\SeleniumDriver')) {
throw new \RuntimeException( throw new \RuntimeException('Install MinkSeleniumDriver in order to activate selenium session.');
'Install MinkSeleniumDriver in order to activate selenium session.'
);
} }
return new Definition('Behat\Mink\Driver\SeleniumDriver', array( return new Definition('Behat\Mink\Driver\SeleniumDriver', [
$config['browser'], $config['browser'],
'%mink.base_url%', '%mink.base_url%',
new Definition('Selenium\Client', array( new Definition('Selenium\Client', [
$config['host'], $config['host'],
$config['port'], $config['port'],
)), ]),
)); ]);
} }
} }

View File

@@ -10,25 +10,16 @@ class WebdriverClassicFactory implements DriverFactory
{ {
use EnvironmentCapabilities; use EnvironmentCapabilities;
/**
* {@inheritdoc}
*/
public function getDriverName(): string public function getDriverName(): string
{ {
return 'webdriver_classic'; return 'webdriver_classic';
} }
/**
* {@inheritdoc}
*/
public function supportsJavascript(): bool public function supportsJavascript(): bool
{ {
return true; return true;
} }
/**
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder): void public function configure(ArrayNodeDefinition $builder): void
{ {
$builder $builder
@@ -43,22 +34,17 @@ class WebdriverClassicFactory implements DriverFactory
->end(); ->end();
} }
/**
* {@inheritdoc}
*/
public function buildDriver(array $config): Definition public function buildDriver(array $config): Definition
{ {
if (!class_exists(WebdriverClassicDriver::class)) { if (!class_exists(WebdriverClassicDriver::class)) {
throw new \RuntimeException( throw new \RuntimeException("Install mink/webdriver-classic-driver in order to use the {$this->getDriverName()} driver.");
"Install mink/webdriver-classic-driver in order to use the {$this->getDriverName()} driver."
);
} }
return new Definition(WebdriverClassicDriver::class, [ return new Definition(WebdriverClassicDriver::class, [
$config['browser'], $config['browser'],
array_merge( array_merge(
$this->guessEnvironmentCapabilities(), $this->guessEnvironmentCapabilities(),
$config['capabilities'] is_array($config['capabilities']) ? $config['capabilities'] : []
), ),
$config['wd_host'], $config['wd_host'],
]); ]);

View File

@@ -15,26 +15,17 @@ use Symfony\Component\DependencyInjection\Definition;
class ZombieFactory implements DriverFactory class ZombieFactory implements DriverFactory
{ {
/** public function getDriverName(): string
* {@inheritdoc}
*/
public function getDriverName()
{ {
return 'zombie'; return 'zombie';
} }
/** public function supportsJavascript(): bool
* {@inheritdoc}
*/
public function supportsJavascript()
{ {
return true; return true;
} }
/** public function configure(ArrayNodeDefinition $builder): void
* {@inheritdoc}
*/
public function configure(ArrayNodeDefinition $builder)
{ {
$builder $builder
->children() ->children()
@@ -49,27 +40,25 @@ class ZombieFactory implements DriverFactory
} }
/** /**
* {@inheritdoc} * @param array<string, mixed> $config
*/ */
public function buildDriver(array $config) public function buildDriver(array $config): Definition
{ {
trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "zombie" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.'); trigger_deprecation('friends-of-behat/mink-extension', '2.8.0', 'Configuration for the "zombie" driver is deprecated, since the client implementation has been abandoned. Support for it will be removed in the next major version of this extension.');
if (!class_exists('Behat\Mink\Driver\ZombieDriver')) { if (!class_exists('Behat\Mink\Driver\ZombieDriver')) {
throw new \RuntimeException( throw new \RuntimeException('Install MinkZombieDriver in order to use zombie driver.');
'Install MinkZombieDriver in order to use zombie driver.'
);
} }
return new Definition('Behat\Mink\Driver\ZombieDriver', array( return new Definition('Behat\Mink\Driver\ZombieDriver', [
new Definition('Behat\Mink\Driver\NodeJS\Server\ZombieServer', array( new Definition('Behat\Mink\Driver\NodeJS\Server\ZombieServer', [
$config['host'], $config['host'],
$config['port'], $config['port'],
$config['node_bin'], $config['node_bin'],
$config['server_path'], $config['server_path'],
$config['threshold'], $config['threshold'],
$config['node_modules_path'], $config['node_modules_path'],
)), ]),
)); ]);
} }
} }

View File

@@ -15,7 +15,6 @@ use Behat\MinkExtension\ServiceContainer\Driver\AppiumFactory;
use Behat\MinkExtension\ServiceContainer\Driver\BrowserKitFactory; use Behat\MinkExtension\ServiceContainer\Driver\BrowserKitFactory;
use Behat\MinkExtension\ServiceContainer\Driver\BrowserStackFactory; use Behat\MinkExtension\ServiceContainer\Driver\BrowserStackFactory;
use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory; use Behat\MinkExtension\ServiceContainer\Driver\DriverFactory;
use Behat\MinkExtension\ServiceContainer\Driver\GoutteFactory;
use Behat\MinkExtension\ServiceContainer\Driver\SahiFactory; use Behat\MinkExtension\ServiceContainer\Driver\SahiFactory;
use Behat\MinkExtension\ServiceContainer\Driver\SauceLabsFactory; use Behat\MinkExtension\ServiceContainer\Driver\SauceLabsFactory;
use Behat\MinkExtension\ServiceContainer\Driver\Selenium2Factory; use Behat\MinkExtension\ServiceContainer\Driver\Selenium2Factory;
@@ -43,19 +42,18 @@ use Symfony\Component\DependencyInjection\Reference;
*/ */
class MinkExtension implements ExtensionInterface class MinkExtension implements ExtensionInterface
{ {
const MINK_ID = 'mink'; public const MINK_ID = 'mink';
const SELECTORS_HANDLER_ID = 'mink.selectors_handler'; public const SELECTORS_HANDLER_ID = 'mink.selectors_handler';
const SELECTOR_TAG = 'mink.selector'; public const SELECTOR_TAG = 'mink.selector';
/** /**
* @var DriverFactory[] * @var DriverFactory[]
*/ */
private $driverFactories = array(); private $driverFactories = [];
public function __construct() public function __construct()
{ {
$this->registerDriverFactory(new GoutteFactory());
$this->registerDriverFactory(new BrowserKitFactory()); $this->registerDriverFactory(new BrowserKitFactory());
$this->registerDriverFactory(new SahiFactory()); $this->registerDriverFactory(new SahiFactory());
$this->registerDriverFactory(new SeleniumFactory()); $this->registerDriverFactory(new SeleniumFactory());
@@ -73,18 +71,17 @@ class MinkExtension implements ExtensionInterface
$this->driverFactories[$driverFactory->getDriverName()] = $driverFactory; $this->driverFactories[$driverFactory->getDriverName()] = $driverFactory;
} }
/**
* {@inheritDoc}
*/
public function load(ContainerBuilder $container, array $config): void public function load(ContainerBuilder $container, array $config): void
{ {
if (isset($config['mink_loader'])) { if (isset($config['mink_loader'])) {
$basePath = $container->getParameter('paths.base'); $basePath = $container->getParameter('paths.base');
$basePath = is_string($basePath) ? $basePath : '';
$minkLoader = is_string($config['mink_loader']) ? $config['mink_loader'] : '';
if (file_exists($basePath.DIRECTORY_SEPARATOR.$config['mink_loader'])) { if (file_exists($basePath.DIRECTORY_SEPARATOR.$minkLoader)) {
require($basePath.DIRECTORY_SEPARATOR.$config['mink_loader']); require $basePath.DIRECTORY_SEPARATOR.$minkLoader;
} else { } else {
require($config['mink_loader']); require $minkLoader;
} }
} }
@@ -101,31 +98,38 @@ class MinkExtension implements ExtensionInterface
unset($config['sessions']); unset($config['sessions']);
$container->setParameter('mink.parameters', $config); $container->setParameter('mink.parameters', $config);
$container->setParameter('mink.base_url', $config['base_url']); $container->setParameter('mink.base_url', is_string($config['base_url'] ?? '') ? ($config['base_url'] ?? '') : '');
$container->setParameter('mink.browser_name', $config['browser_name']); $container->setParameter('mink.browser_name', is_string($config['browser_name'] ?? '') ? ($config['browser_name'] ?? '') : '');
} }
/**
* {@inheritDoc}
*/
public function configure(ArrayNodeDefinition $builder): void public function configure(ArrayNodeDefinition $builder): void
{ {
// Rewrite keys to define a shortcut way without allowing conflicts with real keys // Rewrite keys to define a shortcut way without allowing conflicts with real keys
$renamedKeys = array_diff( $renamedKeys = array_diff(
array_keys($this->driverFactories), 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') ['mink_loader', 'base_url', 'files_path', 'show_auto', 'show_cmd', 'show_tmp_dir', 'default_session', 'javascript_session', 'browser_name', 'sessions']
); );
$builder $builder
->beforeNormalization() ->beforeNormalization()
->always() ->always()
->then(function ($v) use ($renamedKeys) { ->then(function (mixed $v) use ($renamedKeys): mixed {
if (!is_array($v)) {
return $v;
}
foreach ($renamedKeys as $driverType) { foreach ($renamedKeys as $driverType) {
if (!array_key_exists($driverType, $v) || isset($v['sessions'][$driverType])) { if (!array_key_exists($driverType, $v)) {
continue;
}
/** @var array<string, array<string, mixed>> $sessions */
$sessions = is_array($v['sessions']) ? $v['sessions'] : [];
if (isset($sessions[$driverType])) {
continue; continue;
} }
$v['sessions'][$driverType][$driverType] = $v[$driverType]; $sessions[$driverType][$driverType] = $v[$driverType];
$v['sessions'] = $sessions;
unset($v[$driverType]); unset($v[$driverType]);
} }
@@ -164,84 +168,87 @@ class MinkExtension implements ExtensionInterface
$sessionsBuilder $sessionsBuilder
->validate() ->validate()
->ifTrue(function ($v) {return count($v) > 1;}) ->ifTrue(function (mixed $v): bool {return is_array($v) && count($v) > 1; })
->thenInvalid('You cannot set multiple driver types for the same session') ->thenInvalid('You cannot set multiple driver types for the same session')
->end() ->end()
->validate() ->validate()
->ifTrue(function ($v) {return count($v) === 0;}) ->ifTrue(function (mixed $v): bool {return is_array($v) && 0 === count($v); })
->thenInvalid('You must set a driver definition for the session.') ->thenInvalid('You must set a driver definition for the session.')
->end() ->end()
; ;
} }
/**
* {@inheritDoc}
*/
public function getConfigKey(): string public function getConfigKey(): string
{ {
return 'mink'; return 'mink';
} }
/**
* {@inheritdoc}
*/
public function initialize(ExtensionManager $extensionManager): void public function initialize(ExtensionManager $extensionManager): void
{ {
} }
/**
* {@inheritDoc}
*/
public function process(ContainerBuilder $container): void public function process(ContainerBuilder $container): void
{ {
$this->processSelectors($container); $this->processSelectors($container);
} }
private function loadMink(ContainerBuilder $container) private function loadMink(ContainerBuilder $container): void
{ {
$container->setDefinition(self::MINK_ID, new Definition('Behat\Mink\Mink')); $container->setDefinition(self::MINK_ID, new Definition('Behat\Mink\Mink'));
} }
private function loadContextInitializer(ContainerBuilder $container) private function loadContextInitializer(ContainerBuilder $container): void
{ {
$definition = new Definition('Behat\MinkExtension\Context\Initializer\MinkAwareInitializer', array( $definition = new Definition('Behat\MinkExtension\Context\Initializer\MinkAwareInitializer', [
new Reference(self::MINK_ID), new Reference(self::MINK_ID),
'%mink.parameters%', '%mink.parameters%',
)); ]);
$definition->addTag(ContextExtension::INITIALIZER_TAG, array('priority' => 0)); $definition->addTag(ContextExtension::INITIALIZER_TAG, ['priority' => 0]);
$container->setDefinition('mink.context_initializer', $definition); $container->setDefinition('mink.context_initializer', $definition);
} }
private function loadSelectorsHandler(ContainerBuilder $container) private function loadSelectorsHandler(ContainerBuilder $container): void
{ {
$container->setDefinition(self::SELECTORS_HANDLER_ID, new Definition('Behat\Mink\Selector\SelectorsHandler')); $container->setDefinition(self::SELECTORS_HANDLER_ID, new Definition('Behat\Mink\Selector\SelectorsHandler'));
$cssSelectorDefinition = new Definition('Behat\Mink\Selector\CssSelector'); $cssSelectorDefinition = new Definition('Behat\Mink\Selector\CssSelector');
$cssSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'css')); $cssSelectorDefinition->addTag(self::SELECTOR_TAG, ['alias' => 'css']);
$container->setDefinition(self::SELECTOR_TAG . '.css', $cssSelectorDefinition); $container->setDefinition(self::SELECTOR_TAG.'.css', $cssSelectorDefinition);
$namedSelectorDefinition = new Definition('Behat\Mink\Selector\NamedSelector'); $namedSelectorDefinition = new Definition('Behat\Mink\Selector\NamedSelector');
$namedSelectorDefinition->addTag(self::SELECTOR_TAG, array('alias' => 'named')); $namedSelectorDefinition->addTag(self::SELECTOR_TAG, ['alias' => 'named']);
$container->setDefinition(self::SELECTOR_TAG . '.named', $namedSelectorDefinition); $container->setDefinition(self::SELECTOR_TAG.'.named', $namedSelectorDefinition);
} }
private function loadSessions(ContainerBuilder $container, array $config) /**
* @param array<string, mixed> $config
*/
private function loadSessions(ContainerBuilder $container, array $config): void
{ {
$defaultSession = $config['default_session']; /** @var string|null $defaultSession */
$javascriptSession = $config['javascript_session']; $defaultSession = is_string($config['default_session']) ? $config['default_session'] : null;
$javascriptSessions = $nonJavascriptSessions = array(); /** @var string|null $javascriptSession */
$javascriptSession = is_string($config['javascript_session']) ? $config['javascript_session'] : null;
/** @var string[] $javascriptSessions */
$javascriptSessions = [];
/** @var string[] $nonJavascriptSessions */
$nonJavascriptSessions = [];
$minkDefinition = $container->getDefinition(self::MINK_ID); $minkDefinition = $container->getDefinition(self::MINK_ID);
foreach ($config['sessions'] as $name => $session) { /** @var array<string, array<string, mixed>> $sessions */
$driver = key($session); $sessions = is_array($config['sessions']) ? $config['sessions'] : [];
foreach ($sessions as $name => $session) {
$driver = (string) key($session);
$factory = $this->driverFactories[$driver]; $factory = $this->driverFactories[$driver];
$definition = new Definition('Behat\Mink\Session', array( /** @var array<string, mixed> $driverConfig */
$factory->buildDriver($session[$driver]), $driverConfig = is_array($session[$driver]) ? $session[$driver] : [];
$definition = new Definition('Behat\Mink\Session', [
$factory->buildDriver($driverConfig),
new Reference(self::SELECTORS_HANDLER_ID), new Reference(self::SELECTORS_HANDLER_ID),
)); ]);
$minkDefinition->addMethodCall('registerSession', array($name, $definition)); $minkDefinition->addMethodCall('registerSession', [$name, $definition]);
if ($factory->supportsJavascript()) { if ($factory->supportsJavascript()) {
$javascriptSessions[] = $name; $javascriptSessions[] = $name;
@@ -253,16 +260,12 @@ class MinkExtension implements ExtensionInterface
if (null === $javascriptSession && !empty($javascriptSessions)) { if (null === $javascriptSession && !empty($javascriptSessions)) {
$javascriptSession = $javascriptSessions[0]; $javascriptSession = $javascriptSessions[0];
} elseif (null !== $javascriptSession && !in_array($javascriptSession, $javascriptSessions)) { } elseif (null !== $javascriptSession && !in_array($javascriptSession, $javascriptSessions)) {
throw new InvalidConfigurationException(sprintf( throw new InvalidConfigurationException(sprintf('The javascript session must be one of the enabled javascript sessions (%s), but got %s', json_encode($javascriptSessions), $javascriptSession));
'The javascript session must be one of the enabled javascript sessions (%s), but got %s',
json_encode($javascriptSessions),
$javascriptSession
));
} }
if (null === $defaultSession) { if (null === $defaultSession) {
$defaultSession = !empty($nonJavascriptSessions) ? $nonJavascriptSessions[0] : $javascriptSessions[0]; $defaultSession = !empty($nonJavascriptSessions) ? $nonJavascriptSessions[0] : $javascriptSessions[0];
} elseif (!isset($config['sessions'][$defaultSession])) { } elseif (!is_array($config['sessions']) || !isset($config['sessions'][$defaultSession])) {
throw new InvalidConfigurationException(sprintf('The default session must be one of the enabled sessions, but got %s', $defaultSession)); throw new InvalidConfigurationException(sprintf('The default session must be one of the enabled sessions, but got %s', $defaultSession));
} }
@@ -271,43 +274,39 @@ class MinkExtension implements ExtensionInterface
$container->setParameter('mink.available_javascript_sessions', $javascriptSessions); $container->setParameter('mink.available_javascript_sessions', $javascriptSessions);
} }
private function loadSessionsListener(ContainerBuilder $container) private function loadSessionsListener(ContainerBuilder $container): void
{ {
$definition = new Definition('Behat\MinkExtension\Listener\SessionsListener', array( $definition = new Definition('Behat\MinkExtension\Listener\SessionsListener', [
new Reference(self::MINK_ID), new Reference(self::MINK_ID),
'%mink.default_session%', '%mink.default_session%',
'%mink.javascript_session%', '%mink.javascript_session%',
'%mink.available_javascript_sessions%', '%mink.available_javascript_sessions%',
)); ]);
$definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0)); $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]);
$container->setDefinition('mink.listener.sessions', $definition); $container->setDefinition('mink.listener.sessions', $definition);
} }
private function loadFailureShowListener(ContainerBuilder $container) private function loadFailureShowListener(ContainerBuilder $container): void
{ {
$definition = new Definition('Behat\MinkExtension\Listener\FailureShowListener', array( $definition = new Definition('Behat\MinkExtension\Listener\FailureShowListener', [
new Reference(self::MINK_ID), new Reference(self::MINK_ID),
'%mink.parameters%', '%mink.parameters%',
)); ]);
$definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, array('priority' => 0)); $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]);
$container->setDefinition('mink.listener.failure_show', $definition); $container->setDefinition('mink.listener.failure_show', $definition);
} }
private function processSelectors(ContainerBuilder $container) private function processSelectors(ContainerBuilder $container): void
{ {
$handlerDefinition = $container->getDefinition(self::SELECTORS_HANDLER_ID); $handlerDefinition = $container->getDefinition(self::SELECTORS_HANDLER_ID);
foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $id => $tags) { foreach ($container->findTaggedServiceIds(self::SELECTOR_TAG) as $id => $tags) {
foreach ($tags as $tag) { foreach ($tags as $tag) {
if (!isset($tag['alias'])) { if (!is_array($tag) || !isset($tag['alias'])) {
throw new ProcessingException(sprintf( throw new ProcessingException(sprintf('All `%s` tags should have an `alias` attribute, but `%s` service has none.', self::SELECTOR_TAG, $id));
'All `%s` tags should have an `alias` attribute, but `%s` service has none.',
$tag,
$id
));
} }
$handlerDefinition->addMethodCall( $handlerDefinition->addMethodCall(
'registerSelector', array($tag['alias'], new Reference($id)) 'registerSelector', [is_string($tag['alias']) ? $tag['alias'] : '', new Reference($id)]
); );
} }
} }