From c36e8bbf3cbfc56ea5b5af7ad385535e11fc5d2f Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 12:36:11 +0100 Subject: [PATCH 1/7] 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/7] 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/7] 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 e3fa72fc59d8f660410e442d39367f392b6be8d6 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 13:09:33 +0100 Subject: [PATCH 4/7] Fix various phpstan warnings --- .editorconfig | 3 +++ phpstan.dist.neon | 37 ++++++++++++++++++++++++--------- src/WebdriverClassicDriver.php | 25 ++++++++++++++++++---- tests/Custom/CapabilityTest.php | 10 +++++++-- tests/Custom/TimeoutTest.php | 3 +++ 5 files changed, 62 insertions(+), 16 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9cd5d20..879a1c1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,6 @@ indent_size = 2 [*.php] ij_php_align_multiline_parameters = false + +[*.neon] +indent_style = tab diff --git a/phpstan.dist.neon b/phpstan.dist.neon index db774f4..7b42960 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -1,11 +1,28 @@ -parameters: - level: 8 - paths: - - src - - tests - checkMissingIterableValueType: false - treatPhpDocTypesAsCertain: false - includes: - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon + - vendor/phpstan/phpstan-phpunit/extension.neon + - vendor/phpstan/phpstan-phpunit/rules.neon + +parameters: + level: 8 + paths: + - src + - tests + ignoreErrors: + - + # See: https://github.com/php-webdriver/php-webdriver/pull/1120 + message: '#^Parameter \#1 \$seconds of method Facebook\\WebDriver\\WebDriverTimeouts\:\:implicitlyWait\(\) expects int, float\|int given\.$#' + identifier: argument.type + count: 1 + path: src/WebdriverClassicDriver.php + - + # See: https://github.com/php-webdriver/php-webdriver/pull/1120 + message: '#^Parameter \#1 \$seconds of method Facebook\\WebDriver\\WebDriverTimeouts\:\:pageLoadTimeout\(\) expects int, float\|int given\.$#' + identifier: argument.type + count: 1 + path: src/WebdriverClassicDriver.php + - + # See: https://github.com/php-webdriver/php-webdriver/pull/1120 + message: '#^Parameter \#1 \$seconds of method Facebook\\WebDriver\\WebDriverTimeouts\:\:setScriptTimeout\(\) expects int, float\|int given\.$#' + identifier: argument.type + count: 1 + path: src/WebdriverClassicDriver.php diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index 97976be..da24eae 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -32,6 +32,9 @@ use JetBrains\PhpStorm\Language; /** + * @phpstan-type TTimeouts array{script?: null|numeric, implicit?: null|numeric, page?: null|numeric, "page load"?: null|numeric, pageLoad?: null|numeric} + * @phpstan-type TCapabilities array + * @phpstan-type TElementValue array|bool|mixed|string|null * @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): RemoteWebDriver */ class WebdriverClassicDriver extends CoreDriver @@ -80,6 +83,9 @@ class WebdriverClassicDriver extends CoreDriver private DesiredCapabilities $desiredCapabilities; + /** + * @var TTimeouts + */ private array $timeouts = []; private string $webDriverHost; @@ -93,6 +99,7 @@ class WebdriverClassicDriver extends CoreDriver /** * @param string $browserName One of 'edge', 'firefox', 'chrome' or any one of {@see WebDriverBrowserType} constants. + * @param TCapabilities $desiredCapabilities * @param TWebDriverInstantiator|null $webDriverInstantiator */ public function __construct( @@ -340,12 +347,16 @@ public function getAttribute( return $this->executeJsOnXpath($xpath, $script); } + /** + * {@inheritdoc} + * @return TElementValue + */ public function getValue( #[Language('XPath')] string $xpath ) { $element = $this->findElement($xpath); - $widgetType = strtolower($element->getTagName() ?? ''); + $widgetType = $element->getTagName(); if ($widgetType === 'input') { $widgetType = strtolower((string)$element->getAttribute('type')); } @@ -380,13 +391,17 @@ public function getValue( } } + /** + * {@inheritdoc} + * @param TElementValue $value + */ public function setValue( #[Language('XPath')] string $xpath, $value ): void { $element = $this->findElement($xpath); - $widgetType = strtolower($element->getTagName() ?? ''); + $widgetType = $element->getTagName(); if ($widgetType === 'input') { $widgetType = strtolower((string)$element->getAttribute('type')); } @@ -519,7 +534,7 @@ public function selectOption( bool $multiple = false ): void { $element = $this->findElement($xpath); - $tagName = strtolower($element->getTagName() ?? ''); + $tagName = $element->getTagName(); if ($tagName === 'input' && strtolower((string)$element->getAttribute('type')) === 'radio') { $this->selectRadioValue($element, $value); @@ -747,7 +762,7 @@ public function getWebDriverSessionId(): ?string /** * Sets the timeouts to apply to the webdriver session * - * @param array $timeouts The session timeout settings: Array of {script, implicit, page} => time in milliseconds + * @param TTimeouts $timeouts The session timeout settings: Array of {script, implicit, page} => time in milliseconds * @throws DriverException * @api */ @@ -805,6 +820,8 @@ private function getNormalisedBrowserName(): string /** * Detect and assign appropriate browser capabilities * + * @param TCapabilities $desiredCapabilities + * * @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities */ private function initCapabilities(array $desiredCapabilities): DesiredCapabilities diff --git a/tests/Custom/CapabilityTest.php b/tests/Custom/CapabilityTest.php index d85a9df..025c8ed 100644 --- a/tests/Custom/CapabilityTest.php +++ b/tests/Custom/CapabilityTest.php @@ -7,11 +7,14 @@ use Facebook\WebDriver\WebDriverTimeouts; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; +/** + * @phpstan-import-type TCapabilities from WebdriverClassicDriver + */ class CapabilityTest extends \PHPUnit\Framework\TestCase { /** - * @param array $desiredCapabilities - * @param array $expectedCapabilities + * @param TCapabilities $desiredCapabilities + * @param TCapabilities $expectedCapabilities * * @dataProvider capabilitiesDataProvider */ @@ -39,6 +42,9 @@ function ($host, $capabilities) use (&$actualCapabilities, $mockWebDriver) { $this->assertSame($expectedCapabilities, $actualCapabilities); } + /** + * @return iterable + */ public static function capabilitiesDataProvider(): iterable { yield 'unknown browser starts with default driver capabilities' => [ diff --git a/tests/Custom/TimeoutTest.php b/tests/Custom/TimeoutTest.php index b8c49e9..9fe3ae6 100644 --- a/tests/Custom/TimeoutTest.php +++ b/tests/Custom/TimeoutTest.php @@ -65,6 +65,9 @@ public function testDeprecatedShortPageLoadTimeoutThrowsException(string $type): $this->driver->visit($this->pathTo('/page_load.php?sleep=2')); } + /** + * @return iterable + */ public static function deprecatedPageLoadDataProvider(): iterable { yield 'selenium 3 style' => ['type' => 'pageLoad']; From d909da0fbc9c60d9f3fae313dde9ae768770bd9a Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 14:29:15 +0100 Subject: [PATCH 5/7] Increase PHPStan level to 9 --- phpstan.dist.neon | 2 +- src/WebdriverClassicDriver.php | 44 ++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 7b42960..f0dc8e8 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -3,7 +3,7 @@ includes: - vendor/phpstan/phpstan-phpunit/rules.neon parameters: - level: 8 + level: 9 paths: - src - tests diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index da24eae..47c18f7 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -280,7 +280,7 @@ public function getWindowNames(): array public function getWindowName(): string { - $name = (string)$this->evaluateScript('window.name'); + $name = $this->getAsString($this->evaluateScript('window.name'), 'Window name'); if ($name === '') { $name = self::W3C_WINDOW_HANDLE_PREFIX . $this->getWebDriver()->getWindowHandle(); @@ -317,7 +317,7 @@ public function getText( return trim(str_replace( ["\r\n", "\r", "\n", "\xc2\xa0"], ' ', - $this->getElementDomProperty($this->findElement($xpath), 'innerText') + $this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'innerText'), 'The element\'s innerText') )); } @@ -325,14 +325,14 @@ public function getHtml( #[Language('XPath')] string $xpath ): string { - return $this->getElementDomProperty($this->findElement($xpath), 'innerHTML'); + return $this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'innerHTML'), 'The element\'s innerHTML'); } public function getOuterHtml( #[Language('XPath')] string $xpath ): string { - return $this->getElementDomProperty($this->findElement($xpath), 'outerHTML'); + return $this->getAsString($this->getElementDomProperty($this->findElement($xpath), 'outerHTML'), 'The element\'s outerHTML'); } public function getAttribute( @@ -344,7 +344,8 @@ public function getAttribute( // so we cannot use webdriver api for this. See also: https://w3c.github.io/webdriver/#dfn-get-element-attribute $escapedName = $this->jsonEncode($name, 'get attribute', 'attribute name'); $script = "return arguments[0].getAttribute($escapedName)"; - return $this->executeJsOnXpath($xpath, $script); + $result = $this->executeJsOnXpath($xpath, $script); + return $result === null ? null : $this->getAsString($result, "The element's $name attribute"); } /** @@ -412,7 +413,7 @@ public function setValue( if (is_array($value)) { $this->deselectAllOptions($element); foreach ($value as $option) { - $this->selectOptionOnElement($element, $option, true); + $this->selectOptionOnElement($element, $this->getAsString($option, 'Option value'), true); } return; } @@ -941,12 +942,13 @@ private function charToSynOptions($char, ?string $modifier = null): string * Executes JS on a given element - pass in a js script string and argument[0] will * be replaced with a reference to the result of the $xpath query * - * @param string $xpath the xpath to search with - * @param string $script the script to execute + * Example: + * ``` + * $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length'); + * ``` * * @return mixed * @throws DriverException - * @example $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length'); */ private function executeJsOnXpath( #[Language('XPath')] @@ -960,11 +962,13 @@ private function executeJsOnXpath( /** * Executes JS on a given element - pass in a js script string and argument[0] will contain a reference to the element * - * @param RemoteWebElement $element the webdriver element - * @param string $script the script to execute + * Example: + * ``` + * $this->executeJsOnElement($element, 'return argument[0].childNodes.length'); + * ``` + * * @return mixed * @throws DriverException - * @example $this->executeJsOnXpath($xpath, 'return argument[0].childNodes.length'); */ private function executeJsOnElement( RemoteWebElement $element, @@ -1257,5 +1261,21 @@ private function getElementDomProperty(RemoteWebElement $element, string $proper } } + /** + * @param mixed $value + * @throws DriverException + */ + private function getAsString($value, string $name): string + { + if (!is_scalar($value)) { + $actualType = gettype($value); + throw new DriverException( + "$name should be a string or at least a scalar value, but received `$actualType` instead" + ); + } + + return (string)$value; + } + // } From 5a53ec5995c7a670fa87d214f13627c3d18c4771 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Sat, 4 Jan 2025 14:41:09 +0100 Subject: [PATCH 6/7] Add string type assertion test --- tests/Custom/WebDriverTest.php | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Custom/WebDriverTest.php b/tests/Custom/WebDriverTest.php index 1c4ff40..d8fd6fe 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\Remote\RemoteWebElement; +use Facebook\WebDriver\WebDriverOptions; +use Facebook\WebDriver\WebDriverTimeouts; use Mink\WebdriverClassicDriver\WebdriverClassicDriver; class WebDriverTest extends TestCase @@ -72,4 +76,32 @@ public function testClassicDriverCanProvideBrowserName(): void $this->driver->getBrowserName() ); } + + public function testThatDriverCatchesUnexpectedAttributeValueType(): void + { + $mockWebDriver = $this->createMock(RemoteWebDriver::class); + $mockWebDriverOptions = $this->createMock(WebDriverOptions::class); + $mockWebDriverTimeouts = $this->createMock(WebDriverTimeouts::class); + $mockElement = $this->createMock(RemoteWebElement::class); + $mockWebDriver->method('manage')->willReturn($mockWebDriverOptions); + $mockWebDriverOptions->method('timeouts')->willReturn($mockWebDriverTimeouts); + $mockWebDriver + ->expects($this->once()) + ->method('findElement') + ->willReturn($mockElement); + $mockWebDriver + ->expects($this->once()) + ->method('executeScript') + ->with('return arguments[0].getAttribute("some-attribute")', [$mockElement]) + ->willReturn(['invalid attribute value']); + + $driver = new WebdriverClassicDriver('fake browser', [], 'example.com', fn() => $mockWebDriver); + + $driver->start(); + + $this->expectException(DriverException::class); + $this->expectExceptionMessage('The element\'s some-attribute attribute should be a string or at least a scalar value, but received `array` instead'); + + $driver->getAttribute('//fake', 'some-attribute'); + } } From 602aa24be858614bb347e5ad0cd400ccf9ed85ab Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 1 Jan 2025 15:21:35 +0100 Subject: [PATCH 7/7] Upgrade phpstan; increase phpstan level; fix a few bugs --- composer.json | 4 ++-- phpstan.dist.neon | 12 +++++++++++- src/WebdriverClassicDriver.php | 25 +++++++++---------------- tests/WebdriverClassicConfig.php | 5 ++++- tests/bootstrap.php | 5 ++--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index e8f1a03..f7cae58 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,8 @@ }, "require-dev": { "mink/driver-testsuite": "dev-master", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan": "^2", + "phpstan/phpstan-phpunit": "^2", "phpunit/phpunit": "^9.6.8", "symfony/error-handler": "^5.4 || ^6.0 || ^7.0", "symfony/process": "^5.4 || ^6.0 || ^7.0", diff --git a/phpstan.dist.neon b/phpstan.dist.neon index f0dc8e8..7639a1a 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -3,7 +3,7 @@ includes: - vendor/phpstan/phpstan-phpunit/rules.neon parameters: - level: 9 + level: 10 paths: - src - tests @@ -26,3 +26,13 @@ parameters: identifier: argument.type count: 1 path: src/WebdriverClassicDriver.php + - + message: '#^Method Mink\\WebdriverClassicDriver\\WebdriverClassicDriver\:\:getWindowHandleFromName\(\) should return string but returns mixed\.$#' + identifier: return.type + count: 1 + path: src/WebdriverClassicDriver.php + - + message: '#^Parameter \#1 \$handle of method Facebook\\WebDriver\\Remote\\RemoteTargetLocator\:\:window\(\) expects string, mixed given\.$#' + identifier: argument.type + count: 3 + path: src/WebdriverClassicDriver.php diff --git a/src/WebdriverClassicDriver.php b/src/WebdriverClassicDriver.php index 47c18f7..1f3e7a8 100644 --- a/src/WebdriverClassicDriver.php +++ b/src/WebdriverClassicDriver.php @@ -670,10 +670,6 @@ public function executeScript( $this->getWebDriver()->executeScript($script); } - /** - * {@inheritdoc} - * @return mixed - */ public function evaluateScript( #[Language('JavaScript')] string $script @@ -971,7 +967,7 @@ private function executeJsOnXpath( * @throws DriverException */ private function executeJsOnElement( - RemoteWebElement $element, + WebDriverElement $element, #[Language('JavaScript')] string $script ) { @@ -1044,7 +1040,7 @@ private function getWindowHandleFromName(string $name): string } } - private function clickOnElement(RemoteWebElement $element): void + private function clickOnElement(WebDriverElement $element): void { $element->getLocationOnScreenOnceScrolledIntoView(); $element->click(); @@ -1108,14 +1104,11 @@ private function withWindow(?string $name, callable $callback): void */ private function findElement( #[Language('XPath')] - string $xpath, - ?RemoteWebElement $parent = null + string $xpath ): RemoteWebElement { try { $finder = WebDriverBy::xpath($xpath); - return $parent - ? $parent->findElement($finder) - : $this->getWebDriver()->findElement($finder); + return $this->getWebDriver()->findElement($finder); } catch (\Throwable $e) { throw new DriverException("Failed to find element: {$e->getMessage()}", 0, $e); } @@ -1124,7 +1117,7 @@ private function findElement( /** * @throws DriverException */ - private function selectRadioValue(RemoteWebElement $element, string $value): void + private function selectRadioValue(WebDriverElement $element, string $value): void { try { (new WebDriverRadios($element))->selectByValue($value); @@ -1142,7 +1135,7 @@ private function selectRadioValue(RemoteWebElement $element, string $value): voi /** * @throws DriverException */ - private function selectOptionOnElement(RemoteWebElement $element, string $value, bool $multiple = false): void + private function selectOptionOnElement(WebDriverElement $element, string $value, bool $multiple = false): void { try { $select = new WebDriverSelect($element); @@ -1172,7 +1165,7 @@ private function selectOptionOnElement(RemoteWebElement $element, string $value, * * @throws DriverException */ - private function deselectAllOptions(RemoteWebElement $element): void + private function deselectAllOptions(WebDriverElement $element): void { try { (new WebDriverSelect($element))->deselectAll(); @@ -1190,7 +1183,7 @@ private function deselectAllOptions(RemoteWebElement $element): void * @throws DriverException */ private function ensureInputType( - RemoteWebElement $element, + WebDriverElement $element, #[Language('XPath')] string $xpath, string $type, @@ -1233,7 +1226,7 @@ private function jsonEncode($value, string $action, string $field): string * @param mixed $value * @throws DriverException */ - private function setElementDomProperty(RemoteWebElement $element, string $property, $value): void + private function setElementDomProperty(WebDriverElement $element, string $property, $value): void { $this->executeJsOnElement( $element, diff --git a/tests/WebdriverClassicConfig.php b/tests/WebdriverClassicConfig.php index f3d19bd..3dbd06d 100644 --- a/tests/WebdriverClassicConfig.php +++ b/tests/WebdriverClassicConfig.php @@ -21,7 +21,10 @@ public static function getInstance(): self public function createDriver(): WebdriverClassicDriver { - $seleniumHost = $_SERVER['DRIVER_URL']; + $seleniumHost = $_SERVER['DRIVER_URL'] ?? null; + if (!is_string($seleniumHost)) { + throw new \RuntimeException('Selenium host must be specified (as a string) in $_SERVER[DRIVER_URL].'); + } return new WebdriverClassicDriver($this->getBrowserName(), [], $seleniumHost); } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d4d3a01..59207f1 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -4,9 +4,8 @@ use Symfony\Component\Process\Process; -$minkTestServerPort = isset($_SERVER['WEB_FIXTURES_HOST']) - ? parse_url($_SERVER['WEB_FIXTURES_HOST'], PHP_URL_PORT) - : '8002'; +$fixturesHost = $_SERVER['WEB_FIXTURES_HOST'] ?? ''; +$minkTestServerPort = parse_url(is_string($fixturesHost) ? $fixturesHost : '', PHP_URL_PORT) ?: '8002'; $minkTestServer = new Process([ PHP_BINARY,