From c36e8bbf3cbfc56ea5b5af7ad385535e11fc5d2f Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 12:36:11 +0100 Subject: [PATCH 1/5] Implement driver initializer --- src/WebdriverClassicDriver.php | 24 +++++++++++++++-- tests/Custom/CapabilityTest.php | 46 ++++++++++++++++----------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index d072288..26e757f 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -31,6 +31,9 @@ use Facebook\WebDriver\WebDriverSelect; use JetBrains\PhpStorm\Language; +/** + * @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): RemoteWebDriver + */ class WebdriverClassicDriver extends CoreDriver { public const DEFAULT_BROWSER = WebDriverBrowserType::CHROME; @@ -81,19 +84,27 @@ class WebdriverClassicDriver extends CoreDriver private string $webDriverHost; + /** + * @var TWebDriverInstantiator + */ + private $webDriverInstantiator; + private ?string $initialWindowHandle = null; /** * @param string $browserName One of 'edge', 'firefox', 'chrome' or any one of {@see WebDriverBrowserType} constants. + * @param TWebDriverInstantiator|null $webDriverInstantiator */ public function __construct( string $browserName = self::DEFAULT_BROWSER, array $desiredCapabilities = [], - string $webDriverHost = 'http://localhost:4444/wd/hub' + string $webDriverHost = 'http://localhost:4444/wd/hub', + ?callable $webDriverInstantiator = null ) { $this->browserName = $browserName; $this->desiredCapabilities = $this->initCapabilities($desiredCapabilities); $this->webDriverHost = $webDriverHost; + $this->webDriverInstantiator = $webDriverInstantiator ?? [self::class, 'instantiateWebDriver']; } // @@ -762,7 +773,7 @@ protected function createWebDriver(): void throw new DriverException('Base driver has already been created'); } - $this->webDriver = RemoteWebDriver::create($this->webDriverHost, $this->getDesiredCapabilities()); + $this->webDriver = ($this->webDriverInstantiator)($this->webDriverHost, $this->desiredCapabilities); } /** @@ -782,6 +793,15 @@ protected function getDesiredCapabilities(): array return $this->desiredCapabilities->toArray(); } + // + + // + + private static function instantiateWebDriver(string $driverHost, DesiredCapabilities $capabilities): RemoteWebDriver + { + return RemoteWebDriver::create($driverHost, $capabilities); + } + private function getNormalisedBrowserName(): string { return self::BROWSER_NAME_ALIAS_MAP[$this->browserName] ?? $this->browserName; diff --git a/tests/Custom/CapabilityTest.php b/tests/Custom/CapabilityTest.php index f3a3082..d85a9df 100644 --- a/tests/Custom/CapabilityTest.php +++ b/tests/Custom/CapabilityTest.php @@ -2,6 +2,9 @@ namespace Mink\WebdriverClassicDriver\Tests\Custom; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\WebDriverOptions; +use Facebook\WebDriver\WebDriverTimeouts; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; class CapabilityTest extends \PHPUnit\Framework\TestCase @@ -14,9 +17,26 @@ class CapabilityTest extends \PHPUnit\Framework\TestCase */ public function testThatCapabilitiesAreAsExpected(string $browserName, array $desiredCapabilities, array $expectedCapabilities): void { - $driver = $this->createDriverExposingCapabilities($browserName, $desiredCapabilities); + $mockWebDriver = $this->createMock(RemoteWebDriver::class); + $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); + $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); + $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); + $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); - $this->assertSame($expectedCapabilities, $driver->capabilities); + $actualCapabilities = null; + $driver = new WebdriverClassicDriver( + $browserName, + $desiredCapabilities, + 'example.com', + function ($host, $capabilities) use (&$actualCapabilities, $mockWebDriver) { + $actualCapabilities = $capabilities->toArray(); + return $mockWebDriver; + } + ); + + $driver->start(); + + $this->assertSame($expectedCapabilities, $actualCapabilities); } public static function capabilitiesDataProvider(): iterable @@ -78,26 +98,4 @@ public static function capabilitiesDataProvider(): iterable ], ]; } - - /** - * @param string $browserName - * @param array $desiredCapabilities - * @return WebdriverClassicDriver&object{capabilities: array} - */ - private function createDriverExposingCapabilities(string $browserName, array $desiredCapabilities = []): WebdriverClassicDriver - { - return new class($browserName, $desiredCapabilities) extends WebdriverClassicDriver { - /** - * @var array - */ - public array $capabilities; - - public function __construct(string $browserName, array $desiredCapabilities) - { - parent::__construct($browserName, $desiredCapabilities); - - $this->capabilities = $this->getDesiredCapabilities(); - } - }; - } } From ac0c0feba739f802dfe43d9a6a01708eabfdf2e5 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 12:45:32 +0100 Subject: [PATCH 2/5] Remove unused method --- src/WebdriverClassicDriver.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index 26e757f..e973872 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -788,11 +788,6 @@ protected function getWebDriver(): RemoteWebDriver throw new DriverException('Base driver has not been created'); } - protected function getDesiredCapabilities(): array - { - return $this->desiredCapabilities->toArray(); - } - // // From 817e92e18f05b29ef58cfce90f2221d9ff1f7fe3 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 12:46:21 +0100 Subject: [PATCH 3/5] Update comment --- src/WebdriverClassicDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index e973872..97976be 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -762,7 +762,7 @@ public function setTimeouts(array $timeouts): void // - // + // /** * @throws DriverException From b9bd78ed0d82fa21bc20246eeb4478ecfcb2db2d Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 14:46:53 +0100 Subject: [PATCH 4/5] Avoid mocking our own driver --- tests/Custom/WebDriverTest.php | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/Custom/WebDriverTest.php b/tests/Custom/WebDriverTest.php index 1c4ff40..5407ecc 100644 --- a/tests/Custom/WebDriverTest.php +++ b/tests/Custom/WebDriverTest.php @@ -3,6 +3,10 @@ namespace Mink\WebdriverClassicDriver\Tests\Custom; use Behat\Mink\Exception\DriverException; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\WebDriver; +use Facebook\WebDriver\WebDriverOptions; +use Facebook\WebDriver\WebDriverTimeouts; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; class WebDriverTest extends TestCase @@ -35,11 +39,9 @@ public function testStartedDriverCannotBeSubsequentlyStarted(): void public function testDriverCatchesUpstreamErrorsDuringStart(): void { - $driver = $this->createPartialMock(WebdriverClassicDriver::class, ['createWebDriver', 'getWebDriver']); - $driver - ->expects($this->once()) - ->method('createWebDriver') - ->willThrowException(new \RuntimeException('An upstream error')); + $driver = new WebdriverClassicDriver('fake browser', [], 'example.com', function () { + throw new \RuntimeException('An upstream error'); + }); $this->expectException(DriverException::class); $this->expectExceptionMessage('Could not start driver: An upstream error'); @@ -49,15 +51,15 @@ public function testDriverCatchesUpstreamErrorsDuringStart(): void public function testDriverCatchesUpstreamErrorsDuringStop(): void { - $driver = $this->createPartialMock(WebdriverClassicDriver::class, ['createWebDriver', 'isStarted', 'getWebDriver']); - $driver - ->expects($this->once()) - ->method('isStarted') - ->willReturn(true); - $driver - ->expects($this->once()) - ->method('getWebDriver') - ->willThrowException(new \RuntimeException('An upstream error')); + $mockWebDriver = $this->createMock(RemoteWebDriver::class); + $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); + $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); + $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); + $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); + $mockWebDriver->method('quit')->willThrowException(new \RuntimeException('An upstream error')); + $driver = new WebdriverClassicDriver('fake browser', [], 'example.com', fn() => $mockWebDriver); + + $driver->start(); $this->expectException(DriverException::class); $this->expectExceptionMessage('Could not close connection: An upstream error'); From b2dfacb28c3761b2d8c45fe6da8d7e7c2b454600 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 18:26:38 +0100 Subject: [PATCH 5/5] Move shared logic into trait --- tests/Custom/CapabilityTest.php | 12 ++++-------- tests/Custom/WebDriverTest.php | 13 ++++--------- tests/WebDriverMockingTrait.php | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 tests/WebDriverMockingTrait.php diff --git a/tests/Custom/CapabilityTest.php b/tests/Custom/CapabilityTest.php index d85a9df..9abc275 100644 --- a/tests/Custom/CapabilityTest.php +++ b/tests/Custom/CapabilityTest.php @@ -2,13 +2,13 @@ namespace Mink\WebdriverClassicDriver\Tests\Custom; -use Facebook\WebDriver\Remote\RemoteWebDriver; -use Facebook\WebDriver\WebDriverOptions; -use Facebook\WebDriver\WebDriverTimeouts; +use Mink\WebdriverClassicDriver\Tests\WebDriverMockingTrait; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; class CapabilityTest extends \PHPUnit\Framework\TestCase { + use WebDriverMockingTrait; + /** * @param array $desiredCapabilities * @param array $expectedCapabilities @@ -17,11 +17,7 @@ class CapabilityTest extends \PHPUnit\Framework\TestCase */ public function testThatCapabilitiesAreAsExpected(string $browserName, array $desiredCapabilities, array $expectedCapabilities): void { - $mockWebDriver = $this->createMock(RemoteWebDriver::class); - $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); - $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); - $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); - $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); + $mockWebDriver = $this->createMockWebDriver(); $actualCapabilities = null; $driver = new WebdriverClassicDriver( diff --git a/tests/Custom/WebDriverTest.php b/tests/Custom/WebDriverTest.php index 5407ecc..6a778d4 100644 --- a/tests/Custom/WebDriverTest.php +++ b/tests/Custom/WebDriverTest.php @@ -3,14 +3,13 @@ namespace Mink\WebdriverClassicDriver\Tests\Custom; use Behat\Mink\Exception\DriverException; -use Facebook\WebDriver\Remote\RemoteWebDriver; -use Facebook\WebDriver\WebDriver; -use Facebook\WebDriver\WebDriverOptions; -use Facebook\WebDriver\WebDriverTimeouts; +use Mink\WebdriverClassicDriver\Tests\WebDriverMockingTrait; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; class WebDriverTest extends TestCase { + use WebDriverMockingTrait; + public function testDriverMustBeStartedBeforeUse(): void { $this->expectException(DriverException::class); @@ -51,11 +50,7 @@ public function testDriverCatchesUpstreamErrorsDuringStart(): void public function testDriverCatchesUpstreamErrorsDuringStop(): void { - $mockWebDriver = $this->createMock(RemoteWebDriver::class); - $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); - $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); - $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); - $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); + $mockWebDriver = $this->createMockWebDriver(); $mockWebDriver->method('quit')->willThrowException(new \RuntimeException('An upstream error')); $driver = new WebdriverClassicDriver('fake browser', [], 'example.com', fn() => $mockWebDriver); diff --git a/tests/WebDriverMockingTrait.php b/tests/WebDriverMockingTrait.php new file mode 100644 index 0000000..3bce02e --- /dev/null +++ b/tests/WebDriverMockingTrait.php @@ -0,0 +1,34 @@ + $class + * @return T&MockObject + */ + abstract function createMock(string $class): object; + + /** + * @return RemoteWebDriver&MockObject + */ + private function createMockWebDriver(): RemoteWebDriver + { + $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); + + $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); + $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); + + $mockWebDriver = $this->createMock(RemoteWebDriver::class); + $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); + + return $mockWebDriver; + } +}