From 6b551c314873c7c4a0950ca9cb3c51c27635ee3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 24 Aug 2021 12:11:32 +0200 Subject: [PATCH 001/130] Update codestyle, remove php-cs-fixer deprecations --- .php_cs.dist => .php-cs-fixer.dist.php | 68 +++++++++++++------ lib/AbstractWebDriverCheckboxOrRadio.php | 2 +- lib/Net/URLChecker.php | 1 + lib/Remote/HttpCommandExecutor.php | 2 +- lib/Remote/RemoteMouse.php | 2 +- lib/Remote/RemoteWebDriver.php | 4 +- lib/Remote/RemoteWebElement.php | 3 +- lib/Remote/Service/DriverCommandExecutor.php | 2 +- lib/Remote/Service/DriverService.php | 2 +- lib/Support/Events/EventFiringWebDriver.php | 2 + .../Events/EventFiringWebDriverNavigation.php | 2 + lib/Support/Events/EventFiringWebElement.php | 3 + lib/WebDriverExpectedCondition.php | 1 + lib/WebDriverWait.php | 2 +- lib/WebDriverWindow.php | 2 +- .../Chrome/ChromeDevToolsDriverTest.php | 2 +- .../Firefox/FirefoxDriverServiceTest.php | 4 +- tests/functional/RemoteKeyboardTest.php | 2 +- tests/functional/RemoteWebElementTest.php | 2 + tests/functional/WebDriverActionsTest.php | 2 +- tests/functional/WebDriverTestCase.php | 6 +- tests/functional/WebDriverWindowTest.php | 6 +- tests/functional/web/slow_pixel.png.php | 3 +- tests/unit/Firefox/FirefoxOptionsTest.php | 2 +- 24 files changed, 82 insertions(+), 45 deletions(-) rename .php_cs.dist => .php-cs-fixer.dist.php (58%) diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 58% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index a438f2095..5ffcc38ed 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -3,23 +3,34 @@ $finder = PhpCsFixer\Finder::create() ->in([__DIR__ . '/lib', __DIR__ . '/tests']); -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setRules([ '@PSR2' => true, 'array_syntax' => ['syntax' => 'short'], 'binary_operator_spaces' => true, - 'blank_line_before_return' => true, + 'blank_line_before_statement' => ['statements' => ['return', 'try']], + 'braces' => ['allow_single_line_anonymous_class_with_empty_body' => true, 'allow_single_line_closure' => true], 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => ['method' => 'one']], + 'clean_namespace' => true, + 'compact_nullable_typehint' => true, 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => true, + 'fopen_flag_order' => true, + 'fopen_flags' => true, + 'full_opening_tag' => true, 'function_typehint_space' => true, - 'general_phpdoc_annotation_remove' => ['author'], 'implode_call' => true, 'is_null' => true, + 'lambda_not_used_import' => true, 'linebreak_after_opening_tag' => true, 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, 'mb_str_functions' => true, - 'method_separation' => true, 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, 'new_with_braces' => true, 'no_alias_functions' => true, 'no_blank_lines_after_class_opening' => true, @@ -27,17 +38,23 @@ 'no_empty_comment' => true, 'no_empty_phpdoc' => true, 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => [ - 'use', - 'break', - 'continue', - 'extra', - 'return', - 'throw', - 'useTrait', - 'curly_brace_block', - 'parenthesis_brace_block', - 'square_brace_block', + 'normalize_index_brace' => true, + 'no_extra_blank_lines' => [ + 'tokens' => [ + 'break', + 'case', + 'continue', + 'curly_brace_block', + 'default', + 'extra', + 'parenthesis_brace_block', + 'return', + 'square_brace_block', + 'switch', + 'throw', + 'use', + 'use_trait', + ], ], 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, @@ -47,6 +64,7 @@ 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, + 'no_useless_sprintf' => true, 'no_whitespace_in_blank_line' => true, 'object_operator_without_whitespace' => true, 'ordered_class_elements' => true, @@ -55,11 +73,10 @@ 'php_unit_dedicate_assert' => false, 'php_unit_expectation' => ['target' => '5.6'], 'php_unit_method_casing' => ['case' => 'camel_case'], - 'php_unit_mock' => true, 'php_unit_mock_short_will_return' => true, + 'php_unit_mock' => true, 'php_unit_namespaced' => ['target' => '5.7'], 'php_unit_no_expectation_annotation' => true, - 'php_unit_ordered_covers' => true, 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'phpdoc_add_missing_param_annotation' => true, @@ -67,25 +84,34 @@ 'phpdoc_no_access' => true, 'phpdoc_no_empty_return' => true, 'phpdoc_no_package' => true, + 'phpdoc_order_by_value' => ['annotations' => ['covers', 'group', 'throws']], 'phpdoc_order' => true, + 'phpdoc_return_self_reference' => true, 'phpdoc_scalar' => true, 'phpdoc_single_line_var_spacing' => true, 'phpdoc_trim' => true, 'phpdoc_types' => true, - 'psr4' => true, + 'phpdoc_var_annotation_correct_order' => true, + 'psr_autoloading' => true, 'self_accessor' => true, + 'set_type_to_cast' => true, 'short_scalar_cast' => true, 'single_blank_line_before_namespace' => true, 'single_quote' => true, + 'single_space_after_construct' => true, + 'single_trait_insert_per_statement' => true, 'space_after_semicolon' => true, 'standardize_not_equals' => true, + 'strict_param' => true, + 'switch_continue_to_break' => true, 'ternary_operator_spaces' => true, - 'trailing_comma_in_multiline_array' => true, + 'ternary_to_elvis_operator' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], 'trim_array_spaces' => true, 'unary_operator_spaces' => true, - 'visibility_required' => true, + 'visibility_required' => ['elements' => ['method', 'property']], 'whitespace_after_comma_in_array' => true, - 'yoda_style' => false, + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], ]) ->setRiskyAllowed(true) ->setFinder($finder); diff --git a/lib/AbstractWebDriverCheckboxOrRadio.php b/lib/AbstractWebDriverCheckboxOrRadio.php index 9cd04c967..450bc387e 100644 --- a/lib/AbstractWebDriverCheckboxOrRadio.php +++ b/lib/AbstractWebDriverCheckboxOrRadio.php @@ -66,7 +66,7 @@ public function getFirstSelectedOption() } throw new NoSuchElementException( - sprintf('No %s are selected', 'radio' === $this->type ? 'radio buttons' : 'checkboxes') + sprintf('No %s are selected', $this->type === 'radio' ? 'radio buttons' : 'checkboxes') ); } diff --git a/lib/Net/URLChecker.php b/lib/Net/URLChecker.php index a4ff2df00..05e9cf1f6 100644 --- a/lib/Net/URLChecker.php +++ b/lib/Net/URLChecker.php @@ -59,6 +59,7 @@ private function getHTTPResponseCode($url) curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, self::CONNECT_TIMEOUT_MS); $code = null; + try { curl_exec($ch); $info = curl_getinfo($ch); diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 69790e0e1..ea625c3c6 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -296,7 +296,7 @@ public function execute(WebDriverCommand $command) curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); } - if (in_array($http_method, ['POST', 'PUT'])) { + if (in_array($http_method, ['POST', 'PUT'], true)) { // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy // https://tools.ietf.org/html/rfc7231#section-5.1.1 curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_merge(static::DEFAULT_HTTP_HEADERS, ['Expect:'])); diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index 3495ea745..d2429967e 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -116,7 +116,7 @@ public function doubleClick(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $clickActions = $this->createClickActions(); - $moveAction = null === $where ? [] : [$this->createMoveAction($where)]; + $moveAction = $where === null ? [] : [$this->createMoveAction($where)]; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 215b7fd21..cb162265d 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -368,9 +368,7 @@ public function executeAsyncScript($script, array $arguments = []) */ public function takeScreenshot($save_as = null) { - $screenshot = base64_decode( - $this->execute(DriverCommand::SCREENSHOT) - ); + $screenshot = base64_decode($this->execute(DriverCommand::SCREENSHOT), true); if ($save_as !== null) { $directoryPath = dirname($save_as); diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 6dc379378..2554848a0 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -478,7 +478,8 @@ public function takeElementScreenshot($save_as = null) $this->executor->execute( DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $this->id] - ) + ), + true ); if ($save_as !== null) { diff --git a/lib/Remote/Service/DriverCommandExecutor.php b/lib/Remote/Service/DriverCommandExecutor.php index 36dc2f751..d16ec943d 100644 --- a/lib/Remote/Service/DriverCommandExecutor.php +++ b/lib/Remote/Service/DriverCommandExecutor.php @@ -28,8 +28,8 @@ public function __construct(DriverService $service) /** * @param WebDriverCommand $command * - * @throws WebDriverException * @throws \Exception + * @throws WebDriverException * @return WebDriverResponse */ public function execute(WebDriverCommand $command) diff --git a/lib/Remote/Service/DriverService.php b/lib/Remote/Service/DriverService.php index 8b8914815..237ca46b9 100644 --- a/lib/Remote/Service/DriverService.php +++ b/lib/Remote/Service/DriverService.php @@ -167,7 +167,7 @@ private function createProcess() { // BC: ProcessBuilder deprecated since Symfony 3.4 and removed in Symfony 4.0. if (class_exists(ProcessBuilder::class) - && false === mb_strpos('@deprecated', (new \ReflectionClass(ProcessBuilder::class))->getDocComment()) + && mb_strpos('@deprecated', (new \ReflectionClass(ProcessBuilder::class))->getDocComment()) === false ) { $processBuilder = (new ProcessBuilder()) ->setPrefix($this->executable) diff --git a/lib/Support/Events/EventFiringWebDriver.php b/lib/Support/Events/EventFiringWebDriver.php index 81a27816b..a4a74e346 100644 --- a/lib/Support/Events/EventFiringWebDriver.php +++ b/lib/Support/Events/EventFiringWebDriver.php @@ -63,6 +63,7 @@ public function getWebDriver() public function get($url) { $this->dispatch('beforeNavigateTo', $url, $this); + try { $this->driver->get($url); } catch (WebDriverException $exception) { @@ -162,6 +163,7 @@ public function executeAsyncScript($script, array $arguments = []) } $this->dispatch('beforeScript', $script, $this); + try { $result = $this->driver->executeAsyncScript($script, $arguments); } catch (WebDriverException $exception) { diff --git a/lib/Support/Events/EventFiringWebDriverNavigation.php b/lib/Support/Events/EventFiringWebDriverNavigation.php index 59eab3fd7..5070076d8 100644 --- a/lib/Support/Events/EventFiringWebDriverNavigation.php +++ b/lib/Support/Events/EventFiringWebDriverNavigation.php @@ -49,6 +49,7 @@ public function back() 'beforeNavigateBack', $this->getDispatcher()->getDefaultDriver() ); + try { $this->navigator->back(); } catch (WebDriverException $exception) { @@ -68,6 +69,7 @@ public function forward() 'beforeNavigateForward', $this->getDispatcher()->getDefaultDriver() ); + try { $this->navigator->forward(); } catch (WebDriverException $exception) { diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index 0e9271665..a2fe4bb97 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -56,6 +56,7 @@ public function getElement() public function sendKeys($value) { $this->dispatch('beforeChangeValueOf', $this); + try { $this->element->sendKeys($value); } catch (WebDriverException $exception) { @@ -74,6 +75,7 @@ public function sendKeys($value) public function click() { $this->dispatch('beforeClickOn', $this); + try { $this->element->click(); } catch (WebDriverException $exception) { @@ -129,6 +131,7 @@ public function findElements(WebDriverBy $by) $this, $this->dispatcher->getDefaultDriver() ); + try { $elements = []; foreach ($this->element->findElements($by) as $element) { diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 02d2e71e0..26914a161 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -422,6 +422,7 @@ function (WebDriver $driver) use ($visibility_of_element_located) { $visibility_of_element_located->getApply(), $driver ); + try { if ($element !== null && $element->isEnabled()) { return $element; diff --git a/lib/WebDriverWait.php b/lib/WebDriverWait.php index 34dd057ae..5b2009b3c 100644 --- a/lib/WebDriverWait.php +++ b/lib/WebDriverWait.php @@ -38,9 +38,9 @@ public function __construct(WebDriver $driver, $timeout_in_second = null, $inter * @param callable|WebDriverExpectedCondition $func_or_ec * @param string $message * + * @throws \Exception * @throws NoSuchElementException * @throws TimeoutException - * @throws \Exception * @return mixed The return value of $func_or_ec */ public function until($func_or_ec, $message = '') diff --git a/lib/WebDriverWindow.php b/lib/WebDriverWindow.php index e212f92ed..57754eb76 100644 --- a/lib/WebDriverWindow.php +++ b/lib/WebDriverWindow.php @@ -175,7 +175,7 @@ public function getScreenOrientation() public function setScreenOrientation($orientation) { $orientation = mb_strtoupper($orientation); - if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'])) { + if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'], true)) { throw new IndexOutOfBoundsException( 'Orientation must be either PORTRAIT, or LANDSCAPE' ); diff --git a/tests/functional/Chrome/ChromeDevToolsDriverTest.php b/tests/functional/Chrome/ChromeDevToolsDriverTest.php index acbedb806..b927c73d6 100644 --- a/tests/functional/Chrome/ChromeDevToolsDriverTest.php +++ b/tests/functional/Chrome/ChromeDevToolsDriverTest.php @@ -6,10 +6,10 @@ use Facebook\WebDriver\WebDriverTestCase; /** - * @group exclude-saucelabs * @group exclude-edge * @group exclude-firefox * @group exclude-safari + * @group exclude-saucelabs */ class ChromeDevToolsDriverTest extends WebDriverTestCase { diff --git a/tests/functional/Firefox/FirefoxDriverServiceTest.php b/tests/functional/Firefox/FirefoxDriverServiceTest.php index e39259e2b..ce110acb7 100644 --- a/tests/functional/Firefox/FirefoxDriverServiceTest.php +++ b/tests/functional/Firefox/FirefoxDriverServiceTest.php @@ -6,10 +6,10 @@ use PHPUnit\Framework\TestCase; /** - * @group exclude-saucelabs - * @group exclude-edge * @group exclude-chrome + * @group exclude-edge * @group exclude-safari + * @group exclude-saucelabs * @covers \Facebook\WebDriver\Firefox\FirefoxDriverService */ class FirefoxDriverServiceTest extends TestCase diff --git a/tests/functional/RemoteKeyboardTest.php b/tests/functional/RemoteKeyboardTest.php index 947c8b6f9..a97b3343d 100644 --- a/tests/functional/RemoteKeyboardTest.php +++ b/tests/functional/RemoteKeyboardTest.php @@ -10,12 +10,12 @@ class RemoteKeyboardTest extends WebDriverTestCase use RetrieveEventsTrait; /** + * @group exclude-edge * @group exclude-firefox * Firefox does not properly support keyboard actions: * https://github.com/mozilla/geckodriver/issues/245 * https://github.com/mozilla/geckodriver/issues/646 * https://github.com/mozilla/geckodriver/issues/944 - * @group exclude-edge * @group exclude-safari * https://feedbackassistant.apple.com/feedback/9051272 */ diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index f10c7487b..20a9a3882 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -193,6 +193,7 @@ public function testGeckoDriverShouldClickNotInteractable() $this->driver->get($this->getTestPageUrl('gecko653.html')); $linkElement = $this->driver->findElement(WebDriverBy::id('a-index-plain-hidden')); + try { $linkElement->click(); $this->fail('No exception was thrown when clicking an inaccessible link'); @@ -201,6 +202,7 @@ public function testGeckoDriverShouldClickNotInteractable() } $linkElement = $this->driver->findElement(WebDriverBy::id('a-index-hidden-block-child')); + try { $linkElement->click(); $this->fail('No exception was thrown when clicking an inaccessible link'); diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 91336c473..8a01fb998 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -184,9 +184,9 @@ public function testShouldMoveByOffset() } /** - * @group exclude-saucelabs * @group exclude-safari * https://developer.apple.com/forums/thread/662677 + * @group exclude-saucelabs */ public function testShouldDragAndDrop() { diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 74c4d41e9..dbd6606ac 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -133,10 +133,10 @@ public static function skipForJsonWireProtocol($message = 'Not supported by Json public static function skipForUnmatchedBrowsers($browsers = [], $message = null) { $browserName = (string) getenv('BROWSER_NAME'); - if (array_search($browserName, $browsers) === false) { + if (!in_array($browserName, $browsers, true)) { if (!$message) { - $browserlist = implode(', ', $browsers); - $message = 'Browser ' . $browserName . ' not supported for this test (' . $browserlist . ')'; + $browserList = implode(', ', $browsers); + $message = 'Browser ' . $browserName . ' not supported for this test (' . $browserList . ')'; } static::markTestSkipped($message); diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php index 3b100d8f7..3cd91e587 100644 --- a/tests/functional/WebDriverWindowTest.php +++ b/tests/functional/WebDriverWindowTest.php @@ -49,8 +49,8 @@ public function testShouldMaximizeWindow() } /** - * @group exclude-saucelabs * @group exclude-edge + * @group exclude-saucelabs */ public function testShouldFullscreenWindow() { @@ -74,10 +74,10 @@ public function testShouldFullscreenWindow() } /** - * @group exclude-saucelabs + * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1038050 * @group exclude-chrome * @group exclude-safari - * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1038050 + * @group exclude-saucelabs */ public function testShouldMinimizeWindow() { diff --git a/tests/functional/web/slow_pixel.png.php b/tests/functional/web/slow_pixel.png.php index d95805e21..a834089ad 100644 --- a/tests/functional/web/slow_pixel.png.php +++ b/tests/functional/web/slow_pixel.png.php @@ -6,5 +6,6 @@ header('Content-Type: image/png'); echo base64_decode( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjY' - . 'AAAAAIAAeIhvDMAAAAASUVORK5CYII=' + . 'AAAAAIAAeIhvDMAAAAASUVORK5CYII=', + true ); diff --git a/tests/unit/Firefox/FirefoxOptionsTest.php b/tests/unit/Firefox/FirefoxOptionsTest.php index cd6948167..332f55de9 100644 --- a/tests/unit/Firefox/FirefoxOptionsTest.php +++ b/tests/unit/Firefox/FirefoxOptionsTest.php @@ -6,7 +6,7 @@ class FirefoxOptionsTest extends TestCase { - /** @var [] */ + /** @var array */ const EXPECTED_DEFAULT_PREFS = [ FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED => false, FirefoxPreferences::DEVTOOLS_JSONVIEW => false, From b0d446169e7b3638cfb162b7101144bd793102c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 24 Aug 2021 12:44:06 +0200 Subject: [PATCH 002/130] Move php-cs-fixer to separate composer.json --- .gitattributes | 1 + .gitignore | 2 ++ composer.json | 11 ++++++++--- tools/php-cs-fixer/composer.json | 5 +++++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 tools/php-cs-fixer/composer.json diff --git a/.gitattributes b/.gitattributes index 47a5c0fdf..e6146bc76 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ /scripts export-ignore /tests export-ignore +/tools export-ignore /.gitattributes export-ignore /.gitignore export-ignore /example.php export-ignore diff --git a/.gitignore b/.gitignore index d5605367c..697c5cce5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ composer.phar composer.lock vendor +tools/php-cs-fixer/vendor .php_cs.cache +.php-cs-fixer.cache .phpunit.result.cache phpunit.xml logs/ diff --git a/composer.json b/composer.json index d5d3f22f5..03f038316 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ "facebook/webdriver": "*" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.0", "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", "php-coveralls/php-coveralls": "^2.4", "php-mock/php-mock-phpunit": "^1.1 || ^2.0", @@ -59,6 +58,12 @@ }, "minimum-stability": "beta", "scripts": { + "post-install-cmd": [ + "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction" + ], + "post-update-cmd": [ + "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction" + ], "all": [ "@lint", "@analyze", @@ -66,12 +71,12 @@ ], "analyze": [ "vendor/bin/phpstan analyze -c phpstan.neon --ansi", - "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff --dry-run -vvv --ansi", + "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", "vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/" ], "fix": [ "@composer normalize", - "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff -vvv || exit 0", + "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0", "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" ], "lint": [ diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json new file mode 100644 index 000000000..029b32c27 --- /dev/null +++ b/tools/php-cs-fixer/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "friendsofphp/php-cs-fixer": "^3.0" + } +} From 108ee3c487d351b3f4606f2b5c147efd0b4bcd6e Mon Sep 17 00:00:00 2001 From: William Desportes Date: Wed, 25 Aug 2021 22:11:04 +0200 Subject: [PATCH 003/130] Disable sauce labs tests on forks (#917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Disable sauce labs tests on forks Source: https://github.com/phpmyadmin/phpmyadmin/commit/8021847847714a6546b6af20579b82ab98da59e5 Co-authored-by: Ondřej Machulda --- .github/workflows/sauce-labs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index e183bdfac..427afc6fd 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -8,6 +8,8 @@ on: jobs: tests: runs-on: ubuntu-latest + # Source: https://github.community/t/do-not-run-cron-workflows-in-forks/17636/2 + if: (github.event_name == 'schedule' && github.repository == 'php-webdriver/php-webdriver') || (github.event_name != 'schedule') env: SAUCELABS: 1 SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} From 429d6615750580cf764d478019f1534531216a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 23 Aug 2021 13:53:12 +0200 Subject: [PATCH 004/130] Allow Symfony 6 --- CHANGELOG.md | 2 ++ composer.json | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 085f24450..a765afa00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Allow installation of Symfony 6 components. ## 1.11.1 - 2021-05-21 ### Fixed diff --git a/composer.json b/composer.json index 03f038316..9eb616cf2 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "ext-json": "*", "ext-zip": "*", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" }, "replace": { "facebook/webdriver": "*" @@ -29,7 +29,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" @@ -56,7 +56,7 @@ "tests/functional/" ] }, - "minimum-stability": "beta", + "minimum-stability": "dev", "scripts": { "post-install-cmd": [ "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction" From d4bb05460fa247295b69b401c5ab87892805fd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 26 Aug 2021 14:07:34 +0200 Subject: [PATCH 005/130] Add getDomProperty method to read IDL properties of elements (fixes #929) --- CHANGELOG.md | 3 +++ lib/Remote/RemoteWebElement.php | 25 +++++++++++++++++++++++ lib/WebDriverElement.php | 15 +++++++++++++- tests/functional/RemoteWebElementTest.php | 25 ++++++++++++++++++++++- tests/functional/web/index.html | 7 ++++++- 5 files changed, 72 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a765afa00..6e62cdcf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode. + ### Changed - Allow installation of Symfony 6 components. diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 2554848a0..216044279 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -3,6 +3,7 @@ namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\ElementNotInteractableException; +use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; @@ -132,6 +133,8 @@ public function findElements(WebDriverBy $by) /** * Get the value of the given attribute of the element. + * Attribute is meant what is declared in the HTML markup of the element. + * To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method. * * @param string $attribute_name The name of the attribute. * @return string|null The value of the attribute. @@ -162,6 +165,28 @@ public function getAttribute($attribute_name) return $this->executor->execute(DriverCommand::GET_ELEMENT_ATTRIBUTE, $params); } + /** + * Gets the value of a IDL JavaScript property of this element (for example `innerHTML`, `tagName` etc.). + * + * @see https://developer.mozilla.org/en-US/docs/Glossary/IDL + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties + * @param string $propertyName + * @return string|null The property's current value or null if the value is not set or the property does not exist. + */ + public function getDomProperty($propertyName) + { + if (!$this->isW3cCompliant) { + throw new UnsupportedOperationException('This method is only supported in W3C mode'); + } + + $params = [ + ':name' => $propertyName, + ':id' => $this->id, + ]; + + return $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); + } + /** * Get the value of a given CSS property. * diff --git a/lib/WebDriverElement.php b/lib/WebDriverElement.php index 538e0a8e5..3409dde18 100644 --- a/lib/WebDriverElement.php +++ b/lib/WebDriverElement.php @@ -22,13 +22,26 @@ public function clear(); public function click(); /** - * Get the value of a the given attribute of the element. + * Get the value of the given attribute of the element. + * Attribute is meant what is declared in the HTML markup of the element. + * To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method. * * @param string $attribute_name The name of the attribute. * @return string|null The value of the attribute. */ public function getAttribute($attribute_name); + /* + * Gets the value of a IDL JavaScript property of this element (for example `innerHTML`, `tagName` etc.). + * + * @see https://developer.mozilla.org/en-US/docs/Glossary/IDL + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties + * @param string $propertyName + * @return string|null The property's current value or null if the value is not set or the property does not exist. + * @todo Add in next major release (BC) + */ + // public function getDomProperty($propertyName); + /** * Get the value of a given CSS property. * diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 20a9a3882..086e905ae 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -42,6 +42,29 @@ public function testShouldGetAttributeValue() $this->assertSame('note', $element->getAttribute('role')); $this->assertSame('height: 5em; border: 1px solid black;', $element->getAttribute('style')); $this->assertSame('text-simple', $element->getAttribute('id')); + $this->assertNull($element->getAttribute('notExisting')); + } + + /** + * @covers ::getDomProperty + */ + public function testShouldGetDomPropertyValue() + { + self::skipForJsonWireProtocol(); + + $this->driver->get($this->getTestPageUrl('index.html')); + + $element = $this->driver->findElement(WebDriverBy::id('div-with-html')); + + $this->assertStringContainsString( + '

This div has some more html inside.

', + $element->getDomProperty('innerHTML') + ); + $this->assertSame('foo bar', $element->getDomProperty('className')); // IDL property + $this->assertSame('foo bar', $element->getAttribute('class')); // HTML attribute should be the same + $this->assertSame('DIV', $element->getDomProperty('tagName')); + $this->assertSame(2, $element->getDomProperty('childElementCount')); + $this->assertNull($element->getDomProperty('notExistingProperty')); } /** @@ -56,7 +79,7 @@ public function testShouldGetLocation() $elementLocation = $element->getLocation(); $this->assertInstanceOf(WebDriverPoint::class, $elementLocation); $this->assertSame(33, $elementLocation->getX()); - $this->assertSame(500, $elementLocation->getY()); + $this->assertSame(550, $elementLocation->getY()); } /** diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index 6107856ed..f94c7bc53 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -60,7 +60,12 @@

Welcome to the php-webdriver testing page.

stripped

-
+
+

This div has some more html inside.

+
+
+ +
Foo
From 94db34ff5a1b1519ee8427b54002134b4232cb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Oct 2021 13:47:19 +0200 Subject: [PATCH 006/130] Do not use unsafe static when accessing private propery --- lib/Remote/DesiredCapabilities.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 5b726406d..a7bde312e 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -30,7 +30,7 @@ public function __construct(array $capabilities = []) public static function createFromW3cCapabilities(array $capabilities = []) { - $w3cToOss = array_flip(static::$ossToW3c); + $w3cToOss = array_flip(self::$ossToW3c); foreach ($w3cToOss as $w3cCapability => $ossCapability) { // Copy W3C capabilities to OSS ones @@ -229,16 +229,16 @@ public function toW3cCompatibleArray() } // Convert capabilities with changed name - if (array_key_exists($capabilityKey, static::$ossToW3c)) { + if (array_key_exists($capabilityKey, self::$ossToW3c)) { if ($capabilityKey === WebDriverCapabilityType::PLATFORM) { - $w3cCapabilities[static::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue); + $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue); // Remove platformName if it is set to "any" - if ($w3cCapabilities[static::$ossToW3c[$capabilityKey]] === 'any') { - unset($w3cCapabilities[static::$ossToW3c[$capabilityKey]]); + if ($w3cCapabilities[self::$ossToW3c[$capabilityKey]] === 'any') { + unset($w3cCapabilities[self::$ossToW3c[$capabilityKey]]); } } else { - $w3cCapabilities[static::$ossToW3c[$capabilityKey]] = $capabilityValue; + $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = $capabilityValue; } } From 8ffa927b270e932449e8015abf4d38bb0eff24b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Oct 2021 17:05:03 +0200 Subject: [PATCH 007/130] Add newSession constructor to create new session command without typehint violation --- CHANGELOG.md | 1 + lib/Chrome/ChromeDriver.php | 9 ++------- lib/Firefox/FirefoxDriver.php | 5 +---- lib/Remote/RemoteWebDriver.php | 6 +----- lib/Remote/WebDriverCommand.php | 14 ++++++++++++-- tests/unit/Remote/WebDriverCommandTest.php | 9 +++++++++ 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e62cdcf0..f36782390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added - `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode. +- `WebDriverCommand::newSession()` constructor to create new session command without violating typehints. ### Changed - Allow installation of Symfony 6 components. diff --git a/lib/Chrome/ChromeDriver.php b/lib/Chrome/ChromeDriver.php index a8feac7b8..1d840eaed 100644 --- a/lib/Chrome/ChromeDriver.php +++ b/lib/Chrome/ChromeDriver.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Local\LocalWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; -use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; use Facebook\WebDriver\Remote\WebDriverCommand; @@ -48,9 +47,7 @@ public static function startUsingDriverService( } $executor = new DriverCommandExecutor($service); - $newSessionCommand = new WebDriverCommand( - null, - DriverCommand::NEW_SESSION, + $newSessionCommand = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], @@ -76,9 +73,7 @@ public static function startUsingDriverService( */ public function startSession(DesiredCapabilities $desired_capabilities) { - $command = new WebDriverCommand( - null, - DriverCommand::NEW_SESSION, + $command = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], diff --git a/lib/Firefox/FirefoxDriver.php b/lib/Firefox/FirefoxDriver.php index 1d525da85..34dd13de2 100644 --- a/lib/Firefox/FirefoxDriver.php +++ b/lib/Firefox/FirefoxDriver.php @@ -4,7 +4,6 @@ use Facebook\WebDriver\Local\LocalWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; -use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; use Facebook\WebDriver\Remote\WebDriverCommand; @@ -44,9 +43,7 @@ public static function startUsingDriverService( } $executor = new DriverCommandExecutor($service); - $newSessionCommand = new WebDriverCommand( - null, - DriverCommand::NEW_SESSION, + $newSessionCommand = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index cb162265d..9bc3c7a89 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -126,11 +126,7 @@ public static function create( $parameters['desiredCapabilities'] = (object) $desired_capabilities->toArray(); - $command = new WebDriverCommand( - null, - DriverCommand::NEW_SESSION, - $parameters - ); + $command = WebDriverCommand::newSession($parameters); $response = $executor->execute($command); diff --git a/lib/Remote/WebDriverCommand.php b/lib/Remote/WebDriverCommand.php index e21fbdaae..27f21b605 100644 --- a/lib/Remote/WebDriverCommand.php +++ b/lib/Remote/WebDriverCommand.php @@ -4,7 +4,7 @@ class WebDriverCommand { - /** @var string */ + /** @var string|null */ protected $sessionID; /** @var string */ protected $name; @@ -16,6 +16,7 @@ class WebDriverCommand * @param string $name Constant from DriverCommand * @param array $parameters * @todo In 2.0 force parameters to be an array, then remove is_array() checks in HttpCommandExecutor + * @todo In 2.0 make constructor private. Use by default static `::create()` with sessionID type string. */ public function __construct($session_id, $name, $parameters) { @@ -24,6 +25,15 @@ public function __construct($session_id, $name, $parameters) $this->parameters = $parameters; } + /** + * @return self + */ + public static function newSession(array $parameters) + { + // TODO: In 2.0 call empty constructor and assign properties directly. + return new self(null, DriverCommand::NEW_SESSION, $parameters); + } + /** * @return string */ @@ -33,7 +43,7 @@ public function getName() } /** - * @return string + * @return string|null Could be null for newSession command */ public function getSessionID() { diff --git a/tests/unit/Remote/WebDriverCommandTest.php b/tests/unit/Remote/WebDriverCommandTest.php index 0bf6efb20..8ba7abf33 100644 --- a/tests/unit/Remote/WebDriverCommandTest.php +++ b/tests/unit/Remote/WebDriverCommandTest.php @@ -14,4 +14,13 @@ public function testShouldSetOptionsUsingConstructor() $this->assertSame('bar-baz-name', $command->getName()); $this->assertSame(['foo' => 'bar'], $command->getParameters()); } + + public function testShouldCreateNewSessionCommand() + { + $command = WebDriverCommand::newSession(['bar' => 'baz']); + + $this->assertNull($command->getSessionID()); + $this->assertSame('newSession', $command->getName()); + $this->assertSame(['bar' => 'baz'], $command->getParameters()); + } } From e55e6ab0dc42a48a98b04e5229d319482c6a9908 Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 14 Oct 2021 11:28:08 +0200 Subject: [PATCH 008/130] Fix PHP 8.1 compatibility (#935) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix PHP 8.1 compatibility Co-authored-by: Ondřej Machulda --- lib/Chrome/ChromeOptions.php | 2 ++ lib/Firefox/FirefoxOptions.php | 3 +++ lib/Remote/HttpCommandExecutor.php | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index 6599197b6..7c81955d8 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -4,6 +4,7 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use JsonSerializable; +use ReturnTypeWillChange; /** * The class manages the capabilities in ChromeDriver. @@ -43,6 +44,7 @@ class ChromeOptions implements JsonSerializable * * @return array */ + #[ReturnTypeWillChange] public function jsonSerialize() { return $this->toArray(); diff --git a/lib/Firefox/FirefoxOptions.php b/lib/Firefox/FirefoxOptions.php index fc16fdb75..06e61c3d5 100644 --- a/lib/Firefox/FirefoxOptions.php +++ b/lib/Firefox/FirefoxOptions.php @@ -2,6 +2,8 @@ namespace Facebook\WebDriver\Firefox; +use ReturnTypeWillChange; + /** * Class to manage Firefox-specific capabilities * @@ -101,6 +103,7 @@ public function toArray() return $array; } + #[ReturnTypeWillChange] public function jsonSerialize() { return new \ArrayObject($this->toArray()); diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index ea625c3c6..2d510a038 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -268,7 +268,8 @@ public function execute(WebDriverCommand $command) $http_method = $http_options['method']; $url = $http_options['url']; - $url = str_replace(':sessionId', $command->getSessionID(), $url); + $sessionID = $command->getSessionID(); + $url = str_replace(':sessionId', $sessionID === null ? '' : $sessionID, $url); $params = $command->getParameters(); foreach ($params as $name => $value) { if ($name[0] === ':') { From 0769f3cacbd0de57dc6c27c2d8167d04502bfecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 14 Oct 2021 11:29:23 +0200 Subject: [PATCH 009/130] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f36782390..4254aeb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Allow installation of Symfony 6 components. +### Fixed +- PHP 8.1 compatibility. + ## 1.11.1 - 2021-05-21 ### Fixed - `RemoteWebElement::getLocationOnScreenOnceScrolledIntoView()` was missing polyfill implementation for W3C mode and not working in eg. Safari. From 99d4856ed7dffcdf6a52eccd6551e83d8d557ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 14 Oct 2021 11:30:02 +0200 Subject: [PATCH 010/130] Release version 1.12.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4254aeb41..0d66e7cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog This project versioning adheres to [Semantic Versioning](http://semver.org/). -## Unreleased +## 1.12.0 - 2021-10-14 ### Added - `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode. - `WebDriverCommand::newSession()` constructor to create new session command without violating typehints. From c16f8ee103da236d93d9bed406de49b424c064c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 8 Nov 2021 17:50:46 +0100 Subject: [PATCH 011/130] Fix phpdoc for getAttribute and getDomProperty to match the specs --- lib/Remote/RemoteWebElement.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 216044279..9a4515083 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -137,7 +137,8 @@ public function findElements(WebDriverBy $by) * To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method. * * @param string $attribute_name The name of the attribute. - * @return string|null The value of the attribute. + * @return string|true|null The value of the attribute. If this is boolean attribute, return true if the element + * has it, otherwise return null. */ public function getAttribute($attribute_name) { @@ -171,7 +172,7 @@ public function getAttribute($attribute_name) * @see https://developer.mozilla.org/en-US/docs/Glossary/IDL * @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties * @param string $propertyName - * @return string|null The property's current value or null if the value is not set or the property does not exist. + * @return mixed|null The property's current value or null if the value is not set or the property does not exist. */ public function getDomProperty($propertyName) { From ac1b5fab5691f5231b49cabacf2528d91e94fd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 11 Dec 2021 11:40:57 +0100 Subject: [PATCH 012/130] Normalize composer.json to new schema --- composer.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 9eb616cf2..2b72e5bad 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "name": "php-webdriver/webdriver", - "type": "library", "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", + "license": "MIT", + "type": "library", "keywords": [ "webdriver", "selenium", @@ -10,7 +11,6 @@ "chromedriver" ], "homepage": "https://github.com/php-webdriver/php-webdriver", - "license": "MIT", "require": { "php": "^5.6 || ~7.0 || ^8.0", "ext-curl": "*", @@ -19,9 +19,6 @@ "symfony/polyfill-mbstring": "^1.12", "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" }, - "replace": { - "facebook/webdriver": "*" - }, "require-dev": { "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", "php-coveralls/php-coveralls": "^2.4", @@ -31,12 +28,13 @@ "squizlabs/php_codesniffer": "^3.5", "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" }, + "replace": { + "facebook/webdriver": "*" + }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" }, - "config": { - "sort-packages": true - }, + "minimum-stability": "dev", "autoload": { "psr-4": { "Facebook\\WebDriver\\": "lib/" @@ -56,7 +54,9 @@ "tests/functional/" ] }, - "minimum-stability": "dev", + "config": { + "sort-packages": true + }, "scripts": { "post-install-cmd": [ "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction" From 0d4bc565c6f03369ff8b54772a8be6beb8befbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 5 Jan 2022 13:28:31 +0100 Subject: [PATCH 013/130] Docs: Extend findElement(s) documentation to better explain XPath behavior (#956) --- lib/Remote/RemoteWebElement.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 9a4515083..e0503698e 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -89,6 +89,10 @@ public function click() /** * Find the first WebDriverElement within this element using the given mechanism. * + * When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will + * search the entire document from the root, not just the children (relative context) of this current node. + * Use ".//" to limit your search to the children of this element. + * * @param WebDriverBy $by * @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found. * @see WebDriverBy @@ -109,6 +113,10 @@ public function findElement(WebDriverBy $by) /** * Find all WebDriverElements within this element using the given mechanism. * + * When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will + * search the entire document from the root, not just the children (relative context) of this current node. + * Use ".//" to limit your search to the children of this element. + * * @param WebDriverBy $by * @return RemoteWebElement[] A list of all WebDriverElements, or an empty * array if nothing matches From 289d043ae62eed17cefa3292abd4da7cb938ba7a Mon Sep 17 00:00:00 2001 From: Johnny van de Laar Date: Thu, 6 Jan 2022 11:02:33 +0100 Subject: [PATCH 014/130] Fix PHP 8.1 deprecations in the Cookie class --- lib/Cookie.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index 1005bd0dc..9570f5d6d 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -207,16 +207,19 @@ public function toArray() return $cookie; } + #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookie[$offset]); } + #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->cookie[$offset] : null; } + #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($value === null) { From 25ba0d63a99496095ecb6560b110fb330824e387 Mon Sep 17 00:00:00 2001 From: Johnny van de Laar Date: Thu, 6 Jan 2022 11:13:17 +0100 Subject: [PATCH 015/130] Also offsetUnset --- lib/Cookie.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index 9570f5d6d..b02e0c58f 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -229,6 +229,7 @@ public function offsetSet($offset, $value) } } + #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->cookie[$offset]); From 3d84328de3d7c87edeaf3b5d8213b9c119675bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 29 Mar 2022 15:59:29 +0200 Subject: [PATCH 016/130] Allow composer-normalize plugin --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 2b72e5bad..90f2d0f28 100644 --- a/composer.json +++ b/composer.json @@ -55,6 +55,9 @@ ] }, "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true + }, "sort-packages": true }, "scripts": { From 20b831f76253f492bc5edd11babbdb4e282f0c05 Mon Sep 17 00:00:00 2001 From: tomsykes Date: Wed, 12 Jan 2022 10:20:42 +0000 Subject: [PATCH 017/130] Add PHPDoc return types to ArrayAccess methods, to suppress deprecation warnings in Symfony 5.4/PHP7.4 --- lib/Cookie.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index b02e0c58f..1a31c7df8 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -207,18 +207,27 @@ public function toArray() return $cookie; } + /** + * @return bool + */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookie[$offset]); } + /** + * @return mixed + */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->cookie[$offset] : null; } + /** + * @return void + */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { @@ -229,6 +238,9 @@ public function offsetSet($offset, $value) } } + /** + * @return void + */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { From 301923820e757cc867c0bfc18e79f09e3c0cbb0f Mon Sep 17 00:00:00 2001 From: tomsykes Date: Wed, 12 Jan 2022 10:32:28 +0000 Subject: [PATCH 018/130] Adding param annotations Automated checks failed previously due to lack of param annotations --- lib/Cookie.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index 1a31c7df8..77b04687c 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -208,6 +208,7 @@ public function toArray() } /** + * @param mixed $offset * @return bool */ #[\ReturnTypeWillChange] @@ -217,6 +218,7 @@ public function offsetExists($offset) } /** + * @param mixed $offset * @return mixed */ #[\ReturnTypeWillChange] @@ -226,6 +228,8 @@ public function offsetGet($offset) } /** + * @param mixed $offset + * @param mixed $value * @return void */ #[\ReturnTypeWillChange] @@ -239,6 +243,7 @@ public function offsetSet($offset, $value) } /** + * @param mixed $offset * @return void */ #[\ReturnTypeWillChange] From 1ddc140bd636080979cc28021d0a23fddcafba7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 29 Mar 2022 15:54:00 +0200 Subject: [PATCH 019/130] Disable phpdoc_no_empty_return fixer to allow forward compatibility with PHP 8.1 --- .php-cs-fixer.dist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5ffcc38ed..051fb6579 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -82,7 +82,7 @@ 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, - 'phpdoc_no_empty_return' => true, + // 'phpdoc_no_empty_return' => true, // disabled to allow forward compatibility with PHP 8.1 'phpdoc_no_package' => true, 'phpdoc_order_by_value' => ['annotations' => ['covers', 'group', 'throws']], 'phpdoc_order' => true, From 6fb804b74e3ec74816be4d5111f0591824026205 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Fri, 16 Apr 2021 15:22:42 +0200 Subject: [PATCH 020/130] use Doctum to re-build API docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Ondřej Machulda --- .github/workflows/docs-lint.yml | 23 ++++++++++++++ .github/workflows/docs-publish.yml | 35 +++++++++++++++++++++ .gitignore | 1 + scripts/docs-template.html | 7 +++++ scripts/doctum.php | 30 ++++++++++++++++++ scripts/update-built-docs.sh | 50 ++++++++++++++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 .github/workflows/docs-lint.yml create mode 100755 .github/workflows/docs-publish.yml create mode 100644 scripts/docs-template.html create mode 100644 scripts/doctum.php create mode 100755 scripts/update-built-docs.sh diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml new file mode 100644 index 000000000..56667ce70 --- /dev/null +++ b/.github/workflows/docs-lint.yml @@ -0,0 +1,23 @@ +name: Lint PHP documentation + +on: + push: + pull_request: + branches: + - 'main' + +jobs: + lint-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Lint PHP documentation + uses: sudo-bot/action-doctum@v5 + with: + config-file: scripts/doctum.php + method: 'parse' + cli-args: '--output-format=github --no-ansi --no-progress -v --ignore-parse-errors' diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml new file mode 100755 index 000000000..35941e2dd --- /dev/null +++ b/.github/workflows/docs-publish.yml @@ -0,0 +1,35 @@ +name: Publish API documentation + +on: + repository_dispatch: + types: [ run-build-api-docs ] + workflow_dispatch: + schedule: + - cron: "00 12 * * *" + +jobs: + publish-pages: + environment: + name: API documentation + url: https://php-webdriver.github.io/php-webdriver/ + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.SSH_KEY_DEPLOY }} + + - name: Build PHP documentation + uses: sudo-bot/action-doctum@v5 + with: + config-file: scripts/doctum.php + method: 'update' + cli-args: '--output-format=github --no-ansi --no-progress -v --ignore-parse-errors' + + - name: Set commit author + run: | + git config user.name "Automated" + git config user.email "actions@users.noreply.github.com" + - name: Push the changes + run: ./scripts/update-built-docs.sh diff --git a/.gitignore b/.gitignore index 697c5cce5..a9384110d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ tools/php-cs-fixer/vendor .phpunit.result.cache phpunit.xml logs/ +build/ # generic files to ignore *.lock diff --git a/scripts/docs-template.html b/scripts/docs-template.html new file mode 100644 index 000000000..4814f5ec5 --- /dev/null +++ b/scripts/docs-template.html @@ -0,0 +1,7 @@ + + + + + Taking you to the latest documentation. + + \ No newline at end of file diff --git a/scripts/doctum.php b/scripts/doctum.php new file mode 100644 index 000000000..9cb2cc784 --- /dev/null +++ b/scripts/doctum.php @@ -0,0 +1,30 @@ +files() + ->name('*.php') + ->in($srcRoot . 'lib'); + +$versions = GitVersionCollection::create($srcRoot) + ->addFromTags('1.*') // only latest minor version + ->addFromTags('0.6.0') + ->add('main', 'main branch') +; + +return new Doctum($iterator, [ + 'title' => 'php-webdriver API', + 'theme' => 'default', + 'build_dir' => $root . '/build/dist/%version%/', + 'cache_dir' => $root . '/build/cache/%version%/', + 'include_parent_data' => true, + 'remote_repository' => new GitHubRemoteRepository('php-webdriver/php-webdriver', $srcRoot), + 'versions' => $versions, + 'base_url' => 'https://php-webdriver.github.io/php-webdriver/%version/' +]); diff --git a/scripts/update-built-docs.sh b/scripts/update-built-docs.sh new file mode 100755 index 000000000..76646bed5 --- /dev/null +++ b/scripts/update-built-docs.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -e + +cleanup() { + git ls-files ./ | xargs -r -n 1 rm + rm -rfd ./* +} + +copyToTemp() { + TEMP_DIR="$(mktemp -d --suffix=_doctum-build-php-webdriver)" + cp -rp build/dist/* "${TEMP_DIR}" + cp ./scripts/docs-template.html "${TEMP_DIR}/index.html" +} + +emptyAndRemoveTemp() { + mv "${TEMP_DIR}"/* ./ + # Create symlink for main to latest + ln -s -r ./main ./latest + # Create symlink for main to master + ln -s -r ./main ./master + # Create symlink for main to community + ln -s -r ./main ./community + rm -rf "${TEMP_DIR}" +} + +commitAndPushChanges() { + # Push the changes, only if there is changes + git add -A + git diff-index --quiet HEAD || git commit -m "Api documentations update ($(date --rfc-3339=seconds --utc))" -m "#apidocs" && if [ -z "${SKIP_PUSH}" ]; then git push; fi +} + +if [ ! -d ./build/dist ]; then + echo 'Missing built docs' + exit 1 +fi + +# Remove cache dir, do not upload it +rm -rf ./build/cache + +copyToTemp +# Remove build dir, do not upload it +rm -rf ./build + +git checkout gh-pages + +cleanup +emptyAndRemoveTemp +commitAndPushChanges + +git checkout - > /dev/null From de0b3f0714a0a48f48b3c02c34e075dc27141105 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Mon, 2 May 2022 13:35:41 +0200 Subject: [PATCH 021/130] Bump actions/checkout to v3 --- .github/workflows/docs-publish.yml | 2 +- .github/workflows/sauce-labs.yaml | 2 +- .github/workflows/tests.yaml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 35941e2dd..02a50534b 100755 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 ssh-key: ${{ secrets.SSH_KEY_DEPLOY }} diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index 427afc6fd..878743ef1 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -26,7 +26,7 @@ jobs: name: ${{ matrix.name }} (${{ matrix.tunnel-id }}) steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 45e5761e9..ccf4bd0c0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -11,7 +11,7 @@ jobs: name: "Code style and static analysis" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -32,7 +32,7 @@ jobs: name: "Markdown link check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' @@ -50,7 +50,7 @@ jobs: - { php-version: '8.1', dependencies: '--ignore-platform-req=php' } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -101,7 +101,7 @@ jobs: name: "Functional tests (${{ matrix.browser }}, Selenium server: ${{ matrix.selenium-server }}, W3C: ${{ matrix.w3c }})" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 From f5d8821d33d0ff791fc4a238d1551ad830f97053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 2 May 2022 14:18:32 +0200 Subject: [PATCH 022/130] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d66e7cf6..47445f7a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog This project versioning adheres to [Semantic Versioning](http://semver.org/). +## Unreleased +### Fixed +- Improper PHP documentation for `getAttribute()` and `getDomProperty()`. +- Unsafe use of `static::` when accessing private property in `DesiredCapabilities`. +- PHP 8.1 deprecations in the `Cookie` class. + +### Changed +- Docs: Extend `findElement()`/`findElements()` method documentation to better explain XPath behavior. +- Add `@return` and `@param` type annotations to Cookie class to avoid deprecations in PHP 8.1. + ## 1.12.0 - 2021-10-14 ### Added - `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode. From 3cca205d3d0d72eb0e191488fac76cfed99a17f4 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Tue, 3 May 2022 11:36:28 +0200 Subject: [PATCH 023/130] Fix #977 - broken open-search URL --- scripts/doctum.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/doctum.php b/scripts/doctum.php index 9cb2cc784..08e722917 100644 --- a/scripts/doctum.php +++ b/scripts/doctum.php @@ -24,7 +24,7 @@ 'build_dir' => $root . '/build/dist/%version%/', 'cache_dir' => $root . '/build/cache/%version%/', 'include_parent_data' => true, - 'remote_repository' => new GitHubRemoteRepository('php-webdriver/php-webdriver', $srcRoot), + 'remote_repository' => new GitHubRemoteRepository('php-webdriver/php-webdriver', $srcRoot), 'versions' => $versions, - 'base_url' => 'https://php-webdriver.github.io/php-webdriver/%version/' + 'base_url' => 'https://php-webdriver.github.io/php-webdriver/%version%/' ]); From b27ddf458d273c7d4602106fcaf978aa0b7fe15a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 3 May 2022 14:08:51 +0200 Subject: [PATCH 024/130] Release version 1.12.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47445f7a7..f90ff9039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.12.1 - 2022-05-03 ### Fixed - Improper PHP documentation for `getAttribute()` and `getDomProperty()`. - Unsafe use of `static::` when accessing private property in `DesiredCapabilities`. From eed18d3ce1cce6c1833dacced516b43bef75e916 Mon Sep 17 00:00:00 2001 From: Nikhil <6836536+qwertynik@users.noreply.github.com> Date: Tue, 28 Jun 2022 19:20:07 +0530 Subject: [PATCH 025/130] Removing link to deprecated site The linked site for Chromedriver will soon be deprecated. Adding link to the latest site. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 689a497ab..2eb411645 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ This could be Selenium standalone server, but for local development, you can sen 📙 Below you will find a simple example. Make sure to read our wiki for [more information on Chrome/Chromedriver](https://github.com/php-webdriver/php-webdriver/wiki/Chrome). -Install the latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads). +Install the latest Chrome and [Chromedriver](https://sites.google.com/chromium.org/driver/downloads). Make sure to have a compatible version of Chromedriver and Chrome! Run `chromedriver` binary, you can pass `port` argument, so that it listens on port 4444: From e31d1f792ec71e777de1bee71e1260df2108d078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 23 Aug 2022 18:07:24 +0200 Subject: [PATCH 026/130] Replace no-response probot with 'lee-dohm/no-response' action --- .github/no-response.yml | 17 ----------------- .github/workflows/no-response.yaml | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) delete mode 100644 .github/no-response.yml create mode 100644 .github/workflows/no-response.yaml diff --git a/.github/no-response.yml b/.github/no-response.yml deleted file mode 100644 index 320baf22f..000000000 --- a/.github/no-response.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Configuration for probot-no-response - https://github.com/probot/no-response - -# Number of days of inactivity before an Issue is closed for lack of response -daysUntilClose: 14 - -# Label requiring a response -responseRequiredLabel: "waiting for reaction" - -# Comment to post when closing an Issue for lack of response. Set to `false` to disable -closeComment: > - This issue has been automatically closed because there has been no response - to our request for more information from the original author. With only the - information that is currently in the issue, we don't have enough information - to take action. - - If the original issue author adds comment with more information, - this issue will be automatically reopened and we can investigate further. diff --git a/.github/workflows/no-response.yaml b/.github/workflows/no-response.yaml new file mode 100644 index 000000000..2970c7d72 --- /dev/null +++ b/.github/workflows/no-response.yaml @@ -0,0 +1,26 @@ +name: No Response + +# Both `issue_comment` and `scheduled` event types are required for this Action to work properly. +on: + issue_comment: + types: [created] + schedule: + - cron: '33 * * * *' # every hour at :33 + +jobs: + noResponse: + runs-on: ubuntu-latest + steps: + - uses: lee-dohm/no-response@v0.5.0 + with: + token: ${{ github.token }} + daysUntilClose: 14 + responseRequiredLabel: 'waiting for reaction' + closeComment: > + This issue has been automatically closed because there has been no response + to our request for more information from the original author. With only the + information that is currently in the issue, we don't have enough information + to take action. + + If the original issue author adds comment with more information, + this issue will be automatically reopened and we can investigate further. From 518a8822432a8440864c40154a5c5fd44a38e9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 23 Aug 2022 13:28:31 +0200 Subject: [PATCH 027/130] Mark getAllSessions() deprecated in W3C WebDriver --- lib/Remote/RemoteWebDriver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 9bc3c7a89..fc068a8f3 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -555,6 +555,7 @@ public function getCapabilities() /** * Returns a list of the currently active sessions. * + * @deprecated Removed in W3C WebDriver. * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param int $timeout_in_ms * @return array From c51f35d9c6681e890a6d0a05b26cfc463bbc990c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 24 Aug 2022 17:11:09 +0200 Subject: [PATCH 028/130] Separate takeScreenshot logic to better handle errors, DRY and to fix PHP 8.1 deprecations --- CHANGELOG.md | 2 + lib/Remote/RemoteWebDriver.php | 15 +-- lib/Remote/RemoteWebElement.php | 20 +--- lib/Support/ScreenshotHelper.php | 77 +++++++++++++++ tests/functional/RemoteWebDriverTest.php | 4 +- tests/functional/RemoteWebElementTest.php | 2 +- tests/unit/Support/ScreenshotHelperTest.php | 103 ++++++++++++++++++++ 7 files changed, 190 insertions(+), 33 deletions(-) create mode 100644 lib/Support/ScreenshotHelper.php create mode 100644 tests/unit/Support/ScreenshotHelperTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f90ff9039..71c0f3904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. ## 1.12.1 - 2022-05-03 ### Fixed diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index fc068a8f3..f45ae8da1 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -4,6 +4,7 @@ use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; +use Facebook\WebDriver\Support\ScreenshotHelper; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverCapabilities; @@ -364,19 +365,7 @@ public function executeAsyncScript($script, array $arguments = []) */ public function takeScreenshot($save_as = null) { - $screenshot = base64_decode($this->execute(DriverCommand::SCREENSHOT), true); - - if ($save_as !== null) { - $directoryPath = dirname($save_as); - - if (!file_exists($directoryPath)) { - mkdir($directoryPath, 0777, true); - } - - file_put_contents($save_as, $screenshot); - } - - return $screenshot; + return (new ScreenshotHelper($this->getExecuteMethod()))->takePageScreenshot($save_as); } /** diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index e0503698e..add916934 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -7,6 +7,7 @@ use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; +use Facebook\WebDriver\Support\ScreenshotHelper; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\WebDriverElement; @@ -508,24 +509,7 @@ public function getID() */ public function takeElementScreenshot($save_as = null) { - $screenshot = base64_decode( - $this->executor->execute( - DriverCommand::TAKE_ELEMENT_SCREENSHOT, - [':id' => $this->id] - ), - true - ); - - if ($save_as !== null) { - $directoryPath = dirname($save_as); - if (!file_exists($directoryPath)) { - mkdir($directoryPath, 0777, true); - } - - file_put_contents($save_as, $screenshot); - } - - return $screenshot; + return (new ScreenshotHelper($this->executor))->takeElementScreenshot($this->id, $save_as); } /** diff --git a/lib/Support/ScreenshotHelper.php b/lib/Support/ScreenshotHelper.php new file mode 100644 index 000000000..5fe18efd1 --- /dev/null +++ b/lib/Support/ScreenshotHelper.php @@ -0,0 +1,77 @@ +executor = $executor; + } + + /** + * @param string|null $saveAs + * @throws WebDriverException + * @return string + */ + public function takePageScreenshot($saveAs = null) + { + $commandToExecute = [DriverCommand::SCREENSHOT]; + + return $this->takeScreenshot($commandToExecute, $saveAs); + } + + public function takeElementScreenshot($elementId, $saveAs = null) + { + $commandToExecute = [DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $elementId]]; + + return $this->takeScreenshot($commandToExecute, $saveAs); + } + + private function takeScreenshot(array $commandToExecute, $saveAs = null) + { + $response = $this->executor->execute(...$commandToExecute); + + if (!is_string($response)) { + throw new WebDriverException('Error taking screenshot, no data received from the remote end'); + } + + $screenshot = base64_decode($response, true); + + if ($screenshot === false) { + throw new WebDriverException('Error decoding screenshot data'); + } + + if ($saveAs !== null) { + $this->saveScreenshotToPath($screenshot, $saveAs); + } + + return $screenshot; + } + + private function saveScreenshotToPath($screenshot, $path) + { + $this->createDirectoryIfNotExists(dirname($path)); + + file_put_contents($path, $screenshot); + } + + private function createDirectoryIfNotExists($directoryPath) + { + if (!file_exists($directoryPath)) { + if (!mkdir($directoryPath, 0777, true) && !is_dir($directoryPath)) { + throw new WebDriverException(sprintf('Directory "%s" was not created', $directoryPath)); + } + } + } +} diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 25b01c40f..984c11978 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -250,6 +250,7 @@ public function testShouldExecuteScriptWithParamsAndReturnValue() /** * @covers ::takeScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper */ public function testShouldTakeScreenshot() { @@ -269,6 +270,7 @@ public function testShouldTakeScreenshot() /** * @covers ::takeScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper * @group exclude-safari * Safari is returning different color profile and it does not have way to configure "force-color-profile" */ @@ -277,7 +279,7 @@ public function testShouldSaveScreenshotToFile() if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - // Intentionally save screenshot to subdirectory to tests it is being created + $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/selenium-screenshot.png'; $this->driver->get($this->getTestPageUrl('index.html')); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 086e905ae..bb6568f6d 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -455,6 +455,7 @@ public function testShouldFindMultipleChildElements() /** * @covers ::takeElementScreenshot + * @covers \Facebook\WebDriver\Support\ScreenshotHelper * @group exclude-saucelabs */ public function testShouldTakeAndSaveElementScreenshot() @@ -469,7 +470,6 @@ public function testShouldTakeAndSaveElementScreenshot() $isCi = (new CiDetector())->isCiDetected(); $isSafari = getenv('BROWSER_NAME') === 'safari'; - // Intentionally save screenshot to subdirectory to tests it is being created $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/element-screenshot.png'; $this->driver->get($this->getTestPageUrl('index.html')); diff --git a/tests/unit/Support/ScreenshotHelperTest.php b/tests/unit/Support/ScreenshotHelperTest.php new file mode 100644 index 000000000..edc3f661f --- /dev/null +++ b/tests/unit/Support/ScreenshotHelperTest.php @@ -0,0 +1,103 @@ +assertDirectoryNotExists($directoryPath); + + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takePageScreenshot($fullFilePath); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + + $this->assertDirectoryExists($directoryPath); + $this->assertFileExists($fullFilePath); + + unlink($fullFilePath); + rmdir($directoryPath); + } + + public function testShouldOnlyReturnBase64IfDirectoryNotProvided() + { + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takePageScreenshot(); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + } + + public function testShouldSaveElementScreenshotToSubdirectoryIfNotExists() + { + $fullFilePath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-', true) . '/screenshot.png'; + $directoryPath = dirname($fullFilePath); + $this->assertDirectoryNotExists($directoryPath); + $elementId = 'foo-id'; + + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with(DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $elementId]) + ->willReturn(self::BLACK_PIXEL); + + $helper = new ScreenshotHelper($executorMock); + $output = $helper->takeElementScreenshot($elementId, $fullFilePath); + + $this->assertSame(base64_decode(self::BLACK_PIXEL, true), $output); + $this->assertDirectoryExists($directoryPath); + $this->assertFileExists($fullFilePath); + + unlink($fullFilePath); + rmdir($directoryPath); + } + + /** + * @dataProvider provideInvalidData + * @param mixed $data + * @param string $expectedExceptionMessage + */ + public function testShouldThrowExceptionWhenInvalidDataReceived($data, $expectedExceptionMessage) + { + $executorMock = $this->createMock(RemoteExecuteMethod::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->equalTo(DriverCommand::SCREENSHOT)) + ->willReturn($data); + + $helper = new ScreenshotHelper($executorMock); + + $this->expectException(WebDriverException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + $helper->takePageScreenshot(); + } + + public function provideInvalidData() + { + return [ + 'empty response' => [null, 'Error taking screenshot, no data received from the remote end'], + 'not valid base64 response' => ['invalid%base64', 'Error decoding screenshot data'], + ]; + } +} From 1dcffc8af811b9485c3df1c1e95577ae39ed2bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 27 Aug 2022 10:37:11 +0200 Subject: [PATCH 029/130] Run functional tests on Selenium server 4.3 --- .github/workflows/tests.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ccf4bd0c0..2cea57936 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -84,7 +84,7 @@ jobs: functional-tests: runs-on: ${{ matrix.os }} env: - SELENIUM_SERVER_DOWNLOAD_URL: http://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar + SELENIUM_SERVER_DOWNLOAD_URL: https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.3.0/selenium-server-4.3.0.jar strategy: fail-fast: false @@ -116,13 +116,13 @@ jobs: - name: Start Selenium standalone server # If you want to run your Selenium WebDriver tests on GitHub actions, we recommend using service containers # with eg. selenium/standalone-chrome image. See https://docs.github.com/en/actions/guides/about-service-containers - # But for the purpose of testing this library itself, we need more control so we set everything up manually. + # But for the purpose of testing this library itself, we need more control, so we set everything up manually. if: ${{ matrix.selenium-server }} run: | - mkdir -p build - wget -q -t 3 -O build/selenium-server-standalone.jar $SELENIUM_SERVER_DOWNLOAD_URL - java -jar build/selenium-server-standalone.jar -version - xvfb-run --server-args="-screen 0, 1280x720x24" --auto-servernum java -jar build/selenium-server-standalone.jar -log logs/selenium-server.log & + mkdir -p build logs + wget -q -t 3 -O build/selenium-server.jar $SELENIUM_SERVER_DOWNLOAD_URL + java -jar build/selenium-server.jar standalone --version + xvfb-run --server-args="-screen 0, 1280x720x24" --auto-servernum java -jar build/selenium-server.jar standalone --log logs/selenium-server.log & - name: Start ChromeDriver if: ${{ !matrix.selenium-server && matrix.browser == 'chrome' }} From 02a17b3843693cc1f994606aa0b7f8d8531ae540 Mon Sep 17 00:00:00 2001 From: Maxim Leontyev Date: Thu, 16 Aug 2018 13:25:05 +0300 Subject: [PATCH 030/130] Add new firefox xpi extensions support --- lib/Firefox/FirefoxProfile.php | 73 ++++++++----- .../functional/Firefox/FirefoxProfileTest.php | 96 ++++++++++++++++++ .../FirefoxWebdriverTestExtension-0.1-fx.xpi | Bin 0 -> 5384 bytes 3 files changed, 143 insertions(+), 26 deletions(-) create mode 100644 tests/functional/Firefox/FirefoxProfileTest.php create mode 100644 tests/functional/Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index cce9baaf4..645d5cae9 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -185,44 +185,65 @@ public function encode() /** * @param string $extension The path to the extension. * @param string $profile_dir The path to the profile directory. + * @throws WebDriverException + * @throws \Exception * @return string The path to the directory of this extension. */ private function installExtension($extension, $profile_dir) { - $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfileExtension'); + $temp_dir = $this->createTempDirectory(); + $this->extractTo($extension, $temp_dir); - // This is a hacky way to parse the id since there is no offical RDF parser library. - // Find the correct namespace for the id element. - $install_rdf_path = $temp_dir . '/install.rdf'; - $xml = simplexml_load_file($install_rdf_path); - $ns = $xml->getDocNamespaces(); - $prefix = ''; - if (!empty($ns)) { - foreach ($ns as $key => $value) { - if (mb_strpos($value, '//www.mozilla.org/2004/em-rdf') > 0) { - if ($key != '') { - $prefix = $key . ':'; // Separate the namespace from the name. - } - break; - } - } + $mozilla_rsa_path = $temp_dir . '/META-INF/mozilla.rsa'; + $mozilla_rsa_binary_data = file_get_contents($mozilla_rsa_path); + $mozilla_rsa_hex = bin2hex($mozilla_rsa_binary_data); + + //We need to find plugin id. This is second occurrence of object identifier "2.5.4.3 commonName" + + //That is marker "2.5.4.3 commonName" in hex: + $object_identifier_hex_marker = '0603550403'; + + $first_marker_pos_in_hex = strpos($mozilla_rsa_hex, $object_identifier_hex_marker); // phpcs:ignore + + $second_marker_pos_in_hex_string = + strpos($mozilla_rsa_hex, $object_identifier_hex_marker, $first_marker_pos_in_hex + 2); + + if ($second_marker_pos_in_hex_string === false) { + throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); } - // Get the extension id from the install manifest. - $matches = []; - preg_match('#<' . $prefix . 'id>([^<]+)#', $xml->asXML(), $matches); - if (isset($matches[1])) { - $ext_dir = $profile_dir . '/extensions/' . $matches[1]; - mkdir($ext_dir, 0777, true); - $this->extractTo($extension, $ext_dir); - } else { - $this->deleteDirectory($temp_dir); - throw new WebDriverException('Cannot get the extension id from the install manifest.'); + $common_name_string_position_in_binary = + ($second_marker_pos_in_hex_string + strlen($object_identifier_hex_marker)) / 2; + + $common_name_string_length = ord($mozilla_rsa_binary_data[$common_name_string_position_in_binary + 1]); + $addon_common_name = substr( + $mozilla_rsa_binary_data, + $common_name_string_position_in_binary + 2, + $common_name_string_length + ); + + if (!preg_match('/^\\{[0-9a-f-]{36}\\}$/', $addon_common_name)) { + throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); } $this->deleteDirectory($temp_dir); + //install extension to profile directory + $ext_dir = $profile_dir . '/extensions/'; + if (!is_dir($ext_dir) && !mkdir($ext_dir, 0777, true) && !is_dir($ext_dir)) { + throw new WebDriverException('Cannot install extension - cannot create directory'); + } + + if (!copy($extension, $ext_dir . $addon_common_name . '.xpi')) { + throw new WebDriverException('Cannot install extension - cannot copy file'); + } + + //extension installation with empty preferences (empty users.js) fails: + if (empty($this->preferences)) { + $this->setPreference('dom.webdriver.enabled', true); + } + return $ext_dir; } diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php new file mode 100644 index 000000000..469308553 --- /dev/null +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -0,0 +1,96 @@ +markTestSkipped('FirefoxProfileTest is run only when running against local firefox'); + } + } + + public function testShouldInstallExtension() + { + $this->desiredCapabilities = DesiredCapabilities::firefox(); + + $firefoxProfile = new FirefoxProfile(); + $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); + + $this->desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); + + $this->driver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); + + $this->driver->get($this->getTestPageUrl('index.html')); + + $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); + + $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); + $this->assertInstanceOf(RemoteWebElement::class, $element); + + $this->driver->quit(); + } + + public function testShouldInstallExtensionInHeadlessMode() + { + $this->desiredCapabilities = DesiredCapabilities::firefox(); + + $firefoxProfile = new FirefoxProfile(); + $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); + + $this->desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); + + $arguments[] = '--headless'; + $this->desiredCapabilities->setCapability('moz:firefoxOptions', ['args' => $arguments]); + + $this->driver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); + + $this->driver->get($this->getTestPageUrl('index.html')); + + $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); + + $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); + $this->assertInstanceOf(RemoteWebElement::class, $element); + + $this->driver->quit(); + } +} diff --git a/tests/functional/Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi b/tests/functional/Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi new file mode 100644 index 0000000000000000000000000000000000000000..854e14c081de304c0143efb610e544e6dd4d9cdb GIT binary patch literal 5384 zcmZ`-2UL^Gwhl!)h!mxl0HH|eO#(;}kS4vCNbfxe7)n5q-a(Kmf*?guiu52o^w1)L zH0fPB2*Jm>=iTGG=bbzM+H1}F*X%tr-`?}hnomaq51$4A01yG1#Wl5TXV0RZkpln+ zlmNgVf3=hi6u@eaR0Q2UpE|p_Squ31T2E#KN~O8a_YT>(nf4;%-AoxOg+&JqP(~%Rv(k648FWm%9(5fOfIKRCbVgUkVi$Cuo{?^i4zHyHn&Aa{eMfSbb*Krw8Km3NXYV3P0_3`$(dCG@9gmp9t(CMPElNe|c^x!{&V})I}3N zy5Zs`X)*&YO5;bN7Z2bq-nz{Plepd&UB7@hX46o}IcqyPVK!Ys7tW|w`;E2wVijE= zaTv{w40$~!fKMUPawtC%JnEm23wLscfw3(++R6)=7SnG8}t0(LXW1UljU(4 zeZU}~oOa5@!_wQ)8k;{Y^1ozUyz3bC(&}%iJ?^+z{@g9_HBFYmfNH2^@x7$f_G|S* z-A;}3?ciw0w_`JZ#IpQ{^q zbhMaA6NOT%W<(rH>{{!5V^@TB}Sw)3%Junt&+GN zskrpfL^h2gLB7NG7%e4+;C8F{?$sm(1zSSJ%n0Lq&kDThaCHfn_7YVJ{9qqL0Eq5| z%i}BdxX@-GTQ=dR@Fanx6PIB{-4(s&uU@biE|`YKVCD_xpm%7)oyNymld;X`bK9aL z=E0`57siK^9XTkS&H-IZ@ZO!}wOJ%3LM*^1n0lpQv)vA`O z#xM45zOn7VR=4$6yoPiprd;8x8+YS2w=OAMp&CVxnF&d1b36%yAi$@kOECWnRK&pQ zlGV)Q)4duMX&+i~FRJwy{Vioyc8_3%0$Xt#i)S$HlLN21quL$i+SEp;?bxFB25aX? zeeS6dzo&N_^5HLbNQcQpMs+N#j|NQCm_R<;u3!A2;KSwRd?)>k%Ycqc2BS zZ{OSI6We>%{KMgpv*M36URPULIRS@b zNEoZ+%sR|D*hcplnYh}L?mxrpW(m2&ce{u;#ObA%ODO{DEoDjM9nx~N9fGkuMZ~6w z`#&no9d-_h1W4bZdhHv7eNFrz3h$?X5)-!G2OkH8U5E;hN@s}Xq)I9X%}Rbwx#=&x zj$;DA0{~bGFLEi`P``k~*wF0;)15$J@vY6_O*>Ry-g&|)OTHEB^w(kr$x(X9B0TBy z2bm9hBv91S%oz3EV%J{Qp6xm6@iRW$hr6{^Ys?MgG(B0utO+h{qvS*DviiOT%UU92=*85Pm#a-&-n+SyR*)g>1jQo% zTAEN*;=EZ|P`x_$MuT>apl-Sqh}d{@SiEFtcx}sq+WJ`Cw+yMIWup+7yHQwrI+@9X z5v^^$sCNnLsKgwWH(d@v$6>Y2BggbNMd>*K z7H?B0TPNO+io^LFYWdzyDj>F$^{eXN`^|!Nn2$^b_+UTj& zue{%H8#HZO|L*0Ota#bgGiI0$gFGQK;U~8a0L2PE8zcVtT@865`5^srw@olWFwUHs z;QR7&xI8ja?Gb>F0_)g;<_i^yG9p9AP$^f{A-!WOto3cg7+grwm+wZ;j7X%To$QH! zEuzO2*Z|~9`)U$|T!M_xUh5couOLt;H1qWIFUZsB2jLRJm#>X@c=11cb<<{Px+8Q~ z7>8#4fU&XgK?+YYYUl?Qu&_fdbdfz=;53x+3MccYudGBXXHe5a#jwj)erS5X#FVV^ znH%voo%yTbXZBNMpxwNyRvhd>D$45NeX_%#g7eGP;D`ItGxTMQHQ^fZCfQ{ zgG)G~`MYl#z+&UZ?y>aPW`%drfA#*B83{uN>69AiH&u1Oz&Z_ip1;JJs%Ow-3Mz97 za{H3>4>JfoQWdf!B!~hGS+K}Hse`&=BuH*&e$4|XAkI})4afnG=@tRz=Twv zJ1$l-7THz`lhWR7tc=Po4Gr5Jy2peOj;{_0w7fbsp6B;}eHn}#eBHZ2aVHaENQw`_ ze45r^b!b`~?6D#*$3QBbZ>3I)pc-yR4OZ6YDgv9o_yst4zF^?_BJt)aXu z6y&2*^_C}MVd4|dY(_L6L#G5e-Xg0q#^v>hh>tZ43+S5QnVvD!93H6T&HXsCa=Ha$ z8f}suE-lV% z2$UP2Tw+m)>uA3H?66p^NZBZ2A*=Ii*83&S?5~oxqRT7Ey~J|SS+G9Nt;R0W)|rXs z3ro4dk0Mq_I$2YMA7lp`>hY&Gd+63oPVcQ!@{-w_v5!PtW8k zr&)DHEZ&AnNgH^txNS7g=cjS8z+a{Ag2iA@BYuqvcnu<4X22Pcy<1Ti8?(_XO76W) zBRA5>zagu3`49A(As795_ydLG1^PriMfDq*VHM-?>|?DNL^$2a2^g+kb|2#2Hh|6_ zkGAW}kqDrXs-ZOp-pEb3eyn}IHsWiuwzGTS{6xUT*F9dA^qCNivVUlVIZX_#61E=c z%3VLk{!`#YK~wRdcBsws`B>@riqGC+g>+iiR`R!o!4G3`IA&;ixA%I1`hMoiPQYbO zssI!GbQ?X$afcUq3lQcR7QgM5$nz_Vr;kmvYm_mU;;rB9ofB}#0r7!&ukYqDY}^+_ zsH!%3iY*+S>l|C@dr{_w+9v$>q5*)M>qTF$^RGs5-E+3}^zaq@_ZT98Ijy+{hfJ%% zT^s;l0T%!u`)iDsuz>Fq$LY;1v2AjEyE*_;O!FL_D>`32f{@$ z>N<1E-&FH}kKM_x@VG2iv2@femD9x}1=0__^5+#j;ye94zX>`Uy7yL0Yh;d~v~;mB zg;+LgwbW8BbcEF!TY?jg;ib}tE@i7|z9FRy_{7J7an{U%TZd4qADdlxb4-0C$yv}M zZM_hj-=G#<1D_WwD>-uwX3;gU*Drm5yS0b2gS{{8x?Vgz?2~3=I)o@A_JU)CGL+3| zzqCQxRZXTyjny@Kq?;wkwRlL!{O4f|buJzqIeUXSnw1>4DALBz#JMAk70iYN5;hXM z$;n(VsE|Jz%6Yx^>Em#J$uN9UnJ=UUZ5$Q5z{)^d9A8~_o)@c}shgNC45^ff2+&np zREt6V$jOkR5G_mn2a-$2-LW54l~g8Ji#alWA(7lV;}=eV$e33+Eslh&YPcMM{u8lbpVFzZ_7HGFdh0Y6t4IC zr_d2-wo>SHjez-eY5k|r@ozZX9nwdJe5!?L5R0(An?9 zoI^MxzTfWDL*NgI#J%_VRnh_t{^UDV$;8r#;%69}34Y|X782-&d6h9T=VDU?c;Dn< zuF3tY4T`Oq#c8<$w-d`CKWw5+>27UTYj}KJ-H1rylrSTG)03Q`AoHb7Co`|LM_Vd@ zD~)@tp_SLtMO;H(w*6yaxg_RY>qd#kTly^BvfF+yFSx^Y0>Nnn6kQQlMz1Y$9a!|5 z%*+>i7o%=YEwslzo|!fyj}-v8k{Fh#XADk}saj`)1aU1YTMP%sUL^;uaWt#Wme$nA z%%q;ugKa zA=~lL`tXw#eZ9u3d(r~=9+~+%k((z}<7Ih9sVc1D^cpy4xH=j*xHNeGT~52+&c7Ps zy7K;EXV>D|9}({3{daEv)1M7d8&-FHc>iMh-){fRu7A2wBCrZB6u-Il_f-GHw!f!J z_{--{?)^O*5#XOF<+tb3&A$=M-x2!zIsDTV06^euTuYF@bN~BP{yoCq>*aTZXH@?; d!v6}Gz79GXH~zSA0EE}y+v`MV{ZVCr{{iA}wu%4% literal 0 HcmV?d00001 From dddfd3bd1845dba840bc7e5a796d27140084cd74 Mon Sep 17 00:00:00 2001 From: Alexander Schranz Date: Sun, 23 Jan 2022 18:19:37 +0100 Subject: [PATCH 031/130] Fix tests and add codestyle exception for Firefox extension --- .php-cs-fixer.dist.php | 1 + lib/Firefox/FirefoxProfile.php | 12 +-- .../Firefox/FirefoxDriverServiceTest.php | 2 +- .../functional/Firefox/FirefoxDriverTest.php | 2 +- .../functional/Firefox/FirefoxProfileTest.php | 75 +++++++++---------- 5 files changed, 42 insertions(+), 50 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 051fb6579..5cf7d36f6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,6 +1,7 @@ notPath('Firefox/FirefoxProfile.php') // need to use str_* instead of mb_str_* methods ->in([__DIR__ . '/lib', __DIR__ . '/tests']); return (new PhpCsFixer\Config()) diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index 645d5cae9..b211c97ca 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -185,8 +185,8 @@ public function encode() /** * @param string $extension The path to the extension. * @param string $profile_dir The path to the profile directory. - * @throws WebDriverException * @throws \Exception + * @throws WebDriverException * @return string The path to the directory of this extension. */ private function installExtension($extension, $profile_dir) @@ -207,26 +207,22 @@ private function installExtension($extension, $profile_dir) $first_marker_pos_in_hex = strpos($mozilla_rsa_hex, $object_identifier_hex_marker); // phpcs:ignore $second_marker_pos_in_hex_string = - strpos($mozilla_rsa_hex, $object_identifier_hex_marker, $first_marker_pos_in_hex + 2); + strpos($mozilla_rsa_hex, $object_identifier_hex_marker, $first_marker_pos_in_hex + 2); // phpcs:ignore if ($second_marker_pos_in_hex_string === false) { throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); } $common_name_string_position_in_binary = - ($second_marker_pos_in_hex_string + strlen($object_identifier_hex_marker)) / 2; + ($second_marker_pos_in_hex_string + strlen($object_identifier_hex_marker)) / 2; // phpcs:ignore $common_name_string_length = ord($mozilla_rsa_binary_data[$common_name_string_position_in_binary + 1]); - $addon_common_name = substr( + $addon_common_name = substr( // phpcs:ignore $mozilla_rsa_binary_data, $common_name_string_position_in_binary + 2, $common_name_string_length ); - if (!preg_match('/^\\{[0-9a-f-]{36}\\}$/', $addon_common_name)) { - throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); - } - $this->deleteDirectory($temp_dir); //install extension to profile directory diff --git a/tests/functional/Firefox/FirefoxDriverServiceTest.php b/tests/functional/Firefox/FirefoxDriverServiceTest.php index ce110acb7..190286847 100644 --- a/tests/functional/Firefox/FirefoxDriverServiceTest.php +++ b/tests/functional/Firefox/FirefoxDriverServiceTest.php @@ -21,7 +21,7 @@ protected function setUp(): void { if (getenv('BROWSER_NAME') !== 'firefox' || empty(getenv('GECKODRIVER_PATH')) || WebDriverTestCase::isSauceLabsBuild()) { - $this->markTestSkipped('The test is run only when running against local chrome'); + $this->markTestSkipped('The test is run only when running against local firefox'); } } diff --git a/tests/functional/Firefox/FirefoxDriverTest.php b/tests/functional/Firefox/FirefoxDriverTest.php index ab6a03846..cc107793a 100644 --- a/tests/functional/Firefox/FirefoxDriverTest.php +++ b/tests/functional/Firefox/FirefoxDriverTest.php @@ -22,7 +22,7 @@ protected function setUp(): void { if (getenv('BROWSER_NAME') !== 'firefox' || empty(getenv('GECKODRIVER_PATH')) || WebDriverTestCase::isSauceLabsBuild()) { - $this->markTestSkipped('The test is run only when running against local chrome'); + $this->markTestSkipped('The test is run only when running against local firefox'); } } diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 469308553..420dc87f6 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -20,40 +20,42 @@ use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverTestCase; +use PHPUnit\Framework\TestCase; /** - * @group firefox + * @group exclude-saucelabs * @covers \Facebook\WebDriver\Firefox\FirefoxProfile */ -class FirefoxProfileTest extends WebDriverTestCase +class FirefoxProfileTest extends TestCase { + /** @var FirefoxDriver */ + protected $driver; + protected $firefoxTestExtensionFilename = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi'; - protected function setUp() + protected function setUp(): void { - if (getenv('BROWSER_NAME') !== 'firefox' || getenv('SAUCELABS')) { - $this->markTestSkipped('FirefoxProfileTest is run only when running against local firefox'); + if (getenv('BROWSER_NAME') !== 'firefox' || empty(getenv('GECKODRIVER_PATH')) + || WebDriverTestCase::isSauceLabsBuild()) { + $this->markTestSkipped('The test is run only when running against local firefox'); } } - public function testShouldInstallExtension() + protected function tearDown(): void { - $this->desiredCapabilities = DesiredCapabilities::firefox(); + if ($this->driver instanceof RemoteWebDriver && $this->driver->getCommandExecutor() !== null) { + $this->driver->quit(); + } + } + public function testShouldInstallExtension() + { $firefoxProfile = new FirefoxProfile(); $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); - - $this->desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); - - $this->driver = RemoteWebDriver::create( - $this->serverUrl, - $this->desiredCapabilities, - $this->connectionTimeout, - $this->requestTimeout - ); + $this->driver = $this->startFirefoxDriver($firefoxProfile, ['-headless']); $this->driver->get($this->getTestPageUrl('index.html')); @@ -61,36 +63,29 @@ public function testShouldInstallExtension() $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); $this->assertInstanceOf(RemoteWebElement::class, $element); - - $this->driver->quit(); } - public function testShouldInstallExtensionInHeadlessMode() + protected function getTestPageUrl($path) { - $this->desiredCapabilities = DesiredCapabilities::firefox(); - - $firefoxProfile = new FirefoxProfile(); - $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); - - $this->desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); - - $arguments[] = '--headless'; - $this->desiredCapabilities->setCapability('moz:firefoxOptions', ['args' => $arguments]); - - $this->driver = RemoteWebDriver::create( - $this->serverUrl, - $this->desiredCapabilities, - $this->connectionTimeout, - $this->requestTimeout - ); + $host = 'http://localhost:8000/'; + if ($alternateHost = getenv('FIXTURES_HOST')) { + $host = $alternateHost; + } - $this->driver->get($this->getTestPageUrl('index.html')); + return $host . '/' . $path; + } - $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); + private function startFirefoxDriver(FirefoxProfile $firefoxProfile, array $arguments = []) + { + // The createDefaultService() method expect path to the executable to be present in the environment variable + putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); - $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); - $this->assertInstanceOf(RemoteWebElement::class, $element); + $firefoxOptions = new FirefoxOptions(); + $firefoxOptions->addArguments($arguments); + $desiredCapabilities = DesiredCapabilities::firefox(); + $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); + $desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); - $this->driver->quit(); + return FirefoxDriver::start($desiredCapabilities); } } From e297e2b9c301406baa60b01df2dcef53f505c8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 26 Aug 2022 14:59:48 +0200 Subject: [PATCH 032/130] Refactor installing Firefox extensions --- lib/Firefox/FirefoxProfile.php | 97 ++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index b211c97ca..8e6e1094e 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -184,63 +184,28 @@ public function encode() /** * @param string $extension The path to the extension. - * @param string $profile_dir The path to the profile directory. + * @param string $profileDir The path to the profile directory. * @throws \Exception * @throws WebDriverException - * @return string The path to the directory of this extension. */ - private function installExtension($extension, $profile_dir) + private function installExtension($extension, $profileDir) { - $temp_dir = $this->createTempDirectory(); - - $this->extractTo($extension, $temp_dir); - - $mozilla_rsa_path = $temp_dir . '/META-INF/mozilla.rsa'; - $mozilla_rsa_binary_data = file_get_contents($mozilla_rsa_path); - $mozilla_rsa_hex = bin2hex($mozilla_rsa_binary_data); - - //We need to find plugin id. This is second occurrence of object identifier "2.5.4.3 commonName" - - //That is marker "2.5.4.3 commonName" in hex: - $object_identifier_hex_marker = '0603550403'; - - $first_marker_pos_in_hex = strpos($mozilla_rsa_hex, $object_identifier_hex_marker); // phpcs:ignore - - $second_marker_pos_in_hex_string = - strpos($mozilla_rsa_hex, $object_identifier_hex_marker, $first_marker_pos_in_hex + 2); // phpcs:ignore + $extensionCommonName = $this->parseExtensionName($extension); - if ($second_marker_pos_in_hex_string === false) { - throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); + // install extension to profile directory + $extensionDir = $profileDir . '/extensions/'; + if (!is_dir($extensionDir) && !mkdir($extensionDir, 0777, true) && !is_dir($extensionDir)) { + throw new WebDriverException('Cannot install Firefox extension - cannot create directory'); } - $common_name_string_position_in_binary = - ($second_marker_pos_in_hex_string + strlen($object_identifier_hex_marker)) / 2; // phpcs:ignore - - $common_name_string_length = ord($mozilla_rsa_binary_data[$common_name_string_position_in_binary + 1]); - $addon_common_name = substr( // phpcs:ignore - $mozilla_rsa_binary_data, - $common_name_string_position_in_binary + 2, - $common_name_string_length - ); - - $this->deleteDirectory($temp_dir); - - //install extension to profile directory - $ext_dir = $profile_dir . '/extensions/'; - if (!is_dir($ext_dir) && !mkdir($ext_dir, 0777, true) && !is_dir($ext_dir)) { - throw new WebDriverException('Cannot install extension - cannot create directory'); + if (!copy($extension, $extensionDir . $extensionCommonName . '.xpi')) { + throw new WebDriverException('Cannot install Firefox extension - cannot copy file'); } - if (!copy($extension, $ext_dir . $addon_common_name . '.xpi')) { - throw new WebDriverException('Cannot install extension - cannot copy file'); - } - - //extension installation with empty preferences (empty users.js) fails: + // extension installation with empty preferences (empty users.js) fails, thus add some dummy data if (empty($this->preferences)) { - $this->setPreference('dom.webdriver.enabled', true); + $this->setPreference('dummy.preference', true); } - - return $ext_dir; } /** @@ -305,4 +270,44 @@ private function extractTo($xpi, $target_dir) return $this; } + + private function parseExtensionName($extensionPath) + { + $temp_dir = $this->createTempDirectory(); + + $this->extractTo($extensionPath, $temp_dir); + + $mozillaRsaPath = $temp_dir . '/META-INF/mozilla.rsa'; + $mozillaRsaBinaryData = file_get_contents($mozillaRsaPath); + $mozillaRsaHex = bin2hex($mozillaRsaBinaryData); + + //We need to find the plugin id. This is the second occurrence of object identifier "2.5.4.3 commonName". + + //That is marker "2.5.4.3 commonName" in hex: + $objectIdentifierHexMarker = '0603550403'; + + $firstMarkerPosInHex = strpos($mozillaRsaHex, $objectIdentifierHexMarker); // phpcs:ignore + + $secondMarkerPosInHexString = + strpos($mozillaRsaHex, $objectIdentifierHexMarker, $firstMarkerPosInHex + 2); // phpcs:ignore + + if ($secondMarkerPosInHexString === false) { + throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); + } + + // phpcs:ignore + $commonNameStringPositionInBinary = ($secondMarkerPosInHexString + strlen($objectIdentifierHexMarker)) / 2; + + $commonNameStringLength = ord($mozillaRsaBinaryData[$commonNameStringPositionInBinary + 1]); + // phpcs:ignore + $extensionCommonName = substr( + $mozillaRsaBinaryData, + $commonNameStringPositionInBinary + 2, + $commonNameStringLength + ); + + $this->deleteDirectory($temp_dir); + + return $extensionCommonName; + } } From 62ee7d0a5695c883308cc4dfd307b78e1281c0d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 26 Aug 2022 15:05:46 +0200 Subject: [PATCH 033/130] Update and re-sign Firefox text extension fixture Also add step-by-step instructions for extension re-validation and re-signing using web-ext tool --- .../functional/Firefox/FirefoxProfileTest.php | 23 +++++++++++++----- .../Firefox/Fixtures/FirefoxExtension.xpi | Bin 0 -> 8630 bytes .../FirefoxWebdriverTestExtension-0.1-fx.xpi | Bin 5384 -> 0 bytes 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 tests/functional/Firefox/Fixtures/FirefoxExtension.xpi delete mode 100644 tests/functional/Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 420dc87f6..2d44cc6e5 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -17,12 +17,26 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; -use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverTestCase; use PHPUnit\Framework\TestCase; /** + * ## Generating/updating the Firefox extension fixture ## + * + * For testing purposes, we use dummy Firefox extension (`Fixtures/FirefoxExtension.xpi`), which adds `
` element + * with some text at the end of each page Firefox renders. + * + * In case the extension will need to be modified, steps below must be followed, + * otherwise firefox won't load the modified extension: + * + * - Extract the xpi file (it is a zip archive) to some temporary directory + * - Make needed changes in the files + * - Install web-ext tool from Mozilla (@see https://github.com/mozilla/web-ext) + * - Sign in to https://addons.mozilla.org/cs/developers/addon/api/key/ to get your JWT API key and JWT secret + * - Run `web-ext sign --channel=unlisted --api-key=[you-api-key] --api-secret=[your-api-secret]` in the extension dir + * - Store the output file (`web-ext-artifacts/[...].xpi`) to the Fixtures/ directory + * * @group exclude-saucelabs * @covers \Facebook\WebDriver\Firefox\FirefoxProfile */ @@ -31,10 +45,7 @@ class FirefoxProfileTest extends TestCase /** @var FirefoxDriver */ protected $driver; - protected $firefoxTestExtensionFilename = - __DIR__ . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'Fixtures/FirefoxWebdriverTestExtension-0.1-fx.xpi'; + protected $firefoxTestExtensionFilename = __DIR__ . '/Fixtures/FirefoxExtension.xpi'; protected function setUp(): void { @@ -62,7 +73,7 @@ public function testShouldInstallExtension() $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); - $this->assertInstanceOf(RemoteWebElement::class, $element); + $this->assertEquals('This element was added by browser extension', $element->getText()); } protected function getTestPageUrl($path) diff --git a/tests/functional/Firefox/Fixtures/FirefoxExtension.xpi b/tests/functional/Firefox/Fixtures/FirefoxExtension.xpi new file mode 100644 index 0000000000000000000000000000000000000000..c5805fbeb821161ab0e7644ef0bdfdcf5ba0b7e2 GIT binary patch literal 8630 zcma)?1yGz_viETYch}(V5`tR@t`j^s!JXh9JOp?5;K3PSaCdi?;O_pB+Hd#GzW46d z?Ww7nd1}s?)Bn@YneP6Lk}Na~HU!}H@pnUbyF?=u#IXF68#s!*=yfs#xK2$dq)A%X$;%U$9!q~oS`$|yv?I9vnTdWB2=v7 z5|it83bK#lOeq^7hLHh1Ap(Zn3s{kO%wKJpm|LRo z{FLVCwra4AI|AB$?!v6GTzlV`hfFVuXNrc~O#u2P_V0fID8@LV|>VQ2LLKqr85?+Q7!# z)Wp&0)pc7NgqgvWy0{#bCi73OEB_kS4ZDf$X^YbN;1y4eiMp#@BLz&M&3SW9;<#c;v9ln!` z%@wgp0j@m9{ea^@2wuq)n+X(_Ew2Yly_XP`1Ek3Q>2ix(h4G26RVS?)(wUcUWX1QZ zwS6GR8vWuKwRzAgzV)hBbvpA}j@ks5zGGlEE_JXEtJX@}EM6en5NG+Zf;3PSMx!&D zl(R?sPxZ{!@g|{PmFxMh>LI_XXKrL`g<7B^z-5j z`gXQRlapmCH~J5x?yoc`G0|JLWJ(E9$bP*N;isfzXu?ZIZo~?>?-0jbY3+dk7>5Dg z9`n$bmnXy3GYw`ulS8h{0_n1@baVhS!E%8#GVMW57f=3$-$lzwxVP$U)}0oh-Kemz z@6DHc(^4&#tj7-gM`Sfxi!9!vz*d)50&W_}fIXYT%U$dR4{PbWAjEA&V*HYXZ~=)O zWhI(eCZNwg60Ao2BSSBiiGLLgT8~!fK$C>Hq2`+|>_;r=M}{6-9u3vrI@Iiz4^E>M zqNWkKQnpL{b;YCAC2*mO5h6->p$F+A0L&Yxb}me#!jt;G3Nt^ody$_*)aV?*XGP}7 zAC7|uyh#rP7pgqBM?FwJI{h*kgRB>nMW3MlsW+tnSdHgZ>$TS_|2bN5U-g!kR1;;E zR(Q{9Wb0_c^3T|f8*3WL5y5Ud%P7oxIfo2@k}+)WD0TU`RX0)j4mlS&kpSazkGD&g zh%q%*^-=MBaK3=LAHCIED#TtTz5qZaiM|v^p(>|H8@!aqiAU@o+{a+o-N#+Hg#8o< z8H8Ly`hqG%K66)A@8n0TMsl|5!|bgZ?$#M>~CB8 z%bnudi(7e1hmFYi7O|(zlJ!*|B&3#{PR@&XEoKm`X0xaD19mwu~95&OXxX74uKP=@udyglp(x2i;S+x!2}J;)np$W(HW(1Ia8meU$#B4g#_Vem zuSXW+EPq<`8$1jBN`j22K5%=p|*>YmG7unGZbK7 z?7T7K5eceSHsuBM!lf9(X|xaRgFihC%m!U9D-+!sUr5dkFIs2*xP3$_zFsk5X2j}w z+`cH=#^I%jT0NP4f|w0=OeimN8By5NqZ2Cajch$3xaJrUaJDsF!MiIZgI=kmdm|q$ zEIzfK&h`lGwzZCo5bi%r!!)^!gjdj67yPx7Nv5v-ZO?w zs(OTVD5>!UfWOlMc}hxndQiEcDS0KKdl7jiZ|35ei2TA)anluvOnt+@4O~sd$nPJ! zdH9nq@!5Zs4R+;{Tv)O>*bp0GzT-;F%MCV7+z;0zn!@b==69&$zW}A((&yUDVq(0( zbeH1aperB7CQZl%Rh6P}LnAy^K^+=QkKCiu--t9EF+bQd*BFVbkuK#V*1nPuH{Z2q zOkcg2C^2#K@Pt}|N-Aev@fO%*_0(B_?{zzz_7Uu6Uh_VjGD4U4s zbBtNg=tGOk8-YX1-@8h;n3-opE`z?$9cnwev@#*!5Hzcu7H13Zyg-!k%{S$o@B)tq zh3Atn9v^h()ULTI8PHK+ZC{_b8q*z2TjD-4vmW3*afAm>T?atGZ6Wz$D?tE639vgP zv4Z2EFd%+HmB&UgL6nk51K#>V#Z#Bk8W5ld#H@rt<>ie5y3{$%%f%TeapIYU%uD;M zhIQUnOvw(}5^l3+%KJW2NbAD)k3!zzVPRKrw>U=9ksxkc7cqge0$Ba)p44IocksGi zgi}93K#;@M_GlZ!hv0sJ`amVBIV(&-Vbzqq{!)|iri<%qLpz+oA^NGW1Su(qXer(j z#RBOR4xN_EpM+2}F6s49?KgS3qWVES_P9#)Od1FXl?A80^X5uZHp>G(otu61y0iZ} zc-k4w=(4Xh{QXPg7W`3_Q{P*I2%qtii-FK%9a~ULjqz=orYZ6En3obwSJjOxiw?A92~J(nphS2D4tqTo zVxa0Hf^EohiMYvzp3l2-reH6AjR8zWe$-h)@TX8A8LDsRl(wKscKDr_AhN%d?W4^D2s+pi<4s`hqLL;0 z;^Zh*y!vS*aKk0usL1tO>0u_z=U2`3io%76XV-@uz8hYLkhwoVf)b(p(h=1~%X_E3 z$ewgR%+0|=biFn3S!JRSr^cpFJ1No`XRwj9vcb}sF=cK1aexYoGrYN)Ubjy*kIZo@!uG+43E1ObMm^V~J2*G=Uj)J{s|n9$+*7zeLoT+H z7`{~ti=FTo*4iM`abAvGfCJ?Aj}NK&prqFYS{y}0H04^6d5&O?Yw-uo!y@ss2Q#p0X%K4w4Hj@ke zXtic`F4p03$`aR2PP2~-A`@m=`?6Rj!&&JC*GzyNB~TphJJG|D97Bp84ChcO2wbM) zhruZn%6rPc@>j@(vpFYj#`>OAWSA2lf3h?>A^_? z^JZ40sc{X&81i7e8Xyz5M?w=kjd$U7+Qf|GQxtt@rJxWDgXR}u8|Q>F_min?YQ0E0 zy+booIJ)WSUOBpTOZola2J<`xFDd7VA>@U@+y`nJQ`Mp)*T~3E#_Ajj`tz#?1%e*9 z6JNQ2G)FL{dCuh97G-){5wNuY$a}_4Wlg2J4XMk77T60yI^0JPcZ2u<64Hk4Ps509 z-A#U0HeO!m$sQr4G!Kf-PnHL`?uW%vnLIvBh8iw)Bj-G`PqKA^b@!DVzpT_$xb6{d zaWNW28dAZi(^6HOJ30FH%OjyZQXlj}0t*ObX}JtR319sbGai0+ER=m@n0MxtA!O%MO2pJR$xqBA1;##oRI05aHot?ku@;pPS&>f67-SBsV zH>3fs?>(YQsZ~dHR#{r{e<{B1t|{k!IYnlc@hkbEK{;7wmP(Z$=PZ>WGVClI zH!^ZpOtPrXPQ0Aw3MGBveTsFk=Ik8Yom1H68|k~6K=injjonXC`L;18N!I4a+Q;UH zrl!Upv9-3=ZLDGlNe}mr4lM_}VPBg!|8PQ3fD``f>k^}0pa0hY{{K!mI2uePy1k8i zSnXY|Ca8*J9?WA)3j>`b8z@O&Gpkm;uS;g9N~uIPk22*-kdfIA!3?ruo+*_#k>``3 z#vsWDeyQ3z)lq15EM9di-?=*gWgNJ?+_VUvoOpSy+&KFj&t5^gRYAyDd6mi^mQs-; z4sP|!`3oc~<3orTX3-&nL=jO{A=SO=A(939k;u`u5zf(PlI`#z0UgQFO86BZhC{p^HraG=_z*@-MOy#>MIgaA%wb z;xFj|kp@1YEb1q?(0O%d8Jz44UK-t7%yIg6KJFp|;4R9CeT}BXTY$T2A2Rokh7E&u z$)2wPULSw5*5K+~xab-B=A@x93VapieHj${OafOa69WTfCc*Xt z;DF_=aL(%2)5gS1E~(@#8DdC9U#dity5BoxcWy-NmnYh9Yp;y!!vkv|GH;8}-CB6h z$fE;=CrUMryt@KZOuC#TGDCTX{_WC?E98e17Y&;yIaGi|4ttIWsnv2ME1^hOn;V-s zMie|&@R`2v2eOAYD^TWK7S{;OX0(c^_R7aZv8%_eH@Y}4KiS7yJswr-Rvyv>0!0E2 zri&R4r^YwoANEldYl+C4v76tI=;!m1thZvYpu7XmvSf~U#jt`Bls#EnKodIqVC}sT zNYwn~k2k-{1kT)N5$t=@5Lx++8S za(VUURuIr7V*t)`-gNfBvtF|7bg!F|u+aNqGk1de@WbR5Mw4XqSxpX$Lt?G5*ugfD z7XKj$^`T05p}X89en(0u``HFZjfSNuO!>hj-c@z$5LqVANO#bEr zVL*c8pttb5nIR-=3>Rb=Sbxf7(k2;MVj)J03O?LZ(7-NpzR;l39YffKDxTv|<0(>qE1dL`w-)*?NV zigDt^+u)*OL5d5l__jShB%Ssa>=KF|<^th(OVni}F z_)`ZAD_>9z#@WRO5?QHIUB8x|Z6*bRa6TK?Qz=@60}*($N|yy=$hdpcRy26YRIjB3 zM?Ker604fptlG8pB*dfFCT&c%(kO>nK_=9?3g7&0Lmb>{wQ^J6$*6Je z+4Bt_I4!y*FSN6!LK*{A<_Z<$+_&Z8LN5!I6{da>_hTJ}mOrsZe$NSX>9n+ywx7Xi z7$iBf#oO(9P9T#Y?Ya;iD4ITQO0>1^-;Wj)&$^acnObZCIo>8ID)`4jpPN7y3Evls zA((ir`xc12fxQ-GSW&XF))`W*hPSJp-@eF}4l1`!x_zG!ig$R4K5>`8lHWmT{rw7N zD|FvaAg_bBtIb4W>_s8*$BSx1Ddco(yCi#3x8cEfk-MKy>eY{6O~yW7WD(2{P%q7| zhbe;azzg~9ldT9y+m)POsK`~-fay_fu~fqexJ{)2Ew9SVN|#NBgwJ( z$Gr!O+Abel8!+#CZ+!&d6`e~&VDw<((q)1+waa^Syf2pW9W{}BStS<49GLDqh)cDg zacg)eA7N8>)1A9R>iH?`Rzu)!X+DH6(65Eb?pdg0f8T#TBzcZZRdj_>-CZ|!JO5T|B1(GDVijYg6Sa6L zCJ$*{CWG9b&q|3@$k7`$%=@U5&c@{sszgI;1r*(QpY07zUi*A`aogJnj5_5W?>5O+ z+IJAw?a9eDzA8=5GsS~VtgfavcxOsM3Mx9YU}`Lzoz(^TtR~OIr~<@;bzmqk?25a0OAsQ4%}L{tdoMOZDQ;Z7YC z07gOifIOAN%)j^LX1sH<$kpA>*PDEkt>H5@DXsyE&0rM_x2uNNyYPNfwt08PPFa&+ zI8N`4b;6AM7pq%%efNcz1pmyK63WEY-IR~Io*iF2m-(E)phgo_rKa3ApHz_bIfCqA z$xcdYU<=1P!JdyEI5-RamaE@hK9*{CE?qZyE}EU}ZEt_$6?^7v2{LnA(VVCzmdkK- zr(KykR`cv2!&YvhziVVEd9dxBk>$l#`xd57v+5`D*gaxiOf_Lx4WbBVKGRr>LM!kn%fU(YYgjiP}tO&KZOhl0MLTu7Gw z+lZSc6EW~@Q_S`CR_dM;F?S+FZsWSIne}JZ>$WeuV?CpCu?AtHoh{$;3r!`zZ|KT{ z;ow)kZO-BG%BQ%>INO1wDOrnCqaT~pN9xmm3g&w*>tIgDSl|P%O!Z1XZXfm4E0T&C0Lc5{7AuK00ZjmV5S6z1PQM` zCq4Y_UM%KgwBpJtD61?qw6=N^zQ{?+vK2qf#(~R6MEt|&NW_~2rW$N70SUj6F64yq z$|G}o1Yde(bojG1<0yFASFpI1@UpYE6MS`6;l(*gqW0{Ki2Bsv?QgNl_~FBSAvj_e z(Zaief?V>er(U&@t%aTWij!sT2xi>JD)B(RMOSW{; z5n0Hv-h8CO^ej1f$4j5kpQwF=s#IX}@$P-<6d_?)EF#R^uj%PtMfG z-Pe+3f`oLHO9vC}lUFH-hd#p{y0zAR7hkCL+PM2w-Bc5xBzM?QNyk_(MbY1~^_{VV z!z^nQL|R2;ZR4lP zq}{HMXz#a%+knIUQk+{u&7F)*lub0mUbJX4-v&Qh5;HeGab$bkh8hgndEe$ulyMJG zq1;qOjIy*!)@k_IjQlL3JoKbF9sih)j&dc(W`D8oUy3+w4lN5`+(_^+v4k z1h8Zkvh^NVtZdYFu9@(A;>&Vi5~<6`Nlf~3f$6e4ENwP2cO$Pa>#!}JtoW+JQq%ED z_dBcPt^^Ij(CN!o!aHi|7(snWU4hfA?Z>R0P1=cPw1r=pa={+}5(4`c7lSrhKdwRX znvtJFkG6zS0yZ=RapMaG8ZA5c@4{VixJlHxJZ8$b>_v+fd=iTGG=bbzM+H1}F*X%tr-`?}hnomaq51$4A01yG1#Wl5TXV0RZkpln+ zlmNgVf3=hi6u@eaR0Q2UpE|p_Squ31T2E#KN~O8a_YT>(nf4;%-AoxOg+&JqP(~%Rv(k648FWm%9(5fOfIKRCbVgUkVi$Cuo{?^i4zHyHn&Aa{eMfSbb*Krw8Km3NXYV3P0_3`$(dCG@9gmp9t(CMPElNe|c^x!{&V})I}3N zy5Zs`X)*&YO5;bN7Z2bq-nz{Plepd&UB7@hX46o}IcqyPVK!Ys7tW|w`;E2wVijE= zaTv{w40$~!fKMUPawtC%JnEm23wLscfw3(++R6)=7SnG8}t0(LXW1UljU(4 zeZU}~oOa5@!_wQ)8k;{Y^1ozUyz3bC(&}%iJ?^+z{@g9_HBFYmfNH2^@x7$f_G|S* z-A;}3?ciw0w_`JZ#IpQ{^q zbhMaA6NOT%W<(rH>{{!5V^@TB}Sw)3%Junt&+GN zskrpfL^h2gLB7NG7%e4+;C8F{?$sm(1zSSJ%n0Lq&kDThaCHfn_7YVJ{9qqL0Eq5| z%i}BdxX@-GTQ=dR@Fanx6PIB{-4(s&uU@biE|`YKVCD_xpm%7)oyNymld;X`bK9aL z=E0`57siK^9XTkS&H-IZ@ZO!}wOJ%3LM*^1n0lpQv)vA`O z#xM45zOn7VR=4$6yoPiprd;8x8+YS2w=OAMp&CVxnF&d1b36%yAi$@kOECWnRK&pQ zlGV)Q)4duMX&+i~FRJwy{Vioyc8_3%0$Xt#i)S$HlLN21quL$i+SEp;?bxFB25aX? zeeS6dzo&N_^5HLbNQcQpMs+N#j|NQCm_R<;u3!A2;KSwRd?)>k%Ycqc2BS zZ{OSI6We>%{KMgpv*M36URPULIRS@b zNEoZ+%sR|D*hcplnYh}L?mxrpW(m2&ce{u;#ObA%ODO{DEoDjM9nx~N9fGkuMZ~6w z`#&no9d-_h1W4bZdhHv7eNFrz3h$?X5)-!G2OkH8U5E;hN@s}Xq)I9X%}Rbwx#=&x zj$;DA0{~bGFLEi`P``k~*wF0;)15$J@vY6_O*>Ry-g&|)OTHEB^w(kr$x(X9B0TBy z2bm9hBv91S%oz3EV%J{Qp6xm6@iRW$hr6{^Ys?MgG(B0utO+h{qvS*DviiOT%UU92=*85Pm#a-&-n+SyR*)g>1jQo% zTAEN*;=EZ|P`x_$MuT>apl-Sqh}d{@SiEFtcx}sq+WJ`Cw+yMIWup+7yHQwrI+@9X z5v^^$sCNnLsKgwWH(d@v$6>Y2BggbNMd>*K z7H?B0TPNO+io^LFYWdzyDj>F$^{eXN`^|!Nn2$^b_+UTj& zue{%H8#HZO|L*0Ota#bgGiI0$gFGQK;U~8a0L2PE8zcVtT@865`5^srw@olWFwUHs z;QR7&xI8ja?Gb>F0_)g;<_i^yG9p9AP$^f{A-!WOto3cg7+grwm+wZ;j7X%To$QH! zEuzO2*Z|~9`)U$|T!M_xUh5couOLt;H1qWIFUZsB2jLRJm#>X@c=11cb<<{Px+8Q~ z7>8#4fU&XgK?+YYYUl?Qu&_fdbdfz=;53x+3MccYudGBXXHe5a#jwj)erS5X#FVV^ znH%voo%yTbXZBNMpxwNyRvhd>D$45NeX_%#g7eGP;D`ItGxTMQHQ^fZCfQ{ zgG)G~`MYl#z+&UZ?y>aPW`%drfA#*B83{uN>69AiH&u1Oz&Z_ip1;JJs%Ow-3Mz97 za{H3>4>JfoQWdf!B!~hGS+K}Hse`&=BuH*&e$4|XAkI})4afnG=@tRz=Twv zJ1$l-7THz`lhWR7tc=Po4Gr5Jy2peOj;{_0w7fbsp6B;}eHn}#eBHZ2aVHaENQw`_ ze45r^b!b`~?6D#*$3QBbZ>3I)pc-yR4OZ6YDgv9o_yst4zF^?_BJt)aXu z6y&2*^_C}MVd4|dY(_L6L#G5e-Xg0q#^v>hh>tZ43+S5QnVvD!93H6T&HXsCa=Ha$ z8f}suE-lV% z2$UP2Tw+m)>uA3H?66p^NZBZ2A*=Ii*83&S?5~oxqRT7Ey~J|SS+G9Nt;R0W)|rXs z3ro4dk0Mq_I$2YMA7lp`>hY&Gd+63oPVcQ!@{-w_v5!PtW8k zr&)DHEZ&AnNgH^txNS7g=cjS8z+a{Ag2iA@BYuqvcnu<4X22Pcy<1Ti8?(_XO76W) zBRA5>zagu3`49A(As795_ydLG1^PriMfDq*VHM-?>|?DNL^$2a2^g+kb|2#2Hh|6_ zkGAW}kqDrXs-ZOp-pEb3eyn}IHsWiuwzGTS{6xUT*F9dA^qCNivVUlVIZX_#61E=c z%3VLk{!`#YK~wRdcBsws`B>@riqGC+g>+iiR`R!o!4G3`IA&;ixA%I1`hMoiPQYbO zssI!GbQ?X$afcUq3lQcR7QgM5$nz_Vr;kmvYm_mU;;rB9ofB}#0r7!&ukYqDY}^+_ zsH!%3iY*+S>l|C@dr{_w+9v$>q5*)M>qTF$^RGs5-E+3}^zaq@_ZT98Ijy+{hfJ%% zT^s;l0T%!u`)iDsuz>Fq$LY;1v2AjEyE*_;O!FL_D>`32f{@$ z>N<1E-&FH}kKM_x@VG2iv2@femD9x}1=0__^5+#j;ye94zX>`Uy7yL0Yh;d~v~;mB zg;+LgwbW8BbcEF!TY?jg;ib}tE@i7|z9FRy_{7J7an{U%TZd4qADdlxb4-0C$yv}M zZM_hj-=G#<1D_WwD>-uwX3;gU*Drm5yS0b2gS{{8x?Vgz?2~3=I)o@A_JU)CGL+3| zzqCQxRZXTyjny@Kq?;wkwRlL!{O4f|buJzqIeUXSnw1>4DALBz#JMAk70iYN5;hXM z$;n(VsE|Jz%6Yx^>Em#J$uN9UnJ=UUZ5$Q5z{)^d9A8~_o)@c}shgNC45^ff2+&np zREt6V$jOkR5G_mn2a-$2-LW54l~g8Ji#alWA(7lV;}=eV$e33+Eslh&YPcMM{u8lbpVFzZ_7HGFdh0Y6t4IC zr_d2-wo>SHjez-eY5k|r@ozZX9nwdJe5!?L5R0(An?9 zoI^MxzTfWDL*NgI#J%_VRnh_t{^UDV$;8r#;%69}34Y|X782-&d6h9T=VDU?c;Dn< zuF3tY4T`Oq#c8<$w-d`CKWw5+>27UTYj}KJ-H1rylrSTG)03Q`AoHb7Co`|LM_Vd@ zD~)@tp_SLtMO;H(w*6yaxg_RY>qd#kTly^BvfF+yFSx^Y0>Nnn6kQQlMz1Y$9a!|5 z%*+>i7o%=YEwslzo|!fyj}-v8k{Fh#XADk}saj`)1aU1YTMP%sUL^;uaWt#Wme$nA z%%q;ugKa zA=~lL`tXw#eZ9u3d(r~=9+~+%k((z}<7Ih9sVc1D^cpy4xH=j*xHNeGT~52+&c7Ps zy7K;EXV>D|9}({3{daEv)1M7d8&-FHc>iMh-){fRu7A2wBCrZB6u-Il_f-GHw!f!J z_{--{?)^O*5#XOF<+tb3&A$=M-x2!zIsDTV06^euTuYF@bN~BP{yoCq>*aTZXH@?; d!v6}Gz79GXH~zSA0EE}y+v`MV{ZVCr{{iA}wu%4% From bd7687aead26921fb883f1a1938ed88bced998e9 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Tue, 15 Feb 2022 23:11:46 +0200 Subject: [PATCH 034/130] Add test to make sure Firefox profile creation is working --- .../functional/Firefox/FirefoxProfileTest.php | 40 +++++++++++++++++-- tests/functional/web/index.html | 5 ++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 2d44cc6e5..68f2cd4c8 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -62,11 +62,24 @@ protected function tearDown(): void } } + public function testShouldStartDriverWithEmptyProfile() + { + $firefoxProfile = new FirefoxProfile(); + $this->startFirefoxDriverWithProfile($firefoxProfile); + + $this->driver->get('http://localhost:8000/'); + $element = $this->driver->findElement(WebDriverBy::id('welcome')); + $this->assertSame( + 'Welcome to the php-webdriver testing page.', + $element->getText() + ); + } + public function testShouldInstallExtension() { $firefoxProfile = new FirefoxProfile(); $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); - $this->driver = $this->startFirefoxDriver($firefoxProfile, ['-headless']); + $this->startFirefoxDriverWithProfile($firefoxProfile); $this->driver->get($this->getTestPageUrl('index.html')); @@ -76,6 +89,25 @@ public function testShouldInstallExtension() $this->assertEquals('This element was added by browser extension', $element->getText()); } + public function testShouldUseProfilePreferences() + { + $firefoxProfile = new FirefoxProfile(); + + // Please note, although it is possible to set preferences right into the profile (what this test does), + // we recommend using the setPreference() method on FirefoxOptions instead, so that you don't need to + // create FirefoxProfile. + $firefoxProfile->setPreference('javascript.enabled', false); + + $this->startFirefoxDriverWithProfile($firefoxProfile); + $this->driver->get('http://localhost:8000/'); + + $noScriptElement = $this->driver->findElement(WebDriverBy::id('noscript')); + $this->assertEquals( + 'This element is only shown with JavaScript disabled.', + $noScriptElement->getText() + ); + } + protected function getTestPageUrl($path) { $host = 'http://localhost:8000/'; @@ -86,17 +118,17 @@ protected function getTestPageUrl($path) return $host . '/' . $path; } - private function startFirefoxDriver(FirefoxProfile $firefoxProfile, array $arguments = []) + private function startFirefoxDriverWithProfile(FirefoxProfile $firefoxProfile) { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); $firefoxOptions = new FirefoxOptions(); - $firefoxOptions->addArguments($arguments); + $firefoxOptions->addArguments(['-headless']); $desiredCapabilities = DesiredCapabilities::firefox(); $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); $desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); - return FirefoxDriver::start($desiredCapabilities); + $this->driver = FirefoxDriver::start($desiredCapabilities); } } diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index f94c7bc53..0f379e26b 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -15,7 +15,6 @@ -

Welcome to the php-webdriver testing page.

@@ -65,6 +64,10 @@

Welcome to the php-webdriver testing page.


+ +
Foo
From f34c2ca85f01f591be9655b659e7c6cb73e8b534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 30 Aug 2022 11:48:12 +0200 Subject: [PATCH 035/130] Add setProfile method for straighforward use of FirefoxProfile --- CHANGELOG.md | 4 ++++ lib/Firefox/FirefoxDriver.php | 7 ++++++ lib/Firefox/FirefoxOptions.php | 22 +++++++++++++++++++ .../functional/Firefox/FirefoxProfileTest.php | 2 +- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c0f3904..3e3f018f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,12 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile. + ### Changed - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. +- Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile. ## 1.12.1 - 2022-05-03 ### Fixed diff --git a/lib/Firefox/FirefoxDriver.php b/lib/Firefox/FirefoxDriver.php index 34dd13de2..70f6ff0d9 100644 --- a/lib/Firefox/FirefoxDriver.php +++ b/lib/Firefox/FirefoxDriver.php @@ -9,6 +9,13 @@ class FirefoxDriver extends LocalWebDriver { + /** + * @deprecated Pass Firefox Profile using FirefoxOptions: + * $firefoxOptions = new FirefoxOptions(); + * $firefoxOptions->setProfile($profile->encode()); + * $capabilities = DesiredCapabilities::firefox(); + * $capabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); + */ const PROFILE = 'firefox_profile'; /** diff --git a/lib/Firefox/FirefoxOptions.php b/lib/Firefox/FirefoxOptions.php index 06e61c3d5..0a3a16744 100644 --- a/lib/Firefox/FirefoxOptions.php +++ b/lib/Firefox/FirefoxOptions.php @@ -17,6 +17,8 @@ class FirefoxOptions implements \JsonSerializable const OPTION_ARGS = 'args'; /** @var string */ const OPTION_PREFS = 'prefs'; + /** @var string */ + const OPTION_PROFILE = 'profile'; /** @var array */ private $options = []; @@ -24,6 +26,8 @@ class FirefoxOptions implements \JsonSerializable private $arguments = []; /** @var array */ private $preferences = []; + /** @var FirefoxProfile */ + private $profile; public function __construct() { @@ -50,6 +54,9 @@ public function setOption($name, $value) if ($name === self::OPTION_ARGS) { throw new \InvalidArgumentException('Use addArguments() method to add Firefox arguments'); } + if ($name === self::OPTION_PROFILE) { + throw new \InvalidArgumentException('Use setProfile() method to set Firefox profile'); + } $this->options[$name] = $value; @@ -87,6 +94,18 @@ public function setPreference($name, $value) return $this; } + /** + * @see https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefox-profile + * @param FirefoxProfile $profile + * @return self + */ + public function setProfile(FirefoxProfile $profile) + { + $this->profile = $profile; + + return $this; + } + /** * @return array */ @@ -99,6 +118,9 @@ public function toArray() if (!empty($this->preferences)) { $array[self::OPTION_PREFS] = $this->preferences; } + if (!empty($this->profile)) { + $array[self::OPTION_PROFILE] = $this->profile->encode(); + } return $array; } diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 68f2cd4c8..408390407 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -125,9 +125,9 @@ private function startFirefoxDriverWithProfile(FirefoxProfile $firefoxProfile) $firefoxOptions = new FirefoxOptions(); $firefoxOptions->addArguments(['-headless']); + $firefoxOptions->setProfile($firefoxProfile); $desiredCapabilities = DesiredCapabilities::firefox(); $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); - $desiredCapabilities->setCapability(FirefoxDriver::PROFILE, $firefoxProfile); $this->driver = FirefoxDriver::start($desiredCapabilities); } From d8c07c122e90be74f0e8e455ce20b49b84c5faf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 30 Aug 2022 11:59:26 +0200 Subject: [PATCH 036/130] Add functional test for Firefox setPreference --- .../functional/Firefox/FirefoxDriverTest.php | 26 +++++++++++++++++-- .../functional/Firefox/FirefoxProfileTest.php | 1 + 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/functional/Firefox/FirefoxDriverTest.php b/tests/functional/Firefox/FirefoxDriverTest.php index cc107793a..7c7b91370 100644 --- a/tests/functional/Firefox/FirefoxDriverTest.php +++ b/tests/functional/Firefox/FirefoxDriverTest.php @@ -5,10 +5,14 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; +use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverTestCase; use PHPUnit\Framework\TestCase; /** + * @group exclude-chrome + * @group exclude-edge + * @group exclude-safari * @group exclude-saucelabs * @covers \Facebook\WebDriver\Firefox\FirefoxDriver * @covers \Facebook\WebDriver\Local\LocalWebDriver @@ -49,12 +53,30 @@ public function testShouldStartFirefoxDriver() $this->assertSame('http://localhost:8000/', $this->driver->getCurrentURL()); } - private function startFirefoxDriver() + public function testShouldSetPreferenceWithFirefoxOptions() + { + $firefoxOptions = new FirefoxOptions(); + $firefoxOptions->setPreference('javascript.enabled', false); + + $this->startFirefoxDriver($firefoxOptions); + + $this->driver->get('http://localhost:8000/'); + + $noScriptElement = $this->driver->findElement(WebDriverBy::id('noscript')); + $this->assertEquals( + 'This element is only shown with JavaScript disabled.', + $noScriptElement->getText() + ); + } + + private function startFirefoxDriver(FirefoxOptions $firefoxOptions = null) { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); - $firefoxOptions = new FirefoxOptions(); + if ($firefoxOptions === null) { + $firefoxOptions = new FirefoxOptions(); + } $firefoxOptions->addArguments(['-headless']); $desiredCapabilities = DesiredCapabilities::firefox(); $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 408390407..c06ebd4cf 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -97,6 +97,7 @@ public function testShouldUseProfilePreferences() // we recommend using the setPreference() method on FirefoxOptions instead, so that you don't need to // create FirefoxProfile. $firefoxProfile->setPreference('javascript.enabled', false); + $this->assertSame('false', $firefoxProfile->getPreference('javascript.enabled')); $this->startFirefoxDriverWithProfile($firefoxProfile); $this->driver->get('http://localhost:8000/'); From 15c4d8ed07ddd93dc3c87e2fde39aad914a17a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 30 Aug 2022 12:00:31 +0200 Subject: [PATCH 037/130] Remove no longer needed FirefoxProfile workaround Fixed in Geckodriver, see https://github.com/mozilla/geckodriver/issues/1990 --- lib/Firefox/FirefoxProfile.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index 8e6e1094e..6e06db6be 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -201,11 +201,6 @@ private function installExtension($extension, $profileDir) if (!copy($extension, $extensionDir . $extensionCommonName . '.xpi')) { throw new WebDriverException('Cannot install Firefox extension - cannot copy file'); } - - // extension installation with empty preferences (empty users.js) fails, thus add some dummy data - if (empty($this->preferences)) { - $this->setPreference('dummy.preference', true); - } } /** From 563b71c8a5db643cc30d2df51e2abb625ed22876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 30 Aug 2022 15:45:11 +0200 Subject: [PATCH 038/130] Add notice for using 'prefs' in ChromeOptions, as it won't work in headless mode --- lib/Chrome/ChromeOptions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index 7c81955d8..0e547b209 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -107,6 +107,9 @@ public function addEncodedExtensions(array $encoded_extensions) /** * Sets an experimental option which has not exposed officially. * + * When using "prefs" to set Chrome preferences, please be aware they are so far not supported by + * Chrome running in headless mode, see https://bugs.chromium.org/p/chromium/issues/detail?id=775911 + * * @param string $name * @param mixed $value * @return ChromeOptions From 5a933a3d496ff9b195d21f181619b74bd2f929c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 1 Sep 2022 14:21:58 +0200 Subject: [PATCH 039/130] Throw exception instead of fatal error on invalid server response (fixes #945) --- CHANGELOG.md | 3 +++ lib/Remote/RemoteWebDriver.php | 9 +++++++ tests/unit/Remote/RemoteWebDriverTest.php | 30 +++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3f018f2..923eeee4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. - Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile. +### Fixed +- Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands. + ## 1.12.1 - 2022-05-03 ### Fixed - Improper PHP documentation for `getAttribute()` and `getDomProperty()`. diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index f45ae8da1..9887d2b42 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\UnknownErrorException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\Support\ScreenshotHelper; @@ -209,6 +210,10 @@ public function findElement(WebDriverBy $by) JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); + if ($raw_element === null) { + throw new UnknownErrorException('Unexpected server response to findElement command'); + } + return $this->newElement(JsonWireCompat::getElement($raw_element)); } @@ -226,6 +231,10 @@ public function findElements(WebDriverBy $by) JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); + if ($raw_elements === null) { + throw new UnknownErrorException('Unexpected server response to findElements command'); + } + $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 7f0475dec..198fd3997 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -2,7 +2,9 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\UnknownErrorException; use Facebook\WebDriver\Interactions\WebDriverActions; +use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverNavigation; use Facebook\WebDriver\WebDriverOptions; use Facebook\WebDriver\WebDriverWait; @@ -102,4 +104,32 @@ public function testShouldCreateWebDriverWaitInstance() $this->assertInstanceOf(WebDriverWait::class, $wait); } + + /** + * @param string $method + * @param string $expectedExceptionMessage + * @dataProvider provideMethods + */ + public function testShouldThrowExceptionOnUnexpectedNullValueFromRemoteEnd($method, $expectedExceptionMessage) + { + $executorMock = $this->createMock(HttpCommandExecutor::class); + $executorMock->expects($this->once()) + ->method('execute') + ->with($this->isInstanceOf(WebDriverCommand::class)) + ->willReturn(new WebDriverResponse('session-id')); + + $this->driver->setCommandExecutor($executorMock); + + $this->expectException(UnknownErrorException::class); + $this->expectExceptionMessage($expectedExceptionMessage); + call_user_func([$this->driver, $method], $this->createMock(WebDriverBy::class)); + } + + public function provideMethods() + { + return [ + ['findElement', 'Unexpected server response to findElement command'], + ['findElements', 'Unexpected server response to findElements command'], + ]; + } } From 0ae8712a6c4d5f43d048706e1df5664d309ec4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 4 Sep 2022 12:03:09 +0200 Subject: [PATCH 040/130] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 923eeee4d..1c9509664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,13 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added +- Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method. - `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile. ### Changed - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. - Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile. +- Deprecate `getAllSessions()` method of `RemoteWebDriver` (which is not part of W3C WebDriver). ### Fixed - Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands. From 7c7cf67114c7ef5344b55809e47ac14d8058ed71 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sun, 4 Sep 2022 11:50:43 +0200 Subject: [PATCH 041/130] Add GitHub permissions on action workflows Ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions --- .github/workflows/docs-lint.yml | 3 +++ .github/workflows/docs-publish.yml | 3 +++ .github/workflows/no-response.yaml | 3 +++ .github/workflows/sauce-labs.yaml | 3 +++ .github/workflows/tests.yaml | 3 +++ 5 files changed, 15 insertions(+) diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml index 56667ce70..e0f0bb531 100644 --- a/.github/workflows/docs-lint.yml +++ b/.github/workflows/docs-lint.yml @@ -1,5 +1,8 @@ name: Lint PHP documentation +permissions: + contents: read + on: push: pull_request: diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 02a50534b..edf50d967 100755 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -1,5 +1,8 @@ name: Publish API documentation +permissions: + contents: read + on: repository_dispatch: types: [ run-build-api-docs ] diff --git a/.github/workflows/no-response.yaml b/.github/workflows/no-response.yaml index 2970c7d72..69b1eb840 100644 --- a/.github/workflows/no-response.yaml +++ b/.github/workflows/no-response.yaml @@ -1,5 +1,8 @@ name: No Response +permissions: + issues: write + # Both `issue_comment` and `scheduled` event types are required for this Action to work properly. on: issue_comment: diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index 878743ef1..c966cbacc 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -1,5 +1,8 @@ name: Sauce Labs +permissions: + contents: read + on: push: schedule: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2cea57936..2452c8619 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,5 +1,8 @@ name: Tests +permissions: + contents: read + on: push: pull_request: From ed9fd1268681a98ba0b6862ff8370bff51462245 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sun, 4 Sep 2022 11:51:09 +0200 Subject: [PATCH 042/130] Re-order arguments for createBySessionID --- lib/Remote/RemoteWebDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 9887d2b42..409cfce2a 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -141,8 +141,8 @@ public static function create( * This constructor can boost the performance a lot by reusing the same browser for the whole test suite. * You cannot pass the desired capabilities because the session was created before. * - * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param string $session_id The existing session id + * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server * @param bool $isW3cCompliant True to use W3C WebDriver (default), false to use the legacy JsonWire protocol From 5a28a2f3aaf4cc157b6a2f1b461bdd420571d6e3 Mon Sep 17 00:00:00 2001 From: William Desportes Date: Sun, 4 Sep 2022 11:54:21 +0200 Subject: [PATCH 043/130] Update .gitattributes --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index e6146bc76..7800fad58 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ /example.php export-ignore /phpunit.xml.dist export-ignore /.github export-ignore -/.php_cs.dist export-ignore +/.php-cs-fixer.dist.php export-ignore /phpstan.neon export-ignore /.coveralls.yml export-ignore +/logs export-ignore From 7e3a7e027364ba6ac9927277557f7c4ec2869782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 1 Sep 2022 16:41:05 +0200 Subject: [PATCH 044/130] Increase default request timeout to 3 minutes (address #859) --- CHANGELOG.md | 1 + lib/Remote/HttpCommandExecutor.php | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c9509664..f89893ed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands. +- Increase default request timeout to 3 minutes (instead of 30 seconds). ## 1.12.1 - 2022-05-03 ### Fixed diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 2d510a038..8099535de 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -208,8 +208,9 @@ public function __construct($url, $http_proxy = null, $http_proxy_port = null) curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); - $this->setRequestTimeout(30000); - $this->setConnectionTimeout(30000); + + $this->setConnectionTimeout(30 * 1000); // 30 seconds + $this->setRequestTimeout(180 * 1000); // 3 minutes } public function disableW3cCompliance() From 619d27bf5025b99cf9b37a5962434156fd378328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Sep 2022 15:32:28 +0200 Subject: [PATCH 045/130] Use constants for test page names --- tests/functional/FileUploadTest.php | 2 +- .../functional/Firefox/FirefoxProfileTest.php | 3 +- tests/functional/RemoteKeyboardTest.php | 2 +- tests/functional/RemoteTargetLocatorTest.php | 24 ++++----- .../functional/RemoteWebDriverCreateTest.php | 2 +- .../RemoteWebDriverFindElementTest.php | 10 ++-- tests/functional/RemoteWebDriverTest.php | 20 ++++---- tests/functional/RemoteWebElementTest.php | 50 +++++++++---------- tests/functional/TestPage.php | 25 ++++++++++ tests/functional/WebDriverActionsTest.php | 18 +++---- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverByTest.php | 2 +- tests/functional/WebDriverCheckboxesTest.php | 2 +- tests/functional/WebDriverNavigationTest.php | 12 ++--- .../WebDriverOptionsCookiesTest.php | 2 +- tests/functional/WebDriverRadiosTest.php | 2 +- tests/functional/WebDriverSelectTest.php | 2 +- tests/functional/WebDriverTimeoutsTest.php | 6 +-- 18 files changed, 106 insertions(+), 80 deletions(-) create mode 100644 tests/functional/TestPage.php diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index f4728be41..4ca5e1e60 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -18,7 +18,7 @@ class FileUploadTest extends WebDriverTestCase */ public function testShouldUploadAFile() { - $this->driver->get($this->getTestPageUrl('upload.html')); + $this->driver->get($this->getTestPageUrl(TestPage::UPLOAD)); $fileElement = $this->driver->findElement(WebDriverBy::name('upload')); diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index c06ebd4cf..4e16dd096 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -17,6 +17,7 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\TestPage; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverTestCase; use PHPUnit\Framework\TestCase; @@ -81,7 +82,7 @@ public function testShouldInstallExtension() $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); $this->startFirefoxDriverWithProfile($firefoxProfile); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); diff --git a/tests/functional/RemoteKeyboardTest.php b/tests/functional/RemoteKeyboardTest.php index a97b3343d..dfab3a687 100644 --- a/tests/functional/RemoteKeyboardTest.php +++ b/tests/functional/RemoteKeyboardTest.php @@ -21,7 +21,7 @@ class RemoteKeyboardTest extends WebDriverTestCase */ public function testShouldPressSendAndReleaseKeys() { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $this->driver->getKeyboard()->sendKeys('ab'); $this->driver->getKeyboard()->pressKey(WebDriverKeys::SHIFT); diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index a9e03e514..f3a098be4 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -11,7 +11,7 @@ class RemoteTargetLocatorTest extends WebDriverTestCase { public function testShouldSwitchToWindow() { - $this->driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $originalWindowHandle = $this->driver->getWindowHandle(); $windowHandlesBefore = $this->driver->getWindowHandles(); @@ -49,7 +49,7 @@ public function testShouldSwitchToWindow() public function testActiveElement() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $activeElement = $this->driver->switchTo()->activeElement(); $this->assertInstanceOf(RemoteWebElement::class, $activeElement); @@ -67,7 +67,7 @@ public function testShouldSwitchToFrameByItsId() $firstChildFrame = 'This is the content of the iFrame'; $secondChildFrame = 'open new window'; - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); @@ -95,7 +95,7 @@ public function testShouldSwitchToParentFrame() $parentPage = 'This is the host page which contains an iFrame'; $firstChildFrame = 'This is the content of the iFrame'; - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); @@ -108,7 +108,7 @@ public function testShouldSwitchToParentFrame() public function testShouldSwitchToFrameByElement() { - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $element = $this->driver->findElement(WebDriverBy::id('iframe_content')); $this->driver->switchTo()->frame($element); @@ -125,8 +125,8 @@ public function testShouldCreateNewWindow() // Ensure that the initial context matches. $initialHandle = $this->driver->getWindowHandle(); - $this->driver->get($this->getTestPageUrl('index.html')); - $this->assertEquals($this->getTestPageUrl('index.html'), $this->driver->getCurrentUrl()); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + $this->assertEquals($this->getTestPageUrl(TestPage::INDEX), $this->driver->getCurrentUrl()); $source = $this->driver->getPageSource(); $this->compatAssertStringContainsString('

', $source); $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); @@ -140,13 +140,13 @@ public function testShouldCreateNewWindow() $this->assertCount(2, $windowHandles); $newWindowHandle = $this->driver->getWindowHandle(); - $this->driver->get($this->getTestPageUrl('upload.html')); - $this->assertEquals($this->getTestPageUrl('upload.html'), $this->driver->getCurrentUrl()); + $this->driver->get($this->getTestPageUrl(TestPage::UPLOAD)); + $this->assertEquals($this->getTestPageUrl(TestPage::UPLOAD), $this->driver->getCurrentUrl()); $this->assertNotEquals($initialHandle, $newWindowHandle); // Switch back to original context. $this->driver->switchTo()->window($initialHandle); - $this->assertEquals($this->getTestPageUrl('index.html'), $this->driver->getCurrentUrl()); + $this->assertEquals($this->getTestPageUrl(TestPage::INDEX), $this->driver->getCurrentUrl()); } /** @@ -156,7 +156,7 @@ public function testShouldNotAcceptStringAsFrameIdInW3cMode() { self::skipForJsonWireProtocol(); - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage( @@ -173,7 +173,7 @@ public function testShouldAcceptStringAsFrameIdInJsonWireMode() { self::skipForW3cProtocol(); - $this->driver->get($this->getTestPageUrl('page_with_frame.html')); + $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); $this->driver->switchTo()->frame('iframe_content'); diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 6473e9b4a..3de4b2c93 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -108,7 +108,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->connectionTimeout, $this->requestTimeout ); - $originalDriver->get($this->getTestPageUrl('index.html')); + $originalDriver->get($this->getTestPageUrl(TestPage::INDEX)); $this->compatAssertStringContainsString('/index.html', $originalDriver->getCurrentURL()); // Store session ID diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 5a7d18e70..5887b4fba 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -13,7 +13,7 @@ class RemoteWebDriverFindElementTest extends WebDriverTestCase { public function testShouldThrowExceptionIfElementCannotBeFound() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->expectException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('not_existing')); @@ -21,7 +21,7 @@ public function testShouldThrowExceptionIfElementCannotBeFound() public function testShouldFindElementIfExistsOnAPage() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); @@ -30,7 +30,7 @@ public function testShouldFindElementIfExistsOnAPage() public function testShouldReturnEmptyArrayIfElementsCannotBeFound() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elements = $this->driver->findElements(WebDriverBy::cssSelector('not_existing')); @@ -40,7 +40,7 @@ public function testShouldReturnEmptyArrayIfElementsCannotBeFound() public function testShouldFindMultipleElements() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); @@ -58,7 +58,7 @@ public function testEscapeCssSelector() 'CSS selectors containing special characters are not supported by the legacy protocol' ); - $this->driver->get($this->getTestPageUrl('escape_css.html')); + $this->driver->get($this->getTestPageUrl(TestPage::ESCAPE_CSS)); $element = $this->driver->findElement(WebDriverBy::id('.fo\'oo')); $this->assertSame('Foo', $element->getText()); diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 984c11978..81dbbd953 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -15,7 +15,7 @@ class RemoteWebDriverTest extends WebDriverTestCase */ public function testShouldGetPageTitle() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertEquals( 'php-webdriver test page', @@ -29,7 +29,7 @@ public function testShouldGetPageTitle() */ public function testShouldGetCurrentUrl() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); } @@ -39,7 +39,7 @@ public function testShouldGetCurrentUrl() */ public function testShouldGetPageSource() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $source = $this->driver->getPageSource(); $this->compatAssertStringContainsString('

', $source); @@ -118,7 +118,7 @@ public function testShouldQuitAndUnsetExecutor() */ public function testShouldGetWindowHandles() { - $this->driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $windowHandle = $this->driver->getWindowHandle(); $windowHandles = $this->driver->getWindowHandles(); @@ -142,7 +142,7 @@ public function testShouldGetWindowHandles() */ public function testShouldCloseWindow() { - $this->driver->get($this->getTestPageUrl('open_new_window.html')); + $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); $this->driver->wait()->until(WebDriverExpectedCondition::numberOfWindowsToBe(2)); @@ -160,7 +160,7 @@ public function testShouldCloseWindow() */ public function testShouldExecuteScriptAndDoNotBlockExecution() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -192,7 +192,7 @@ public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() { $this->driver->manage()->timeouts()->setScriptTimeout(1); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -232,7 +232,7 @@ public function testShouldExecuteScriptWithParamsAndReturnValue() { $this->driver->manage()->timeouts()->setScriptTimeout(1); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element1 = $this->driver->findElement(WebDriverBy::id('id_test')); $element2 = $this->driver->findElement(WebDriverBy::className('test_class')); @@ -257,7 +257,7 @@ public function testShouldTakeScreenshot() if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $outputPng = $this->driver->takeScreenshot(); @@ -282,7 +282,7 @@ public function testShouldSaveScreenshotToFile() $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/selenium-screenshot.png'; - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->driver->takeScreenshot($screenshotPath); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index bb6568f6d..04e39bf8b 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -21,7 +21,7 @@ class RemoteWebElementTest extends WebDriverTestCase */ public function testShouldGetText() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elementWithSimpleText = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithTextWithSpaces = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -35,7 +35,7 @@ public function testShouldGetText() */ public function testShouldGetAttributeValue() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('text-simple')); @@ -52,7 +52,7 @@ public function testShouldGetDomPropertyValue() { self::skipForJsonWireProtocol(); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('div-with-html')); @@ -72,7 +72,7 @@ public function testShouldGetDomPropertyValue() */ public function testShouldGetLocation() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); @@ -87,7 +87,7 @@ public function testShouldGetLocation() */ public function testShouldGetLocationOnScreenOnceScrolledIntoView() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-out-of-viewport')); @@ -112,7 +112,7 @@ public function testShouldGetLocationOnScreenOnceScrolledIntoView() */ public function testShouldGetSize() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); @@ -127,7 +127,7 @@ public function testShouldGetSize() */ public function testShouldGetCssValue() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elementWithBorder = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithoutBorder = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -147,7 +147,7 @@ public function testShouldGetCssValue() */ public function testShouldGetTagName() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $paragraphElement = $this->driver->findElement(WebDriverBy::id('id_test')); @@ -159,13 +159,13 @@ public function testShouldGetTagName() */ public function testShouldClick() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); $linkElement->click(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->assertTrue(true); // To generate coverage, see https://github.com/sebastianbergmann/phpunit/issues/3016 @@ -192,7 +192,7 @@ public function testGeckoDriverShouldClickOnBlockLevelElement() ]; foreach ($links as $linkid) { - $this->driver->get($this->getTestPageUrl('gecko653.html')); + $this->driver->get($this->getTestPageUrl(TestPage::GECKO_653)); $linkElement = $this->driver->findElement(WebDriverBy::id($linkid)); $linkElement->click(); @@ -213,7 +213,7 @@ public function testGeckoDriverShouldClickNotInteractable() { self::skipForUnmatchedBrowsers(['firefox']); - $this->driver->get($this->getTestPageUrl('gecko653.html')); + $this->driver->get($this->getTestPageUrl(TestPage::GECKO_653)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-index-plain-hidden')); @@ -239,7 +239,7 @@ public function testGeckoDriverShouldClickNotInteractable() */ public function testShouldClearFormElementText() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -258,7 +258,7 @@ public function testShouldClearFormElementText() */ public function testShouldSendKeysToFormElement() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -294,7 +294,7 @@ public function testShouldSendKeysToFormElement() */ public function testShouldDetectEnabledInputs() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $inputEnabled = $this->driver->findElement(WebDriverBy::id('input-text')); $inputDisabled = $this->driver->findElement(WebDriverBy::id('input-text-disabled')); @@ -308,7 +308,7 @@ public function testShouldDetectEnabledInputs() */ public function testShouldSelectedInputsOrOptions() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $checkboxSelected = $this->driver->findElement( WebDriverBy::cssSelector('input[name=checkbox][value=second]') @@ -336,7 +336,7 @@ public function testShouldSelectedInputsOrOptions() */ public function testShouldSubmitFormBySubmitEventOnForm() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $formElement = $this->driver->findElement(WebDriverBy::cssSelector('form')); @@ -354,7 +354,7 @@ public function testShouldSubmitFormBySubmitEventOnForm() */ public function testShouldSubmitFormBySubmitEventOnFormInputElement() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $inputTextElement = $this->driver->findElement(WebDriverBy::id('input-text')); @@ -372,7 +372,7 @@ public function testShouldSubmitFormBySubmitEventOnFormInputElement() */ public function testShouldSubmitFormByClickOnSubmitInput() { - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); $submitElement = $this->driver->findElement(WebDriverBy::id('submit')); @@ -390,7 +390,7 @@ public function testShouldSubmitFormByClickOnSubmitInput() */ public function testShouldCompareEqualsElement() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $differentElement = $this->driver->findElement(WebDriverBy::cssSelector('#text-simple')); @@ -409,7 +409,7 @@ public function testShouldCompareEqualsElement() */ public function testShouldThrowExceptionIfChildElementCannotBeFound() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $this->expectException(NoSuchElementException::class); @@ -418,7 +418,7 @@ public function testShouldThrowExceptionIfChildElementCannotBeFound() public function testShouldFindChildElementIfExistsOnAPage() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $childElement = $element->findElement(WebDriverBy::cssSelector('li')); @@ -430,7 +430,7 @@ public function testShouldFindChildElementIfExistsOnAPage() public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $childElements = $element->findElements(WebDriverBy::cssSelector('not_existing')); @@ -441,7 +441,7 @@ public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() public function testShouldFindMultipleChildElements() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $allElements = $this->driver->findElements(WebDriverBy::cssSelector('li')); @@ -472,7 +472,7 @@ public function testShouldTakeAndSaveElementScreenshot() $screenshotPath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-') . '/element-screenshot.png'; - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::id('red-box')); diff --git a/tests/functional/TestPage.php b/tests/functional/TestPage.php new file mode 100644 index 000000000..08cb86bec --- /dev/null +++ b/tests/functional/TestPage.php @@ -0,0 +1,25 @@ +driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-1')); @@ -49,7 +49,7 @@ public function testShouldClickOnElement() public function testShouldClickAndHoldOnElementAndRelease() { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-1')); @@ -78,7 +78,7 @@ public function testShouldContextClickOnElement() $this->markTestSkipped('Getting stuck in EdgeDriver'); } - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-2')); @@ -99,7 +99,7 @@ public function testShouldContextClickOnElement() */ public function testShouldDoubleClickOnElement() { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $element = $this->driver->findElement(WebDriverBy::id('item-3')); @@ -115,7 +115,7 @@ public function testShouldDoubleClickOnElement() */ public function testShouldSendKeysUpAndDown() { - $this->driver->get($this->getTestPageUrl('events.html')); + $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); $this->driver->action() ->keyDown(null, WebDriverKeys::CONTROL) @@ -144,7 +144,7 @@ public function testShouldSendKeysUpAndDown() */ public function testShouldMoveToElement() { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); @@ -167,7 +167,7 @@ public function testShouldMoveToElement() */ public function testShouldMoveByOffset() { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); @@ -190,7 +190,7 @@ public function testShouldMoveByOffset() */ public function testShouldDragAndDrop() { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); @@ -223,7 +223,7 @@ public function testShouldDragAndDrop() */ public function testShouldDragAndDropBy() { - $this->driver->get($this->getTestPageUrl('sortable.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index e75533b8d..622dd10dd 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -15,7 +15,7 @@ protected function setUp(): void { parent::setUp(); - $this->driver->get($this->getTestPageUrl('alert.html')); + $this->driver->get($this->getTestPageUrl(TestPage::ALERT)); } public function testShouldAcceptAlert() diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 6ff0c8a55..27ee04755 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -23,7 +23,7 @@ public function testShouldFindTextElementByLocator( $expectedText = null, $expectedAttributeValue = null ) { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $by = call_user_func([WebDriverBy::class, $webDriverByLocatorMethod], $webDriverByLocatorValue); $element = $this->driver->findElement($by); diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index e42ab144b..4d7f82b52 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -15,7 +15,7 @@ protected function setUp(): void { parent::setUp(); - $this->driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } public function testIsMultiple() diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php index 630656ea4..f3fea87e8 100644 --- a/tests/functional/WebDriverNavigationTest.php +++ b/tests/functional/WebDriverNavigationTest.php @@ -13,7 +13,7 @@ class WebDriverNavigationTest extends WebDriverTestCase */ public function testShouldNavigateToUrl() { - $this->driver->navigate()->to($this->getTestPageUrl('index.html')); + $this->driver->navigate()->to($this->getTestPageUrl(TestPage::INDEX)); $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); } @@ -24,25 +24,25 @@ public function testShouldNavigateToUrl() */ public function testShouldNavigateBackAndForward() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); $linkElement->click(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->driver->navigate()->back(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('index.html') + WebDriverExpectedCondition::urlContains(TestPage::INDEX) ); $this->driver->navigate()->forward(); $this->driver->wait()->until( - WebDriverExpectedCondition::urlContains('form.html') + WebDriverExpectedCondition::urlContains(TestPage::FORM) ); $this->assertTrue(true); // To generate coverage, see https://github.com/sebastianbergmann/phpunit/issues/3016 @@ -53,7 +53,7 @@ public function testShouldNavigateBackAndForward() */ public function testShouldRefreshPage() { - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); // Change input element content, to make sure it was refreshed (=> cleared to original value) $inputElement = $this->driver->findElement(WebDriverBy::name('test_name')); diff --git a/tests/functional/WebDriverOptionsCookiesTest.php b/tests/functional/WebDriverOptionsCookiesTest.php index 2b0301256..31b915fd9 100644 --- a/tests/functional/WebDriverOptionsCookiesTest.php +++ b/tests/functional/WebDriverOptionsCookiesTest.php @@ -13,7 +13,7 @@ protected function setUp(): void { parent::setUp(); - $this->driver->get($this->getTestPageUrl('index.html')); + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); } public function testShouldSetGetAndDeleteCookies() diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index b6f900d8a..d2c28d085 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -16,7 +16,7 @@ protected function setUp(): void { parent::setUp(); - $this->driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } public function testIsMultiple() diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index aa3cce1f2..01c075188 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -17,7 +17,7 @@ protected function setUp(): void { parent::setUp(); - $this->driver->get($this->getTestPageUrl('form.html')); + $this->driver->get($this->getTestPageUrl(TestPage::FORM)); } public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple() diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 72e5fd567..66f61f51a 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -17,7 +17,7 @@ class WebDriverTimeoutsTest extends WebDriverTestCase */ public function testShouldFailGettingDelayedElementWithoutWait() { - $this->driver->get($this->getTestPageUrl('delayed_element.html')); + $this->driver->get($this->getTestPageUrl(TestPage::DELAYED_ELEMENT)); $this->expectException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('delayed')); @@ -29,7 +29,7 @@ public function testShouldFailGettingDelayedElementWithoutWait() */ public function testShouldGetDelayedElementWithImplicitWait() { - $this->driver->get($this->getTestPageUrl('delayed_element.html')); + $this->driver->get($this->getTestPageUrl(TestPage::DELAYED_ELEMENT)); $this->driver->manage()->timeouts()->implicitlyWait(2); $element = $this->driver->findElement(WebDriverBy::id('delayed')); @@ -47,7 +47,7 @@ public function testShouldFailIfPageIsLoadingLongerThanPageLoadTimeout() $this->driver->manage()->timeouts()->pageLoadTimeout(1); try { - $this->driver->get($this->getTestPageUrl('slow_loading.html')); + $this->driver->get($this->getTestPageUrl(TestPage::SLOW_LOADING)); $this->fail('ScriptTimeoutException or TimeoutException exception should be thrown'); } catch (TimeoutException $e) { // thrown by Selenium 3.0.0+ } catch (ScriptTimeoutException $e) { // thrown by Selenium 2 From 41dc36f13aeee6985c10531feee5f3bba70da932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 7 Sep 2022 12:49:52 +0200 Subject: [PATCH 046/130] Update readme, as the W3C WebDriver support is no longer experimental --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2eb411645..2f2432095 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,11 @@ Php-webdriver library is PHP language binding for Selenium WebDriver, which allo This library is compatible with Selenium server version 2.x, 3.x and 4.x. -The library supports [JsonWireProtocol](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol) and also -implements **experimental support** of [W3C WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html). -The W3C WebDriver support is not yet full-featured, however it should allow to control Firefox via Geckodriver and new -versions of Chrome and Chromedriver with just a slight limitations. +The library supports modern [W3C WebDriver](https://w3c.github.io/webdriver/) protocol, as well +as legacy [JsonWireProtocol](https://www.selenium.dev/documentation/legacy/json_wire_protocol/). -The concepts of this library are very similar to the "official" Java, .NET, Python and Ruby bindings from the -[Selenium project](https://github.com/SeleniumHQ/selenium/). +The concepts of this library are very similar to the "official" Java, JavaScript, .NET, Python and Ruby libraries +which are developed as part of the [Selenium project](https://github.com/SeleniumHQ/selenium/). ## Installation @@ -208,8 +206,8 @@ There are some projects already providing this: - [Symfony Panther](https://github.com/symfony/panther) uses php-webdriver and integrates with PHPUnit using `PantherTestCase` - [Laravel Dusk](https://laravel.com/docs/dusk) is another project using php-webdriver, could be used for testing via `DuskTestCase` - [Steward](https://github.com/lmc-eu/steward) integrates php-webdriver directly to [PHPUnit](https://phpunit.de/), and provides parallelization -- [Codeception](http://codeception.com) testing framework provides BDD-layer on top of php-webdriver in its [WebDriver module](http://codeception.com/docs/modules/WebDriver) -- You can also check out this [blogpost](http://codeception.com/11-12-2013/working-with-phpunit-and-selenium-webdriver.html) + [demo project](https://github.com/DavertMik/php-webdriver-demo), describing simple [PHPUnit](https://phpunit.de/) integration +- [Codeception](https://codeception.com/) testing framework provides BDD-layer on top of php-webdriver in its [WebDriver module](https://codeception.com/docs/modules/WebDriver) +- You can also check out this [blogpost](https://codeception.com/11-12-2013/working-with-phpunit-and-selenium-webdriver.html) + [demo project](https://github.com/DavertMik/php-webdriver-demo), describing simple [PHPUnit](https://phpunit.de/) integration ## Support From e7e10d016a968c669445c19480bd68ee021d1b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 11 Sep 2022 16:48:07 +0200 Subject: [PATCH 047/130] Run no-response action less often --- .github/workflows/no-response.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-response.yaml b/.github/workflows/no-response.yaml index 69b1eb840..7147e3792 100644 --- a/.github/workflows/no-response.yaml +++ b/.github/workflows/no-response.yaml @@ -8,7 +8,7 @@ on: issue_comment: types: [created] schedule: - - cron: '33 * * * *' # every hour at :33 + - cron: '* */8 * * *' # every hour at :33 jobs: noResponse: From 8fa5706c6178e1cc73ef1fb4700072d9b879f975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 12 Sep 2022 16:04:09 +0200 Subject: [PATCH 048/130] Add isDisplayed test --- lib/Remote/RemoteWebElement.php | 2 ++ tests/functional/RemoteWebElementTest.php | 18 ++++++++++++++++++ tests/functional/web/index.html | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index add916934..499c53989 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -336,6 +336,8 @@ public function getText() * Is this element displayed or not? This method avoids the problem of having * to parse an element's "style" attribute. * + * However, not all browsers implement this feature, because it is not part of W3C WebDriver. + * @see https://w3c.github.io/webdriver/#element-displayedness * @return bool */ public function isDisplayed() diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 04e39bf8b..321a4aa98 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -289,6 +289,24 @@ public function testShouldSendKeysToFormElement() $this->assertSame('bat13 37', $textarea->getAttribute('value')); } + /** + * @covers ::isDisplayed + * @group exclude-safari + * Safari does not support /displayed endpoint. + */ + public function testShouldDetectElementDisplayedness() + { + $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); + + $visibleElement = $this->driver->findElement(WebDriverBy::cssSelector('.test_class')); + $elementOutOfViewport = $this->driver->findElement(WebDriverBy::id('element-out-of-viewport')); + $hiddenElement = $this->driver->findElement(WebDriverBy::id('hidden-element')); + + $this->assertTrue($visibleElement->isDisplayed()); + $this->assertTrue($elementOutOfViewport->isDisplayed()); + $this->assertFalse($hiddenElement->isDisplayed()); + } + /** * @covers ::isEnabled */ diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index 0f379e26b..ca989677a 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -76,5 +76,9 @@

Welcome to the php-webdriver testing page.

Bar

+ + From 09b3fd1db208ca7052db78c405fa6f5874da3605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 5 Sep 2022 12:36:00 +0200 Subject: [PATCH 049/130] Add missing method to EventFiringWebElement, as it will be part of the parent interface in the future --- lib/Support/Events/EventFiringWebElement.php | 10 ++++++++++ phpstan.neon | 15 +++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index a2fe4bb97..a791fa9f5 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -368,6 +368,16 @@ public function equals(WebDriverElement $other) } } + public function takeElementScreenshot($save_as = null) + { + try { + return $this->element->takeElementScreenshot($save_as); + } catch (WebDriverException $exception) { + $this->dispatchOnException($exception); + throw $exception; + } + } + /** * @param WebDriverException $exception */ diff --git a/phpstan.neon b/phpstan.neon index 846484976..d2b36c437 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -5,10 +5,17 @@ parameters: - tests/ ignoreErrors: - # To be fixed: - - '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' + # To be fixed in next major version: + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' + path: 'lib/Interactions/WebDriverTouchActions.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' + path: 'lib/Support/Events/EventFiringWebDriver.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::takeElementScreenshot\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' - '#Unsafe usage of new static\(\)#' # Parameter is intentionally not part of signature to not break BC From 8c1cc96a3147c69c767e895d9df4361c648c90ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Sep 2022 17:38:09 +0200 Subject: [PATCH 050/130] Add Shadow DOM support (fixes #1003) --- lib/Exception/DetachedShadowRootException.php | 10 ++ lib/Exception/NoSuchShadowRootException.php | 10 ++ lib/Exception/WebDriverException.php | 4 + lib/Remote/DriverCommand.php | 3 + lib/Remote/HttpCommandExecutor.php | 12 +++ lib/Remote/RemoteWebElement.php | 21 ++++ lib/Remote/ShadowRoot.php | 95 +++++++++++++++++++ lib/Support/Events/EventFiringWebElement.php | 10 ++ lib/WebDriverElement.php | 10 ++ lib/WebDriverSearchContext.php | 12 +-- phpstan.neon | 2 + tests/functional/ShadowDomTest.php | 85 +++++++++++++++++ tests/functional/web/index.html | 2 + tests/functional/web/web_components.html | 38 ++++++++ 14 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 lib/Exception/DetachedShadowRootException.php create mode 100644 lib/Exception/NoSuchShadowRootException.php create mode 100644 lib/Remote/ShadowRoot.php create mode 100644 tests/functional/ShadowDomTest.php create mode 100644 tests/functional/web/web_components.html diff --git a/lib/Exception/DetachedShadowRootException.php b/lib/Exception/DetachedShadowRootException.php new file mode 100644 index 000000000..b9bfb39cb --- /dev/null +++ b/lib/Exception/DetachedShadowRootException.php @@ -0,0 +1,10 @@ + ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], + DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT => [ + 'method' => 'POST', + 'url' => '/session/:sessionId/shadow/:id/element', + ], + DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT => [ + 'method' => 'POST', + 'url' => '/session/:sessionId/shadow/:id/elements', + ], DriverCommand::FULLSCREEN_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/fullscreen'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'GET', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], @@ -150,6 +158,10 @@ class HttpCommandExecutor implements WebDriverCommandExecutor 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/property/:name', ], + DriverCommand::GET_ELEMENT_SHADOW_ROOT => [ + 'method' => 'GET', + 'url' => '/session/:sessionId/element/:id/shadow', + ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'], DriverCommand::GET_WINDOW_POSITION => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 499c53989..2e5eae131 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -532,6 +532,27 @@ public function equals(WebDriverElement $other) ]); } + /** + * Get representation of an element's shadow root for accessing the shadow DOM of a web component. + * + * @return ShadowRoot + */ + public function getShadowRoot() + { + if (!$this->isW3cCompliant) { + throw new UnsupportedOperationException('This method is only supported in W3C mode'); + } + + $response = $this->executor->execute( + DriverCommand::GET_ELEMENT_SHADOW_ROOT, + [ + ':id' => $this->id, + ] + ); + + return ShadowRoot::createFromResponse($this->executor, $response); + } + /** * Attempt to click on a child level element. * diff --git a/lib/Remote/ShadowRoot.php b/lib/Remote/ShadowRoot.php new file mode 100644 index 000000000..1ec13a217 --- /dev/null +++ b/lib/Remote/ShadowRoot.php @@ -0,0 +1,95 @@ +executor = $executor; + $this->id = $id; + } + + /** + * @param RemoteExecuteMethod $executor + * @param array $response + * @return self + */ + public static function createFromResponse(RemoteExecuteMethod $executor, array $response) + { + if (empty($response[self::SHADOW_ROOT_IDENTIFIER])) { + throw new UnknownErrorException('Shadow root is missing in server response'); + } + + return new self($executor, $response[self::SHADOW_ROOT_IDENTIFIER]); + } + + /** + * @param WebDriverBy $locator + * @return RemoteWebElement + */ + public function findElement(WebDriverBy $locator) + { + $params = JsonWireCompat::getUsing($locator, true); + $params[':id'] = $this->id; + + $rawElement = $this->executor->execute( + DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT, + $params + ); + + return new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true); + } + + /** + * @param WebDriverBy $locator + * @return WebDriverElement[] + */ + public function findElements(WebDriverBy $locator) + { + $params = JsonWireCompat::getUsing($locator, true); + $params[':id'] = $this->id; + + $rawElements = $this->executor->execute( + DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT, + $params + ); + + $elements = []; + foreach ($rawElements as $rawElement) { + $elements[] = new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true); + } + + return $elements; + } + + /** + * @return string + */ + public function getID() + { + return $this->id; + } +} diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index a791fa9f5..fa18b1a93 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -378,6 +378,16 @@ public function takeElementScreenshot($save_as = null) } } + public function getShadowRoot() + { + try { + return $this->element->getShadowRoot(); + } catch (WebDriverException $exception) { + $this->dispatchOnException($exception); + throw $exception; + } + } + /** * @param WebDriverException $exception */ diff --git a/lib/WebDriverElement.php b/lib/WebDriverElement.php index 3409dde18..8ffa18383 100644 --- a/lib/WebDriverElement.php +++ b/lib/WebDriverElement.php @@ -2,6 +2,8 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Remote\ShadowRoot; + /** * Interface for an HTML element in the WebDriver framework. */ @@ -141,4 +143,12 @@ public function getID(); * @todo Add in next major release (BC) */ //public function takeElementScreenshot($save_as = null); + + /** + * Get representation of an element's shadow root for accessing the shadow DOM of a web component. + * + * @return ShadowRoot + * @todo Add in next major release (BC) + */ + //public function getShadowRoot(); } diff --git a/lib/WebDriverSearchContext.php b/lib/WebDriverSearchContext.php index 8a723d8e7..82a5bde8d 100644 --- a/lib/WebDriverSearchContext.php +++ b/lib/WebDriverSearchContext.php @@ -3,18 +3,15 @@ namespace Facebook\WebDriver; /** - * The interface for WebDriver and WebDriverElement which is able to search for - * WebDriverElement inside. + * The interface for WebDriver and WebDriverElement which is able to search for WebDriverElement inside. */ interface WebDriverSearchContext { /** - * Find the first WebDriverElement within this element using the given - * mechanism. + * Find the first WebDriverElement within this element using the given mechanism. * * @param WebDriverBy $locator - * @return WebDriverElement NoSuchElementException is thrown in - * HttpCommandExecutor if no element is found. + * @return WebDriverElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found. * @see WebDriverBy */ public function findElement(WebDriverBy $locator); @@ -23,8 +20,7 @@ public function findElement(WebDriverBy $locator); * Find all WebDriverElements within this element using the given mechanism. * * @param WebDriverBy $locator - * @return WebDriverElement[] A list of all WebDriverElements, or an empty array if - * nothing matches + * @return WebDriverElement[] A list of all WebDriverElements, or an empty array if nothing matches * @see WebDriverBy */ public function findElements(WebDriverBy $locator); diff --git a/phpstan.neon b/phpstan.neon index d2b36c437..747193ddf 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,6 +16,8 @@ parameters: path: 'lib/Support/Events/EventFiringWebElement.php' - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::takeElementScreenshot\(\)#' path: 'lib/Support/Events/EventFiringWebElement.php' + - message: '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getShadowRoot\(\)#' + path: 'lib/Support/Events/EventFiringWebElement.php' - '#Unsafe usage of new static\(\)#' # Parameter is intentionally not part of signature to not break BC diff --git a/tests/functional/ShadowDomTest.php b/tests/functional/ShadowDomTest.php new file mode 100644 index 000000000..ccd144bc9 --- /dev/null +++ b/tests/functional/ShadowDomTest.php @@ -0,0 +1,85 @@ +driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + $shadowRoot = $element->getShadowRoot(); + + $this->assertInstanceOf(ShadowRoot::class, $shadowRoot); + } + + public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavingShadowRoot() + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('#no-shadow-root')); + + $this->expectException(NoSuchShadowRootException::class); + $element->getShadowRoot(); + } + + /** + * @group exclude-firefox + * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 + * Finding elements in shadow DOM is not implemented in Geckodriver + */ + public function testShouldFindElementUnderShadowRoot() + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + $shadowRoot = $element->getShadowRoot(); + + $elementInShadow = $shadowRoot->findElement(WebDriverBy::cssSelector('input')); + $this->assertSame('checkbox', $elementInShadow->getAttribute('type')); + + $elementsInShadow = $shadowRoot->findElements(WebDriverBy::cssSelector('div')); + $this->assertCount(2, $elementsInShadow); + } + + /** + * @group exclude-firefox + * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 + * Finding elements in shadow DOM is not implemented in Geckodriver + */ + public function testShouldReferenceTheSameShadowRootAsFromExecuteScript() + { + $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); + + $element = $this->driver->findElement(WebDriverBy::cssSelector('custom-checkbox-element')); + + /** @var WebDriverElement $elementFromScript */ + $elementFromScript = $this->driver->executeScript( + 'return arguments[0].shadowRoot;', + [$element] + ); + + $shadowRoot = $element->getShadowRoot(); + + $this->assertSame($shadowRoot->getId(), reset($elementFromScript)); + } +} diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index ca989677a..6202e3e9b 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -36,6 +36,8 @@

Welcome to the php-webdriver testing page.

Events | Sortable + | + WebComponents

Test by ID

Test by Class

diff --git a/tests/functional/web/web_components.html b/tests/functional/web/web_components.html new file mode 100644 index 000000000..c76101356 --- /dev/null +++ b/tests/functional/web/web_components.html @@ -0,0 +1,38 @@ + + + + + WebComponents and Shadow DOM tests + + + + +

WebComponents and Shadow DOM tests

+ +
+
+ +
+ + + + From b9c084e83e0a3f67cab4f4ac72a6c47fc222fbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 13 Sep 2022 17:05:21 +0200 Subject: [PATCH 051/130] Add element isDisplayed support even for browsers without native support (fixes #843) --- CHANGELOG.md | 1 + composer.json | 4 +- lib/Remote/RemoteWebDriver.php | 8 + lib/Remote/RemoteWebElement.php | 2 - lib/Support/IsElementDisplayedAtom.php | 71 +++++++ lib/scripts/isElementDisplayed.js | 219 ++++++++++++++++++++++ tests/functional/RemoteWebElementTest.php | 4 +- 7 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 lib/Support/IsElementDisplayedAtom.php create mode 100644 lib/scripts/isElementDisplayed.js diff --git a/CHANGELOG.md b/CHANGELOG.md index f89893ed6..32b4c737f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Added - Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method. - `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile. +- Element `isDisplayed()` can now be used even for browsers not supporting native API endpoint (like Safari), thanks to javascript atom workaround. ### Changed - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. diff --git a/composer.json b/composer.json index 90f2d0f28..13b925349 100644 --- a/composer.json +++ b/composer.json @@ -75,12 +75,12 @@ "analyze": [ "vendor/bin/phpstan analyze -c phpstan.neon --ansi", "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", - "vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/" + "vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "fix": [ "@composer normalize", "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0", - "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" + "vendor/bin/phpcbf --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "lint": [ "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 409cfce2a..9c18cb886 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -5,6 +5,7 @@ use Facebook\WebDriver\Exception\UnknownErrorException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; +use Facebook\WebDriver\Support\IsElementDisplayedAtom; use Facebook\WebDriver\Support\ScreenshotHelper; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverBy; @@ -574,6 +575,13 @@ public static function getAllSessions($selenium_server_url = 'http://localhost:4 public function execute($command_name, $params = []) { + // As we so far only use atom for IS_ELEMENT_DISPLAYED, this condition is hardcoded here. In case more atoms + // are used, this should be rewritten and separated from this class (e.g. to some abstract matcher logic). + if ($command_name === DriverCommand::IS_ELEMENT_DISPLAYED + && IsElementDisplayedAtom::match($this->getCapabilities()->getBrowserName())) { + return (new IsElementDisplayedAtom($this))->execute($params); + } + $command = new WebDriverCommand( $this->sessionID, $command_name, diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 2e5eae131..07c51e33f 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -336,8 +336,6 @@ public function getText() * Is this element displayed or not? This method avoids the problem of having * to parse an element's "style" attribute. * - * However, not all browsers implement this feature, because it is not part of W3C WebDriver. - * @see https://w3c.github.io/webdriver/#element-displayedness * @return bool */ public function isDisplayed() diff --git a/lib/Support/IsElementDisplayedAtom.php b/lib/Support/IsElementDisplayedAtom.php new file mode 100644 index 000000000..b27e9cca7 --- /dev/null +++ b/lib/Support/IsElementDisplayedAtom.php @@ -0,0 +1,71 @@ +driver = $driver; + } + + public static function match($browserName) + { + return !in_array($browserName, self::BROWSERS_WITH_ENDPOINT_SUPPORT, true); + } + + public function execute($params) + { + $element = new RemoteWebElement( + new RemoteExecuteMethod($this->driver), + $params[':id'], + $this->driver->isW3cCompliant() + ); + + return $this->executeAtom('isElementDisplayed', $element); + } + + protected function executeAtom($atomName, ...$params) + { + return $this->driver->executeScript( + sprintf('%s; return (%s).apply(null, arguments);', $this->loadAtomScript($atomName), $atomName), + $params + ); + } + + private function loadAtomScript($atomName) + { + return file_get_contents(__DIR__ . '/../scripts/' . $atomName . '.js'); + } +} diff --git a/lib/scripts/isElementDisplayed.js b/lib/scripts/isElementDisplayed.js new file mode 100644 index 000000000..f24bfa55e --- /dev/null +++ b/lib/scripts/isElementDisplayed.js @@ -0,0 +1,219 @@ +/* + * Imported from WebdriverIO project. + * https://github.com/webdriverio/webdriverio/blob/main/packages/webdriverio/src/scripts/isElementDisplayed.ts + * + * Copyright (C) 2017 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * check if element is visible + * @param {HTMLElement} elem element to check + * @return {Boolean} true if element is within viewport + */ +function isElementDisplayed(element) { + function nodeIsElement(node) { + if (!node) { + return false; + } + + switch (node.nodeType) { + case Node.ELEMENT_NODE: + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + return true; + default: + return false; + } + } + function parentElementForElement(element) { + if (!element) { + return null; + } + return enclosingNodeOrSelfMatchingPredicate(element.parentNode, nodeIsElement); + } + function enclosingNodeOrSelfMatchingPredicate(targetNode, predicate) { + for (let node = targetNode; node && node !== targetNode.ownerDocument; node = node.parentNode) + if (predicate(node)) { + return node; + } + return null; + } + function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) { + for (let element = targetElement; element && element !== targetElement.ownerDocument; element = parentElementForElement(element)) + if (predicate(element)) { + return element; + } + return null; + } + function cascadedStylePropertyForElement(element, property) { + if (!element || !property) { + return null; + } + // if document-fragment, skip it and use element.host instead. This happens + // when the element is inside a shadow root. + // window.getComputedStyle errors on document-fragment. + if (element instanceof ShadowRoot) { + element = element.host; + } + let computedStyle = window.getComputedStyle(element); + let computedStyleProperty = computedStyle.getPropertyValue(property); + if (computedStyleProperty && computedStyleProperty !== 'inherit') { + return computedStyleProperty; + } + // Ideally getPropertyValue would return the 'used' or 'actual' value, but + // it doesn't for legacy reasons. So we need to do our own poor man's cascade. + // Fall back to the first non-'inherit' value found in an ancestor. + // In any case, getPropertyValue will not return 'initial'. + // FIXME: will this incorrectly inherit non-inheritable CSS properties? + // I think all important non-inheritable properties (width, height, etc.) + // for our purposes here are specially resolved, so this may not be an issue. + // Specification is here: https://drafts.csswg.org/cssom/#resolved-values + let parentElement = parentElementForElement(element); + return cascadedStylePropertyForElement(parentElement, property); + } + function elementSubtreeHasNonZeroDimensions(element) { + let boundingBox = element.getBoundingClientRect(); + if (boundingBox.width > 0 && boundingBox.height > 0) { + return true; + } + // Paths can have a zero width or height. Treat them as shown if the stroke width is positive. + if (element.tagName.toUpperCase() === 'PATH' && boundingBox.width + boundingBox.height > 0) { + let strokeWidth = cascadedStylePropertyForElement(element, 'stroke-width'); + return !!strokeWidth && (parseInt(strokeWidth, 10) > 0); + } + let cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow'); + if (cascadedOverflow === 'hidden') { + return false; + } + // If the container's overflow is not hidden and it has zero size, consider the + // container to have non-zero dimensions if a child node has non-zero dimensions. + return Array.from(element.childNodes).some((childNode) => { + if (childNode.nodeType === Node.TEXT_NODE) { + return true; + } + if (nodeIsElement(childNode)) { + return elementSubtreeHasNonZeroDimensions(childNode); + } + return false; + }); + } + function elementOverflowsContainer(element) { + let cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow'); + if (cascadedOverflow !== 'hidden') { + return false; + } + // FIXME: this needs to take into account the scroll position of the element, + // the display modes of it and its ancestors, and the container it overflows. + // See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases. + return true; + } + function isElementSubtreeHiddenByOverflow(element) { + if (!element) { + return false; + } + if (!elementOverflowsContainer(element)) { + return false; + } + if (!element.childNodes.length) { + return false; + } + // This element's subtree is hidden by overflow if all child subtrees are as well. + return Array.from(element.childNodes).every((childNode) => { + // Returns true if the child node is overflowed or otherwise hidden. + // Base case: not an element, has zero size, scrolled out, or doesn't overflow container. + // Visibility of text nodes is controlled by parent + if (childNode.nodeType === Node.TEXT_NODE) { + return false; + } + if (!nodeIsElement(childNode)) { + return true; + } + if (!elementSubtreeHasNonZeroDimensions(childNode)) { + return true; + } + // Recurse. + return isElementSubtreeHiddenByOverflow(childNode); + }); + } + // walk up the tree testing for a shadow root + function isElementInsideShadowRoot(element) { + if (!element) { + return false; + } + if (element.parentNode && element.parentNode.host) { + return true; + } + return isElementInsideShadowRoot(element.parentNode); + } + // This is a partial reimplementation of Selenium's "element is displayed" algorithm. + // When the W3C specification's algorithm stabilizes, we should implement that. + // If this command is misdirected to the wrong document (and is NOT inside a shadow root), treat it as not shown. + if (!isElementInsideShadowRoot(element) && !document.contains(element)) { + return false; + } + // Special cases for specific tag names. + switch (element.tagName.toUpperCase()) { + case 'BODY': + return true; + case 'SCRIPT': + case 'NOSCRIPT': + return false; + case 'OPTGROUP': + case 'OPTION': { + // Option/optgroup are considered shown if the containing is considered not shown. + if (element.type === 'hidden') { + return false; + } + break; + // case 'MAP': + // FIXME: Selenium has special handling for elements. We don't do anything now. + default: + break; + } + if (cascadedStylePropertyForElement(element, 'visibility') !== 'visible') { + return false; + } + let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { + return Number(cascadedStylePropertyForElement(e, 'opacity')) === 0; + }); + let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { + return cascadedStylePropertyForElement(e, 'display') === 'none'; + }); + if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone) { + return false; + } + if (!elementSubtreeHasNonZeroDimensions(element)) { + return false; + } + if (isElementSubtreeHiddenByOverflow(element)) { + return false; + } + return true; +} + diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 321a4aa98..b8e9f1cf8 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -291,8 +291,8 @@ public function testShouldSendKeysToFormElement() /** * @covers ::isDisplayed - * @group exclude-safari - * Safari does not support /displayed endpoint. + * @covers \Facebook\WebDriver\Remote\RemoteWebDriver::execute + * @covers \Facebook\WebDriver\Support\IsElementDisplayedAtom */ public function testShouldDetectElementDisplayedness() { From 4b82945b4b36a1ef81c5c0379a220b77acee035d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 20 Sep 2022 12:10:06 +0200 Subject: [PATCH 052/130] Add coveralls coverage workaround See https://github.com/lemurheavy/coveralls-public/issues/1653#issuecomment-1251587119 --- .github/workflows/coveralls-workaround.yaml | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/coveralls-workaround.yaml diff --git a/.github/workflows/coveralls-workaround.yaml b/.github/workflows/coveralls-workaround.yaml new file mode 100644 index 000000000..fc35817a2 --- /dev/null +++ b/.github/workflows/coveralls-workaround.yaml @@ -0,0 +1,22 @@ +name: Coveralls coverage +# Must be run in separate workflow to have access to repository secrets even for PR from forks. +# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + +permissions: + contents: read + +on: + workflow_run: + workflows: [ "Tests" ] + types: + - completed + +jobs: + coveralls: + name: Coveralls coverage workaround + runs-on: ubuntu-latest + steps: + - name: Coveralls coverage workaround + # see https://github.com/lemurheavy/coveralls-public/issues/1653#issuecomment-1251587119 + run: | + curl --location --request GET 'https://coveralls.io/rerun_build?repo_token=${{%20secrets.COVERALLS_REPO_TOKEN%20}}&build_num=${{%20github.run_id%20}}' From 66371abd2c1f282689d03e33202ecf86f52cf2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 21 Sep 2022 18:39:03 +0200 Subject: [PATCH 053/130] Fix coveralls workaround --- .github/workflows/coveralls-workaround.yaml | 28 ++++++++++++++++++++- .github/workflows/tests.yaml | 9 +++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coveralls-workaround.yaml b/.github/workflows/coveralls-workaround.yaml index fc35817a2..2e4fc227c 100644 --- a/.github/workflows/coveralls-workaround.yaml +++ b/.github/workflows/coveralls-workaround.yaml @@ -15,8 +15,34 @@ jobs: coveralls: name: Coveralls coverage workaround runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' steps: + # see https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + - name: 'Download artifact' + uses: actions/github-script@v6 + with: + script: | + var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "data" + })[0]; + var download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/data.zip', Buffer.from(download.data)); + - run: unzip data.zip - name: Coveralls coverage workaround # see https://github.com/lemurheavy/coveralls-public/issues/1653#issuecomment-1251587119 run: | - curl --location --request GET 'https://coveralls.io/rerun_build?repo_token=${{%20secrets.COVERALLS_REPO_TOKEN%20}}&build_num=${{%20github.run_id%20}}' + BUILD_NUM=$(cat run_id) + curl --location --request GET "https://coveralls.io/rerun_build?repo_token=${{%20secrets.COVERALLS_REPO_TOKEN%20}}&build_num=$BUILD_NUM" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2452c8619..8d0e76e90 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -205,3 +205,12 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true + # Save run_id to be used by follow-up workflow + - name: Save run_id to file + run: | + mkdir -p ./data + echo ${{ github.run_id }} > ./data/run_id + - uses: actions/upload-artifact@v3 + with: + name: data + path: data/ From aad77b446a302985693fb339d40185be07c8f42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 3 Oct 2022 13:40:29 +0200 Subject: [PATCH 054/130] Release version 1.13.0 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32b4c737f..28d50eb5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.13.0 - 2022-10-03 ### Added - Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method. - `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile. @@ -11,10 +13,10 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. - Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile. - Deprecate `getAllSessions()` method of `RemoteWebDriver` (which is not part of W3C WebDriver). +- Increase default request timeout to 3 minutes (instead of 30 seconds). ### Fixed - Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands. -- Increase default request timeout to 3 minutes (instead of 30 seconds). ## 1.12.1 - 2022-05-03 ### Fixed From 514a821abe39191e96c5253a9823c80ca40755bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 7 Oct 2022 11:39:07 +0200 Subject: [PATCH 055/130] Replace issue template with issue form --- .github/ISSUE_TEMPLATE/bug_report.md | 65 --------- .github/ISSUE_TEMPLATE/bug_report.yaml | 124 ++++++++++++++++++ .../{config.yml => config.yaml} | 0 3 files changed, 124 insertions(+), 65 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml rename .github/ISSUE_TEMPLATE/{config.yml => config.yaml} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dcfc43785..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -name: 🐛 Bug report -about: Create a bug report to help us improve php-webdriver -title: '' -labels: bug -assignees: '' ---- - - - -### Bug description - - -### How could the issue be reproduced - -Steps to reproduce the behavior: -1. -2. -3. ... - - - -```php -// You can insert your PHP code here (or remove this block if it is not relevant for the issue). - -// For example you can provide how you create WebDrivere instance: -$capabilities = DesiredCapabilities::chrome(); -$driver = RemoteWebDriver::create('http://localhost:4444/', $capabilities); - -// And the code you use to execute the php-webdriver commands, for example: -$driver->get('http://site.localhost/foo.html'); -$button = $driver->findElement(WebDriverBy::cssSelector('#foo')); -$button->click(); -``` - -```html - -
- -
-``` - -### Expected behavior - - -### Details - - -* Php-webdriver version: -* PHP version: -* How do you start the browser driver or Selenium server: - - -* Selenium server version: -* Browser driver (chromedriver/geckodriver...) version: -* Browser used + version: -* Operating system: - -### Additional context - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 000000000..bac315d1d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,124 @@ +name: 🐛 Bug report +description: Create a bug report to help us improve php-webdriver +labels: [ "bug" ] +body: + - type: markdown + attributes: + value: | + If you have a question, [ask in Discussions](https://github.com/php-webdriver/php-webdriver/discussions) instead of filling a bug. + + If you are reporting a bug, please **fill as much as possible information**, otherwise the community and maintainers cannot provide a prompt feedback and help solving the issue. + - type: textarea + id: bug-description + attributes: + label: Bug description + description: | + A clear description of what the bug is. + validations: + required: true + + - type: textarea + id: steps-to-reproduce + attributes: + label: How could the issue be reproduced + description: | + Provide steps to reproduce the behavior. Please include everything relevant - the PHP code you use to initialize driver instance, the PHP code causing the error, HTML snippet or URL of the page where you encounter the issue etc. + This will be automatically formatted into code, so no need for backticks ```. + placeholder: | + // For example you can provide how you create WebDriver instance: + $capabilities = DesiredCapabilities::chrome(); + $driver = RemoteWebDriver::create('http://localhost:4444/', $capabilities); + // And the code you use to execute the php-webdriver commands, for example: + $driver->get('http://site.localhost/foo.html'); + $button = $driver->findElement(WebDriverBy::cssSelector('#foo')); + $button->click(); + +
+ +
+ + render: shell + validations: + required: true + + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: | + A clear and concise description of what you expected to happen. + validations: + required: false + + - type: input + id: php-webdriver-version + attributes: + label: Php-webdriver version + description: You can run `composer show php-webdriver/webdriver` to find the version number + placeholder: | + For example: 1.13.0 + validations: + required: true + + - type: input + id: php-version + attributes: + label: PHP version + description: You can run `php -v` to find the version + placeholder: | + For example: 8.1.11 + validations: + required: true + + - type: input + id: how-start + attributes: + label: How do you start the browser driver or Selenium server + description: | + For example: Selenium server jar, Selenium in Docker, chromedriver command, Laravel Dusk, SauceLabs etc. + If relevant, provide the complete command you use to start the browser driver or Selenium server + validations: + required: true + + - type: input + id: selenium-version + attributes: + label: Selenium server / Selenium Docker image version + description: Relevant only if you use Selenium server / Selenium in Docker + validations: + required: false + + - type: input + id: browser-driver + attributes: + label: Browser driver (chromedriver/geckodriver...) version + description: You can run `chromedriver --version` or `geckodriver --version` to find the version + placeholder: | + For example: geckodriver 0.31.0 + validations: + required: false + + - type: input + id: browser + attributes: + label: Browser name and version + placeholder: | + For example: Firefox 105.0.2 + validations: + required: false + + - type: input + id: operating-system + attributes: + label: Operating system + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: | + Add any other context or you notes about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yaml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yml rename to .github/ISSUE_TEMPLATE/config.yaml From dd865df75b42f3051ef2bbb3e5f946270e497194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Oct 2022 17:38:29 +0200 Subject: [PATCH 056/130] Replace yaml extension to yml, to fix github non-conforming with the standard --- .github/ISSUE_TEMPLATE/{bug_report.yaml => bug_report.yml} | 0 .github/ISSUE_TEMPLATE/{config.yaml => config.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{bug_report.yaml => bug_report.yml} (100%) rename .github/ISSUE_TEMPLATE/{config.yaml => config.yml} (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/bug_report.yaml rename to .github/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .github/ISSUE_TEMPLATE/config.yaml rename to .github/ISSUE_TEMPLATE/config.yml From 83cefc5511aafdee0c09fe9a4e1451618245d3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Oct 2022 17:29:21 +0200 Subject: [PATCH 057/130] Fix firefox extension test flakiness --- tests/functional/Firefox/FirefoxProfileTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 4e16dd096..59d0f6bfa 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -19,6 +19,7 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\TestPage; use Facebook\WebDriver\WebDriverBy; +use Facebook\WebDriver\WebDriverExpectedCondition; use Facebook\WebDriver\WebDriverTestCase; use PHPUnit\Framework\TestCase; @@ -86,7 +87,11 @@ public function testShouldInstallExtension() $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); - $element = $this->driver->findElement(WebDriverBy::id('webDriverExtensionTest')); + // it sometimes takes split of a second for the extension to render the element, so we must use wait + $element = $this->driver->wait(5, 1)->until( + WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::id('webDriverExtensionTest')) + ); + $this->assertEquals('This element was added by browser extension', $element->getText()); } From 5848cdbef1694a005991c7c85d1787cbaf0f94f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 11 Oct 2022 13:34:04 +0200 Subject: [PATCH 058/130] Do not fail in isDisplayed call when capabilities are missing in WebDriver (fixes #1022) --- CHANGELOG.md | 2 ++ lib/Remote/RemoteWebDriver.php | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d50eb5a..46eada656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- Do not fail when using `isDisplayed()` and capabilities are missing in WebDriver instance. (Happens when driver instance was created using `RemoteWebDriver::createBySessionID()`.) ## 1.13.0 - 2022-10-03 ### Added diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 9c18cb886..3caaa7fc8 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -24,7 +24,7 @@ class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInpu */ protected $executor; /** - * @var WebDriverCapabilities + * @var WebDriverCapabilities|null */ protected $capabilities; @@ -544,7 +544,7 @@ public function getSessionID() /** * Get capabilities of the RemoteWebDriver. * - * @return WebDriverCapabilities + * @return WebDriverCapabilities|null */ public function getCapabilities() { @@ -578,7 +578,13 @@ public function execute($command_name, $params = []) // As we so far only use atom for IS_ELEMENT_DISPLAYED, this condition is hardcoded here. In case more atoms // are used, this should be rewritten and separated from this class (e.g. to some abstract matcher logic). if ($command_name === DriverCommand::IS_ELEMENT_DISPLAYED - && IsElementDisplayedAtom::match($this->getCapabilities()->getBrowserName())) { + && ( + // When capabilities are missing in php-webdriver 1.13.x, always fallback to use the atom + $this->getCapabilities() === null + // If capabilities are present, use the atom only if condition matches + || IsElementDisplayedAtom::match($this->getCapabilities()->getBrowserName()) + ) + ) { return (new IsElementDisplayedAtom($this))->execute($params); } From 6dfe5f814b796c1b5748850aa19f781b9274c36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 11 Oct 2022 13:49:44 +0200 Subject: [PATCH 059/130] Release version 1.13.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46eada656..e1d482657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.13.1 - 2022-10-11 ### Fixed - Do not fail when using `isDisplayed()` and capabilities are missing in WebDriver instance. (Happens when driver instance was created using `RemoteWebDriver::createBySessionID()`.) From 9282b64decbbbf62948b0ff19aa9aa2a430f165d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Oct 2022 12:03:18 +0200 Subject: [PATCH 060/130] Require PHP ^7.3 --- .github/workflows/tests.yaml | 4 ++-- CHANGELOG.md | 3 +++ composer.json | 12 ++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8d0e76e90..4525576d8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -46,10 +46,10 @@ jobs: strategy: matrix: - php-version: ['5.6', '7.1', '7.2', '7.3', '7.4', '8.0'] + php-version: ['7.3', '7.4', '8.0'] dependencies: [''] include: - - { php-version: '5.6', dependencies: '--prefer-lowest' } + - { php-version: '7.3', dependencies: '--prefer-lowest' } - { php-version: '8.1', dependencies: '--ignore-platform-req=php' } steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d50eb5a..828c90f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Require PHP ^7.3. + ## 1.13.0 - 2022-10-03 ### Added - Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method. diff --git a/composer.json b/composer.json index 13b925349..23ce0f693 100644 --- a/composer.json +++ b/composer.json @@ -12,21 +12,21 @@ ], "homepage": "https://github.com/php-webdriver/php-webdriver", "require": { - "php": "^5.6 || ~7.0 || ^8.0", + "php": "^7.3 || ^8.0", "ext-curl": "*", "ext-json": "*", "ext-zip": "*", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0 || ^6.0" + "symfony/process": "^5.0 || ^6.0" }, "require-dev": { - "ondram/ci-detector": "^2.1 || ^3.5 || ^4.0", + "ondram/ci-detector": "^4.0", "php-coveralls/php-coveralls": "^2.4", - "php-mock/php-mock-phpunit": "^1.1 || ^2.0", + "php-mock/php-mock-phpunit": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", + "phpunit/phpunit": "^9", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0 || ^6.0" + "symfony/var-dumper": "^5.0 || ^6.0" }, "replace": { "facebook/webdriver": "*" From f125fb24cf28b6021948505c590c6faa055dea8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Oct 2022 14:27:13 +0200 Subject: [PATCH 061/130] Remove workaround for PHP <7.2 --- .github/workflows/tests.yaml | 4 ---- scripts/apply-phpunit-patches.sh | 20 -------------------- 2 files changed, 24 deletions(-) delete mode 100755 scripts/apply-phpunit-patches.sh diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4525576d8..ea9ccc99b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -66,10 +66,6 @@ jobs: - name: Install PHP dependencies run: composer update --no-interaction ${{ matrix.dependencies }} - - name: Apply PHPUnit patches - if: ${{ matrix.php-version < 7.2 }} - run: scripts/apply-phpunit-patches.sh - - name: Run tests run: vendor/bin/phpunit --testsuite unit --colors=always --coverage-clover ./logs/clover.xml diff --git a/scripts/apply-phpunit-patches.sh b/scripts/apply-phpunit-patches.sh deleted file mode 100755 index 478e01c74..000000000 --- a/scripts/apply-phpunit-patches.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# All commands below must no fail -set -e - -# Be in the root dir -cd "$(dirname $0)/../" - -find tests/ -type f -print0 | xargs -0 sed -i 's/function setUpBeforeClass(): void/function setUpBeforeClass()/g'; -find tests/ -type f -print0 | xargs -0 sed -i 's/function setUp(): void/function setUp()/g'; -find tests/ -type f -print0 | xargs -0 sed -i 's/function tearDown(): void/function tearDown()/g'; - -sed -i 's/endTest(\\PHPUnit\\Framework\\Test \$test, float \$time): void/endTest(\\PHPUnit_Framework_Test \$test, \$time)/g' tests/functional/ReportSauceLabsStatusListener.php; -sed -i 's/: void/ /g' tests/functional/ReportSauceLabsStatusListener.php; -# Drop the listener from the config file -sed -i '//,+2d' phpunit.xml.dist; -sed -i 's/function runBare(): void/function runBare()/g' tests/functional/WebDriverTestCase.php; - -# Return back to original dir -cd - > /dev/null From 939b77fd4b54424ec579be5944c1aa5b8c10d22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Oct 2022 14:46:42 +0200 Subject: [PATCH 062/130] Update codestyle to use PHP 7.3 features --- .php-cs-fixer.dist.php | 21 +- lib/Chrome/ChromeDevToolsDriver.php | 2 +- lib/Chrome/ChromeDriverService.php | 6 +- lib/Chrome/ChromeOptions.php | 4 +- lib/Firefox/FirefoxDriver.php | 2 +- lib/Firefox/FirefoxDriverService.php | 4 +- lib/Firefox/FirefoxOptions.php | 8 +- lib/Firefox/FirefoxPreferences.php | 8 +- .../Internal/WebDriverSingleKeyAction.php | 2 +- lib/Net/URLChecker.php | 4 +- lib/Remote/CustomWebDriverCommand.php | 4 +- lib/Remote/DesiredCapabilities.php | 4 +- lib/Remote/DriverCommand.php | 236 +++++++++--------- lib/Remote/HttpCommandExecutor.php | 6 +- lib/Remote/JsonWireCompat.php | 2 +- lib/Remote/RemoteMouse.php | 10 +- lib/Remote/RemoteWebElement.php | 14 +- lib/Remote/ShadowRoot.php | 2 +- lib/Remote/WebDriverBrowserType.php | 40 +-- lib/Remote/WebDriverCapabilityType.php | 32 +-- lib/Support/IsElementDisplayedAtom.php | 2 +- lib/WebDriverExpectedCondition.php | 8 +- lib/WebDriverKeys.php | 156 ++++++------ lib/WebDriverPlatform.php | 16 +- lib/WebDriverTargetLocator.php | 4 +- lib/WebDriverWait.php | 2 +- .../ReportSauceLabsStatusListener.php | 2 +- tests/functional/TestPage.php | 28 +-- tests/unit/Firefox/FirefoxOptionsTest.php | 2 +- tests/unit/Remote/DesiredCapabilitiesTest.php | 4 +- tests/unit/Support/ScreenshotHelperTest.php | 2 +- 31 files changed, 319 insertions(+), 318 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 5cf7d36f6..b677785f6 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -12,8 +12,9 @@ 'blank_line_before_statement' => ['statements' => ['return', 'try']], 'braces' => ['allow_single_line_anonymous_class_with_empty_body' => true, 'allow_single_line_closure' => true], 'cast_spaces' => true, - 'class_attributes_separation' => ['elements' => ['method' => 'one']], + 'class_attributes_separation' => ['elements' => ['method' => 'one', 'trait_import' => 'none']], 'clean_namespace' => true, + 'combine_nested_dirname' => true, 'compact_nullable_typehint' => true, 'concat_space' => ['spacing' => 'one'], 'declare_equal_normalize' => true, @@ -21,15 +22,18 @@ 'fopen_flags' => true, 'full_opening_tag' => true, 'function_typehint_space' => true, + 'heredoc_indentation' => ['indentation' => 'same_as_start'], 'implode_call' => true, 'is_null' => true, 'lambda_not_used_import' => true, 'linebreak_after_opening_tag' => true, + 'list_syntax' => true, 'lowercase_cast' => true, 'lowercase_static_reference' => true, 'magic_constant_casing' => true, 'magic_method_casing' => true, 'mb_str_functions' => true, + 'method_argument_space' => ['after_heredoc' => true], 'native_function_casing' => true, 'native_function_type_declaration_casing' => true, 'new_with_braces' => true, @@ -54,29 +58,30 @@ 'switch', 'throw', 'use', - 'use_trait', ], ], 'no_leading_import_slash' => true, 'no_leading_namespace_whitespace' => true, 'no_singleline_whitespace_before_semicolons' => true, - 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_comma_in_singleline' => true, 'no_unreachable_default_argument_value' => true, 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], 'no_whitespace_in_blank_line' => true, + 'non_printable_character' => true, 'object_operator_without_whitespace' => true, 'ordered_class_elements' => true, 'ordered_imports' => true, 'php_unit_construct' => true, 'php_unit_dedicate_assert' => false, - 'php_unit_expectation' => ['target' => '5.6'], + 'php_unit_expectation' => ['target' => '8.4'], 'php_unit_method_casing' => ['case' => 'camel_case'], 'php_unit_mock_short_will_return' => true, 'php_unit_mock' => true, - 'php_unit_namespaced' => ['target' => '5.7'], + 'php_unit_namespaced' => ['target' => '6.0'], 'php_unit_no_expectation_annotation' => true, 'php_unit_set_up_tear_down_visibility' => true, 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], @@ -94,6 +99,7 @@ 'phpdoc_types' => true, 'phpdoc_var_annotation_correct_order' => true, 'psr_autoloading' => true, + 'random_api_migration' => true, 'self_accessor' => true, 'set_type_to_cast' => true, 'short_scalar_cast' => true, @@ -107,10 +113,11 @@ 'switch_continue_to_break' => true, 'ternary_operator_spaces' => true, 'ternary_to_elvis_operator' => true, - 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'ternary_to_null_coalescing' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays'], 'after_heredoc' => true], 'trim_array_spaces' => true, 'unary_operator_spaces' => true, - 'visibility_required' => ['elements' => ['method', 'property']], + 'visibility_required' => ['elements' => ['method', 'property', 'const']], 'whitespace_after_comma_in_array' => true, 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], ]) diff --git a/lib/Chrome/ChromeDevToolsDriver.php b/lib/Chrome/ChromeDevToolsDriver.php index ffbb91a18..2d95d274b 100644 --- a/lib/Chrome/ChromeDevToolsDriver.php +++ b/lib/Chrome/ChromeDevToolsDriver.php @@ -11,7 +11,7 @@ */ class ChromeDevToolsDriver { - const SEND_COMMAND = [ + public const SEND_COMMAND = [ 'method' => 'POST', 'url' => '/session/:sessionId/goog/cdp/execute', ]; diff --git a/lib/Chrome/ChromeDriverService.php b/lib/Chrome/ChromeDriverService.php index a6d3eb834..902a48ded 100644 --- a/lib/Chrome/ChromeDriverService.php +++ b/lib/Chrome/ChromeDriverService.php @@ -10,14 +10,14 @@ class ChromeDriverService extends DriverService * The environment variable storing the path to the chrome driver executable. * @deprecated Use ChromeDriverService::CHROME_DRIVER_EXECUTABLE */ - const CHROME_DRIVER_EXE_PROPERTY = 'webdriver.chrome.driver'; + public const CHROME_DRIVER_EXE_PROPERTY = 'webdriver.chrome.driver'; /** @var string The environment variable storing the path to the chrome driver executable */ - const CHROME_DRIVER_EXECUTABLE = 'WEBDRIVER_CHROME_DRIVER'; + public const CHROME_DRIVER_EXECUTABLE = 'WEBDRIVER_CHROME_DRIVER'; /** * @var string Default executable used when no other is provided * @internal */ - const DEFAULT_EXECUTABLE = 'chromedriver'; + public const DEFAULT_EXECUTABLE = 'chromedriver'; /** * @return static diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index 0e547b209..86e133397 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -17,11 +17,11 @@ class ChromeOptions implements JsonSerializable * The key of chromeOptions in desired capabilities (in legacy OSS JsonWire protocol) * @todo Replace value with 'goog:chromeOptions' after JsonWire protocol support is removed */ - const CAPABILITY = 'chromeOptions'; + public const CAPABILITY = 'chromeOptions'; /** * The key of chromeOptions in desired capabilities (in W3C compatible protocol) */ - const CAPABILITY_W3C = 'goog:chromeOptions'; + public const CAPABILITY_W3C = 'goog:chromeOptions'; /** * @var array */ diff --git a/lib/Firefox/FirefoxDriver.php b/lib/Firefox/FirefoxDriver.php index 70f6ff0d9..9d86a2be4 100644 --- a/lib/Firefox/FirefoxDriver.php +++ b/lib/Firefox/FirefoxDriver.php @@ -16,7 +16,7 @@ class FirefoxDriver extends LocalWebDriver * $capabilities = DesiredCapabilities::firefox(); * $capabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); */ - const PROFILE = 'firefox_profile'; + public const PROFILE = 'firefox_profile'; /** * Creates a new FirefoxDriver using default configuration. diff --git a/lib/Firefox/FirefoxDriverService.php b/lib/Firefox/FirefoxDriverService.php index 525c5b5bc..83c6a28fb 100644 --- a/lib/Firefox/FirefoxDriverService.php +++ b/lib/Firefox/FirefoxDriverService.php @@ -9,12 +9,12 @@ class FirefoxDriverService extends DriverService /** * @var string Name of the environment variable storing the path to the driver binary */ - const WEBDRIVER_FIREFOX_DRIVER = 'WEBDRIVER_FIREFOX_DRIVER'; + public const WEBDRIVER_FIREFOX_DRIVER = 'WEBDRIVER_FIREFOX_DRIVER'; /** * @var string Default executable used when no other is provided * @internal */ - const DEFAULT_EXECUTABLE = 'geckodriver'; + public const DEFAULT_EXECUTABLE = 'geckodriver'; /** * @return static diff --git a/lib/Firefox/FirefoxOptions.php b/lib/Firefox/FirefoxOptions.php index 0a3a16744..1c08b28f5 100644 --- a/lib/Firefox/FirefoxOptions.php +++ b/lib/Firefox/FirefoxOptions.php @@ -12,13 +12,13 @@ class FirefoxOptions implements \JsonSerializable { /** @var string The key of FirefoxOptions in desired capabilities */ - const CAPABILITY = 'moz:firefoxOptions'; + public const CAPABILITY = 'moz:firefoxOptions'; /** @var string */ - const OPTION_ARGS = 'args'; + public const OPTION_ARGS = 'args'; /** @var string */ - const OPTION_PREFS = 'prefs'; + public const OPTION_PREFS = 'prefs'; /** @var string */ - const OPTION_PROFILE = 'profile'; + public const OPTION_PROFILE = 'profile'; /** @var array */ private $options = []; diff --git a/lib/Firefox/FirefoxPreferences.php b/lib/Firefox/FirefoxPreferences.php index 159a9c806..2a33fb003 100644 --- a/lib/Firefox/FirefoxPreferences.php +++ b/lib/Firefox/FirefoxPreferences.php @@ -11,13 +11,13 @@ class FirefoxPreferences { /** @var string Port WebDriver uses to communicate with Firefox instance */ - const WEBDRIVER_FIREFOX_PORT = 'webdriver_firefox_port'; + public const WEBDRIVER_FIREFOX_PORT = 'webdriver_firefox_port'; /** @var string Should the reader view (FF 38+) be enabled? */ - const READER_PARSE_ON_LOAD_ENABLED = 'reader.parse-on-load.enabled'; + public const READER_PARSE_ON_LOAD_ENABLED = 'reader.parse-on-load.enabled'; /** @var string Browser homepage */ - const BROWSER_STARTUP_HOMEPAGE = 'browser.startup.homepage'; + public const BROWSER_STARTUP_HOMEPAGE = 'browser.startup.homepage'; /** @var string Should the Devtools JSON view be enabled? */ - const DEVTOOLS_JSONVIEW = 'devtools.jsonview.enabled'; + public const DEVTOOLS_JSONVIEW = 'devtools.jsonview.enabled'; private function __construct() { diff --git a/lib/Interactions/Internal/WebDriverSingleKeyAction.php b/lib/Interactions/Internal/WebDriverSingleKeyAction.php index 9b1a014d5..7adc2784d 100644 --- a/lib/Interactions/Internal/WebDriverSingleKeyAction.php +++ b/lib/Interactions/Internal/WebDriverSingleKeyAction.php @@ -10,7 +10,7 @@ abstract class WebDriverSingleKeyAction extends WebDriverKeysRelatedAction implements WebDriverAction { - const MODIFIER_KEYS = [ + public const MODIFIER_KEYS = [ WebDriverKeys::SHIFT, WebDriverKeys::LEFT_SHIFT, WebDriverKeys::RIGHT_SHIFT, diff --git a/lib/Net/URLChecker.php b/lib/Net/URLChecker.php index 05e9cf1f6..5fe5d4abe 100644 --- a/lib/Net/URLChecker.php +++ b/lib/Net/URLChecker.php @@ -7,8 +7,8 @@ class URLChecker { - const POLL_INTERVAL_MS = 500; - const CONNECT_TIMEOUT_MS = 500; + public const POLL_INTERVAL_MS = 500; + public const CONNECT_TIMEOUT_MS = 500; public function waitUntilAvailable($timeout_in_ms, $url) { diff --git a/lib/Remote/CustomWebDriverCommand.php b/lib/Remote/CustomWebDriverCommand.php index 157902199..94de4fd28 100644 --- a/lib/Remote/CustomWebDriverCommand.php +++ b/lib/Remote/CustomWebDriverCommand.php @@ -6,8 +6,8 @@ class CustomWebDriverCommand extends WebDriverCommand { - const METHOD_GET = 'GET'; - const METHOD_POST = 'POST'; + public const METHOD_GET = 'GET'; + public const METHOD_POST = 'POST'; /** @var string */ private $customUrl; diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index a7bde312e..4c854fac2 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -433,8 +433,6 @@ private function set($key, $value) */ private function get($key, $default = null) { - return isset($this->capabilities[$key]) - ? $this->capabilities[$key] - : $default; + return $this->capabilities[$key] ?? $default; } } diff --git a/lib/Remote/DriverCommand.php b/lib/Remote/DriverCommand.php index 3d35549bd..a3a230b7c 100644 --- a/lib/Remote/DriverCommand.php +++ b/lib/Remote/DriverCommand.php @@ -9,143 +9,143 @@ */ class DriverCommand { - const GET_ALL_SESSIONS = 'getAllSessions'; - const GET_CAPABILITIES = 'getCapabilities'; - const NEW_SESSION = 'newSession'; - const STATUS = 'status'; - const CLOSE = 'close'; - const QUIT = 'quit'; - const GET = 'get'; - const GO_BACK = 'goBack'; - const GO_FORWARD = 'goForward'; - const REFRESH = 'refresh'; - const ADD_COOKIE = 'addCookie'; - const GET_ALL_COOKIES = 'getCookies'; - const DELETE_COOKIE = 'deleteCookie'; - const DELETE_ALL_COOKIES = 'deleteAllCookies'; - const FIND_ELEMENT = 'findElement'; - const FIND_ELEMENTS = 'findElements'; - const FIND_CHILD_ELEMENT = 'findChildElement'; - const FIND_CHILD_ELEMENTS = 'findChildElements'; - const CLEAR_ELEMENT = 'clearElement'; - const CLICK_ELEMENT = 'clickElement'; - const SEND_KEYS_TO_ELEMENT = 'sendKeysToElement'; - const SEND_KEYS_TO_ACTIVE_ELEMENT = 'sendKeysToActiveElement'; - const SUBMIT_ELEMENT = 'submitElement'; - const UPLOAD_FILE = 'uploadFile'; - const GET_CURRENT_WINDOW_HANDLE = 'getCurrentWindowHandle'; - const GET_WINDOW_HANDLES = 'getWindowHandles'; - const GET_CURRENT_CONTEXT_HANDLE = 'getCurrentContextHandle'; - const GET_CONTEXT_HANDLES = 'getContextHandles'; + public const GET_ALL_SESSIONS = 'getAllSessions'; + public const GET_CAPABILITIES = 'getCapabilities'; + public const NEW_SESSION = 'newSession'; + public const STATUS = 'status'; + public const CLOSE = 'close'; + public const QUIT = 'quit'; + public const GET = 'get'; + public const GO_BACK = 'goBack'; + public const GO_FORWARD = 'goForward'; + public const REFRESH = 'refresh'; + public const ADD_COOKIE = 'addCookie'; + public const GET_ALL_COOKIES = 'getCookies'; + public const DELETE_COOKIE = 'deleteCookie'; + public const DELETE_ALL_COOKIES = 'deleteAllCookies'; + public const FIND_ELEMENT = 'findElement'; + public const FIND_ELEMENTS = 'findElements'; + public const FIND_CHILD_ELEMENT = 'findChildElement'; + public const FIND_CHILD_ELEMENTS = 'findChildElements'; + public const CLEAR_ELEMENT = 'clearElement'; + public const CLICK_ELEMENT = 'clickElement'; + public const SEND_KEYS_TO_ELEMENT = 'sendKeysToElement'; + public const SEND_KEYS_TO_ACTIVE_ELEMENT = 'sendKeysToActiveElement'; + public const SUBMIT_ELEMENT = 'submitElement'; + public const UPLOAD_FILE = 'uploadFile'; + public const GET_CURRENT_WINDOW_HANDLE = 'getCurrentWindowHandle'; + public const GET_WINDOW_HANDLES = 'getWindowHandles'; + public const GET_CURRENT_CONTEXT_HANDLE = 'getCurrentContextHandle'; + public const GET_CONTEXT_HANDLES = 'getContextHandles'; // Switching between to window/frame/iframe - const SWITCH_TO_WINDOW = 'switchToWindow'; - const SWITCH_TO_CONTEXT = 'switchToContext'; - const SWITCH_TO_FRAME = 'switchToFrame'; - const SWITCH_TO_PARENT_FRAME = 'switchToParentFrame'; - const GET_ACTIVE_ELEMENT = 'getActiveElement'; + public const SWITCH_TO_WINDOW = 'switchToWindow'; + public const SWITCH_TO_CONTEXT = 'switchToContext'; + public const SWITCH_TO_FRAME = 'switchToFrame'; + public const SWITCH_TO_PARENT_FRAME = 'switchToParentFrame'; + public const GET_ACTIVE_ELEMENT = 'getActiveElement'; // Information of the page - const GET_CURRENT_URL = 'getCurrentUrl'; - const GET_PAGE_SOURCE = 'getPageSource'; - const GET_TITLE = 'getTitle'; + public const GET_CURRENT_URL = 'getCurrentUrl'; + public const GET_PAGE_SOURCE = 'getPageSource'; + public const GET_TITLE = 'getTitle'; // Javascript API - const EXECUTE_SCRIPT = 'executeScript'; - const EXECUTE_ASYNC_SCRIPT = 'executeAsyncScript'; + public const EXECUTE_SCRIPT = 'executeScript'; + public const EXECUTE_ASYNC_SCRIPT = 'executeAsyncScript'; // API getting information from an element. - const GET_ELEMENT_TEXT = 'getElementText'; - const GET_ELEMENT_TAG_NAME = 'getElementTagName'; - const IS_ELEMENT_SELECTED = 'isElementSelected'; - const IS_ELEMENT_ENABLED = 'isElementEnabled'; - const IS_ELEMENT_DISPLAYED = 'isElementDisplayed'; - const GET_ELEMENT_LOCATION = 'getElementLocation'; - const GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = 'getElementLocationOnceScrolledIntoView'; - const GET_ELEMENT_SIZE = 'getElementSize'; - const GET_ELEMENT_ATTRIBUTE = 'getElementAttribute'; - const GET_ELEMENT_VALUE_OF_CSS_PROPERTY = 'getElementValueOfCssProperty'; - const ELEMENT_EQUALS = 'elementEquals'; - const SCREENSHOT = 'screenshot'; + public const GET_ELEMENT_TEXT = 'getElementText'; + public const GET_ELEMENT_TAG_NAME = 'getElementTagName'; + public const IS_ELEMENT_SELECTED = 'isElementSelected'; + public const IS_ELEMENT_ENABLED = 'isElementEnabled'; + public const IS_ELEMENT_DISPLAYED = 'isElementDisplayed'; + public const GET_ELEMENT_LOCATION = 'getElementLocation'; + public const GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = 'getElementLocationOnceScrolledIntoView'; + public const GET_ELEMENT_SIZE = 'getElementSize'; + public const GET_ELEMENT_ATTRIBUTE = 'getElementAttribute'; + public const GET_ELEMENT_VALUE_OF_CSS_PROPERTY = 'getElementValueOfCssProperty'; + public const ELEMENT_EQUALS = 'elementEquals'; + public const SCREENSHOT = 'screenshot'; // Alert API - const ACCEPT_ALERT = 'acceptAlert'; - const DISMISS_ALERT = 'dismissAlert'; - const GET_ALERT_TEXT = 'getAlertText'; - const SET_ALERT_VALUE = 'setAlertValue'; + public const ACCEPT_ALERT = 'acceptAlert'; + public const DISMISS_ALERT = 'dismissAlert'; + public const GET_ALERT_TEXT = 'getAlertText'; + public const SET_ALERT_VALUE = 'setAlertValue'; // Timeout API - const SET_TIMEOUT = 'setTimeout'; - const IMPLICITLY_WAIT = 'implicitlyWait'; - const SET_SCRIPT_TIMEOUT = 'setScriptTimeout'; + public const SET_TIMEOUT = 'setTimeout'; + public const IMPLICITLY_WAIT = 'implicitlyWait'; + public const SET_SCRIPT_TIMEOUT = 'setScriptTimeout'; /** @deprecated */ - const EXECUTE_SQL = 'executeSQL'; - const GET_LOCATION = 'getLocation'; - const SET_LOCATION = 'setLocation'; - const GET_APP_CACHE = 'getAppCache'; - const GET_APP_CACHE_STATUS = 'getStatus'; - const CLEAR_APP_CACHE = 'clearAppCache'; - const IS_BROWSER_ONLINE = 'isBrowserOnline'; - const SET_BROWSER_ONLINE = 'setBrowserOnline'; + public const EXECUTE_SQL = 'executeSQL'; + public const GET_LOCATION = 'getLocation'; + public const SET_LOCATION = 'setLocation'; + public const GET_APP_CACHE = 'getAppCache'; + public const GET_APP_CACHE_STATUS = 'getStatus'; + public const CLEAR_APP_CACHE = 'clearAppCache'; + public const IS_BROWSER_ONLINE = 'isBrowserOnline'; + public const SET_BROWSER_ONLINE = 'setBrowserOnline'; // Local storage - const GET_LOCAL_STORAGE_ITEM = 'getLocalStorageItem'; - const GET_LOCAL_STORAGE_KEYS = 'getLocalStorageKeys'; - const SET_LOCAL_STORAGE_ITEM = 'setLocalStorageItem'; - const REMOVE_LOCAL_STORAGE_ITEM = 'removeLocalStorageItem'; - const CLEAR_LOCAL_STORAGE = 'clearLocalStorage'; - const GET_LOCAL_STORAGE_SIZE = 'getLocalStorageSize'; + public const GET_LOCAL_STORAGE_ITEM = 'getLocalStorageItem'; + public const GET_LOCAL_STORAGE_KEYS = 'getLocalStorageKeys'; + public const SET_LOCAL_STORAGE_ITEM = 'setLocalStorageItem'; + public const REMOVE_LOCAL_STORAGE_ITEM = 'removeLocalStorageItem'; + public const CLEAR_LOCAL_STORAGE = 'clearLocalStorage'; + public const GET_LOCAL_STORAGE_SIZE = 'getLocalStorageSize'; // Session storage - const GET_SESSION_STORAGE_ITEM = 'getSessionStorageItem'; - const GET_SESSION_STORAGE_KEYS = 'getSessionStorageKey'; - const SET_SESSION_STORAGE_ITEM = 'setSessionStorageItem'; - const REMOVE_SESSION_STORAGE_ITEM = 'removeSessionStorageItem'; - const CLEAR_SESSION_STORAGE = 'clearSessionStorage'; - const GET_SESSION_STORAGE_SIZE = 'getSessionStorageSize'; + public const GET_SESSION_STORAGE_ITEM = 'getSessionStorageItem'; + public const GET_SESSION_STORAGE_KEYS = 'getSessionStorageKey'; + public const SET_SESSION_STORAGE_ITEM = 'setSessionStorageItem'; + public const REMOVE_SESSION_STORAGE_ITEM = 'removeSessionStorageItem'; + public const CLEAR_SESSION_STORAGE = 'clearSessionStorage'; + public const GET_SESSION_STORAGE_SIZE = 'getSessionStorageSize'; // Screen orientation - const SET_SCREEN_ORIENTATION = 'setScreenOrientation'; - const GET_SCREEN_ORIENTATION = 'getScreenOrientation'; + public const SET_SCREEN_ORIENTATION = 'setScreenOrientation'; + public const GET_SCREEN_ORIENTATION = 'getScreenOrientation'; // These belong to the Advanced user interactions - an element is optional for these commands. - const CLICK = 'mouseClick'; - const DOUBLE_CLICK = 'mouseDoubleClick'; - const MOUSE_DOWN = 'mouseButtonDown'; - const MOUSE_UP = 'mouseButtonUp'; - const MOVE_TO = 'mouseMoveTo'; + public const CLICK = 'mouseClick'; + public const DOUBLE_CLICK = 'mouseDoubleClick'; + public const MOUSE_DOWN = 'mouseButtonDown'; + public const MOUSE_UP = 'mouseButtonUp'; + public const MOVE_TO = 'mouseMoveTo'; // Those allow interactions with the Input Methods installed on the system. - const IME_GET_AVAILABLE_ENGINES = 'imeGetAvailableEngines'; - const IME_GET_ACTIVE_ENGINE = 'imeGetActiveEngine'; - const IME_IS_ACTIVATED = 'imeIsActivated'; - const IME_DEACTIVATE = 'imeDeactivate'; - const IME_ACTIVATE_ENGINE = 'imeActivateEngine'; + public const IME_GET_AVAILABLE_ENGINES = 'imeGetAvailableEngines'; + public const IME_GET_ACTIVE_ENGINE = 'imeGetActiveEngine'; + public const IME_IS_ACTIVATED = 'imeIsActivated'; + public const IME_DEACTIVATE = 'imeDeactivate'; + public const IME_ACTIVATE_ENGINE = 'imeActivateEngine'; // These belong to the Advanced Touch API - const TOUCH_SINGLE_TAP = 'touchSingleTap'; - const TOUCH_DOWN = 'touchDown'; - const TOUCH_UP = 'touchUp'; - const TOUCH_MOVE = 'touchMove'; - const TOUCH_SCROLL = 'touchScroll'; - const TOUCH_DOUBLE_TAP = 'touchDoubleTap'; - const TOUCH_LONG_PRESS = 'touchLongPress'; - const TOUCH_FLICK = 'touchFlick'; + public const TOUCH_SINGLE_TAP = 'touchSingleTap'; + public const TOUCH_DOWN = 'touchDown'; + public const TOUCH_UP = 'touchUp'; + public const TOUCH_MOVE = 'touchMove'; + public const TOUCH_SCROLL = 'touchScroll'; + public const TOUCH_DOUBLE_TAP = 'touchDoubleTap'; + public const TOUCH_LONG_PRESS = 'touchLongPress'; + public const TOUCH_FLICK = 'touchFlick'; // Window API (beta) - const SET_WINDOW_SIZE = 'setWindowSize'; - const SET_WINDOW_POSITION = 'setWindowPosition'; - const GET_WINDOW_SIZE = 'getWindowSize'; - const GET_WINDOW_POSITION = 'getWindowPosition'; - const MAXIMIZE_WINDOW = 'maximizeWindow'; - const FULLSCREEN_WINDOW = 'fullscreenWindow'; + public const SET_WINDOW_SIZE = 'setWindowSize'; + public const SET_WINDOW_POSITION = 'setWindowPosition'; + public const GET_WINDOW_SIZE = 'getWindowSize'; + public const GET_WINDOW_POSITION = 'getWindowPosition'; + public const MAXIMIZE_WINDOW = 'maximizeWindow'; + public const FULLSCREEN_WINDOW = 'fullscreenWindow'; // Logging API - const GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes'; - const GET_LOG = 'getLog'; - const GET_SESSION_LOGS = 'getSessionLogs'; + public const GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes'; + public const GET_LOG = 'getLog'; + public const GET_SESSION_LOGS = 'getSessionLogs'; // Mobile API - const GET_NETWORK_CONNECTION = 'getNetworkConnection'; - const SET_NETWORK_CONNECTION = 'setNetworkConnection'; + public const GET_NETWORK_CONNECTION = 'getNetworkConnection'; + public const SET_NETWORK_CONNECTION = 'setNetworkConnection'; // Custom command - const CUSTOM_COMMAND = 'customCommand'; + public const CUSTOM_COMMAND = 'customCommand'; // W3C specific - const ACTIONS = 'actions'; - const GET_ELEMENT_PROPERTY = 'getElementProperty'; - const GET_NAMED_COOKIE = 'getNamedCookie'; - const NEW_WINDOW = 'newWindow'; - const TAKE_ELEMENT_SCREENSHOT = 'takeElementScreenshot'; - const MINIMIZE_WINDOW = 'minimizeWindow'; - const GET_ELEMENT_SHADOW_ROOT = 'getElementShadowRoot'; - const FIND_ELEMENT_FROM_SHADOW_ROOT = 'findElementFromShadowRoot'; - const FIND_ELEMENTS_FROM_SHADOW_ROOT = 'findElementsFromShadowRoot'; + public const ACTIONS = 'actions'; + public const GET_ELEMENT_PROPERTY = 'getElementProperty'; + public const GET_NAMED_COOKIE = 'getNamedCookie'; + public const NEW_WINDOW = 'newWindow'; + public const TAKE_ELEMENT_SCREENSHOT = 'takeElementScreenshot'; + public const MINIMIZE_WINDOW = 'minimizeWindow'; + public const GET_ELEMENT_SHADOW_ROOT = 'getElementShadowRoot'; + public const FIND_ELEMENT_FROM_SHADOW_ROOT = 'findElementFromShadowRoot'; + public const FIND_ELEMENTS_FROM_SHADOW_ROOT = 'findElementsFromShadowRoot'; private function __construct() { diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 94e2cb5d5..6dbfd0c0e 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -13,7 +13,7 @@ */ class HttpCommandExecutor implements WebDriverCommandExecutor { - const DEFAULT_HTTP_HEADERS = [ + public const DEFAULT_HTTP_HEADERS = [ 'Content-Type: application/json;charset=UTF-8', 'Accept: application/json', ]; @@ -282,7 +282,7 @@ public function execute(WebDriverCommand $command) $url = $http_options['url']; $sessionID = $command->getSessionID(); - $url = str_replace(':sessionId', $sessionID === null ? '' : $sessionID, $url); + $url = str_replace(':sessionId', $sessionID ?? '', $url); $params = $command->getParameters(); foreach ($params as $name => $value) { if ($name[0] === ':') { @@ -385,7 +385,7 @@ public function execute(WebDriverCommand $command) WebDriverException::throwException($value['error'], $message, $results); } - $status = isset($results['status']) ? $results['status'] : 0; + $status = $results['status'] ?? 0; if ($status !== 0) { // Legacy JsonWire WebDriverException::throwException($status, $message, $results); diff --git a/lib/Remote/JsonWireCompat.php b/lib/Remote/JsonWireCompat.php index 65a6956ba..952cb1906 100644 --- a/lib/Remote/JsonWireCompat.php +++ b/lib/Remote/JsonWireCompat.php @@ -16,7 +16,7 @@ abstract class JsonWireCompat * * @see https://w3c.github.io/webdriver/webdriver-spec.html#elements */ - const WEB_DRIVER_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf'; + public const WEB_DRIVER_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf'; public static function getElement(array $rawElement) { diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index d2429967e..14ad4eaf5 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -11,11 +11,11 @@ class RemoteMouse implements WebDriverMouse { /** @internal */ - const BUTTON_LEFT = 0; + public const BUTTON_LEFT = 0; /** @internal */ - const BUTTON_MIDDLE = 1; + public const BUTTON_MIDDLE = 1; /** @internal */ - const BUTTON_RIGHT = 2; + public const BUTTON_RIGHT = 2; /** * @var RemoteExecuteMethod @@ -274,8 +274,8 @@ private function createMoveAction( $move_action = [ 'type' => 'pointerMove', 'duration' => 100, // to simulate human delay - 'x' => $x_offset === null ? 0 : $x_offset, - 'y' => $y_offset === null ? 0 : $y_offset, + 'x' => $x_offset ?? 0, + 'y' => $y_offset ?? 0, ]; if ($where !== null) { diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 07c51e33f..89b38752a 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -241,11 +241,11 @@ public function getLocationOnScreenOnceScrolledIntoView() { if ($this->isW3cCompliant) { $script = <<executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $script, @@ -462,7 +462,7 @@ public function submit() if ($this->isW3cCompliant) { // Submit method cannot be called directly in case an input of this form is named "submit". // We use this polyfill to trigger 'submit' event using form.dispatchEvent(). - $submitPolyfill = $script = <<executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $submitPolyfill, 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], diff --git a/lib/Remote/ShadowRoot.php b/lib/Remote/ShadowRoot.php index 1ec13a217..63ac2f8e4 100644 --- a/lib/Remote/ShadowRoot.php +++ b/lib/Remote/ShadowRoot.php @@ -14,7 +14,7 @@ class ShadowRoot implements WebDriverSearchContext * * @see https://w3c.github.io/webdriver/#shadow-root */ - const SHADOW_ROOT_IDENTIFIER = 'shadow-6066-11e4-a52e-4f735466cecf'; + public const SHADOW_ROOT_IDENTIFIER = 'shadow-6066-11e4-a52e-4f735466cecf'; /** * @var RemoteExecuteMethod diff --git a/lib/Remote/WebDriverBrowserType.php b/lib/Remote/WebDriverBrowserType.php index 77badd6cc..9e3f4e0ff 100644 --- a/lib/Remote/WebDriverBrowserType.php +++ b/lib/Remote/WebDriverBrowserType.php @@ -9,30 +9,30 @@ */ class WebDriverBrowserType { - const FIREFOX = 'firefox'; - const FIREFOX_PROXY = 'firefoxproxy'; - const FIREFOX_CHROME = 'firefoxchrome'; - const GOOGLECHROME = 'googlechrome'; - const SAFARI = 'safari'; - const SAFARI_PROXY = 'safariproxy'; - const OPERA = 'opera'; - const MICROSOFT_EDGE = 'MicrosoftEdge'; - const IEXPLORE = 'iexplore'; - const IEXPLORE_PROXY = 'iexploreproxy'; - const CHROME = 'chrome'; - const KONQUEROR = 'konqueror'; - const MOCK = 'mock'; - const IE_HTA = 'iehta'; - const ANDROID = 'android'; - const HTMLUNIT = 'htmlunit'; - const IE = 'internet explorer'; - const IPHONE = 'iphone'; - const IPAD = 'iPad'; + public const FIREFOX = 'firefox'; + public const FIREFOX_PROXY = 'firefoxproxy'; + public const FIREFOX_CHROME = 'firefoxchrome'; + public const GOOGLECHROME = 'googlechrome'; + public const SAFARI = 'safari'; + public const SAFARI_PROXY = 'safariproxy'; + public const OPERA = 'opera'; + public const MICROSOFT_EDGE = 'MicrosoftEdge'; + public const IEXPLORE = 'iexplore'; + public const IEXPLORE_PROXY = 'iexploreproxy'; + public const CHROME = 'chrome'; + public const KONQUEROR = 'konqueror'; + public const MOCK = 'mock'; + public const IE_HTA = 'iehta'; + public const ANDROID = 'android'; + public const HTMLUNIT = 'htmlunit'; + public const IE = 'internet explorer'; + public const IPHONE = 'iphone'; + public const IPAD = 'iPad'; /** * @deprecated PhantomJS is no longer developed and its support will be removed in next major version. * Use headless Chrome or Firefox instead. */ - const PHANTOMJS = 'phantomjs'; + public const PHANTOMJS = 'phantomjs'; private function __construct() { diff --git a/lib/Remote/WebDriverCapabilityType.php b/lib/Remote/WebDriverCapabilityType.php index 45833339a..ee2538061 100644 --- a/lib/Remote/WebDriverCapabilityType.php +++ b/lib/Remote/WebDriverCapabilityType.php @@ -9,22 +9,22 @@ */ class WebDriverCapabilityType { - const BROWSER_NAME = 'browserName'; - const VERSION = 'version'; - const PLATFORM = 'platform'; - const JAVASCRIPT_ENABLED = 'javascriptEnabled'; - const TAKES_SCREENSHOT = 'takesScreenshot'; - const HANDLES_ALERTS = 'handlesAlerts'; - const DATABASE_ENABLED = 'databaseEnabled'; - const LOCATION_CONTEXT_ENABLED = 'locationContextEnabled'; - const APPLICATION_CACHE_ENABLED = 'applicationCacheEnabled'; - const BROWSER_CONNECTION_ENABLED = 'browserConnectionEnabled'; - const CSS_SELECTORS_ENABLED = 'cssSelectorsEnabled'; - const WEB_STORAGE_ENABLED = 'webStorageEnabled'; - const ROTATABLE = 'rotatable'; - const ACCEPT_SSL_CERTS = 'acceptSslCerts'; - const NATIVE_EVENTS = 'nativeEvents'; - const PROXY = 'proxy'; + public const BROWSER_NAME = 'browserName'; + public const VERSION = 'version'; + public const PLATFORM = 'platform'; + public const JAVASCRIPT_ENABLED = 'javascriptEnabled'; + public const TAKES_SCREENSHOT = 'takesScreenshot'; + public const HANDLES_ALERTS = 'handlesAlerts'; + public const DATABASE_ENABLED = 'databaseEnabled'; + public const LOCATION_CONTEXT_ENABLED = 'locationContextEnabled'; + public const APPLICATION_CACHE_ENABLED = 'applicationCacheEnabled'; + public const BROWSER_CONNECTION_ENABLED = 'browserConnectionEnabled'; + public const CSS_SELECTORS_ENABLED = 'cssSelectorsEnabled'; + public const WEB_STORAGE_ENABLED = 'webStorageEnabled'; + public const ROTATABLE = 'rotatable'; + public const ACCEPT_SSL_CERTS = 'acceptSslCerts'; + public const NATIVE_EVENTS = 'nativeEvents'; + public const PROXY = 'proxy'; private function __construct() { diff --git a/lib/Support/IsElementDisplayedAtom.php b/lib/Support/IsElementDisplayedAtom.php index b27e9cca7..d95e4f01f 100644 --- a/lib/Support/IsElementDisplayedAtom.php +++ b/lib/Support/IsElementDisplayedAtom.php @@ -24,7 +24,7 @@ class IsElementDisplayedAtom * * @var array */ - const BROWSERS_WITH_ENDPOINT_SUPPORT = [ + public const BROWSERS_WITH_ENDPOINT_SUPPORT = [ WebDriverBrowserType::CHROME, WebDriverBrowserType::FIREFOX, WebDriverBrowserType::MICROSOFT_EDGE, diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 26914a161..5794d2580 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -375,9 +375,7 @@ public static function invisibilityOfElementLocated(WebDriverBy $by) function (WebDriver $driver) use ($by) { try { return !$driver->findElement($by)->isDisplayed(); - } catch (NoSuchElementException $e) { - return true; - } catch (StaleElementReferenceException $e) { + } catch (NoSuchElementException|StaleElementReferenceException $e) { return true; } } @@ -397,9 +395,7 @@ public static function invisibilityOfElementWithText(WebDriverBy $by, $text) function (WebDriver $driver) use ($by, $text) { try { return !($driver->findElement($by)->getText() === $text); - } catch (NoSuchElementException $e) { - return true; - } catch (StaleElementReferenceException $e) { + } catch (NoSuchElementException|StaleElementReferenceException $e) { return true; } } diff --git a/lib/WebDriverKeys.php b/lib/WebDriverKeys.php index 5bba2eff0..40f3ffbf5 100644 --- a/lib/WebDriverKeys.php +++ b/lib/WebDriverKeys.php @@ -9,85 +9,85 @@ */ class WebDriverKeys { - const NULL = "\xEE\x80\x80"; - const CANCEL = "\xEE\x80\x81"; - const HELP = "\xEE\x80\x82"; - const BACKSPACE = "\xEE\x80\x83"; - const TAB = "\xEE\x80\x84"; - const CLEAR = "\xEE\x80\x85"; - const RETURN_KEY = "\xEE\x80\x86"; - const ENTER = "\xEE\x80\x87"; - const SHIFT = "\xEE\x80\x88"; - const CONTROL = "\xEE\x80\x89"; - const ALT = "\xEE\x80\x8A"; - const PAUSE = "\xEE\x80\x8B"; - const ESCAPE = "\xEE\x80\x8C"; - const SPACE = "\xEE\x80\x8D"; - const PAGE_UP = "\xEE\x80\x8E"; - const PAGE_DOWN = "\xEE\x80\x8F"; - const END = "\xEE\x80\x90"; - const HOME = "\xEE\x80\x91"; - const ARROW_LEFT = "\xEE\x80\x92"; - const ARROW_UP = "\xEE\x80\x93"; - const ARROW_RIGHT = "\xEE\x80\x94"; - const ARROW_DOWN = "\xEE\x80\x95"; - const INSERT = "\xEE\x80\x96"; - const DELETE = "\xEE\x80\x97"; - const SEMICOLON = "\xEE\x80\x98"; - const EQUALS = "\xEE\x80\x99"; - const NUMPAD0 = "\xEE\x80\x9A"; - const NUMPAD1 = "\xEE\x80\x9B"; - const NUMPAD2 = "\xEE\x80\x9C"; - const NUMPAD3 = "\xEE\x80\x9D"; - const NUMPAD4 = "\xEE\x80\x9E"; - const NUMPAD5 = "\xEE\x80\x9F"; - const NUMPAD6 = "\xEE\x80\xA0"; - const NUMPAD7 = "\xEE\x80\xA1"; - const NUMPAD8 = "\xEE\x80\xA2"; - const NUMPAD9 = "\xEE\x80\xA3"; - const MULTIPLY = "\xEE\x80\xA4"; - const ADD = "\xEE\x80\xA5"; - const SEPARATOR = "\xEE\x80\xA6"; - const SUBTRACT = "\xEE\x80\xA7"; - const DECIMAL = "\xEE\x80\xA8"; - const DIVIDE = "\xEE\x80\xA9"; - const F1 = "\xEE\x80\xB1"; - const F2 = "\xEE\x80\xB2"; - const F3 = "\xEE\x80\xB3"; - const F4 = "\xEE\x80\xB4"; - const F5 = "\xEE\x80\xB5"; - const F6 = "\xEE\x80\xB6"; - const F7 = "\xEE\x80\xB7"; - const F8 = "\xEE\x80\xB8"; - const F9 = "\xEE\x80\xB9"; - const F10 = "\xEE\x80\xBA"; - const F11 = "\xEE\x80\xBB"; - const F12 = "\xEE\x80\xBC"; - const META = "\xEE\x80\xBD"; - const ZENKAKU_HANKAKU = "\xEE\x80\xC0"; - const RIGHT_SHIFT = "\xEE\x81\x90"; - const RIGHT_CONTROL = "\xEE\x81\x91"; - const RIGHT_ALT = "\xEE\x81\x92"; - const RIGHT_META = "\xEE\x81\x93"; - const NUMPAD_PAGE_UP = "\xEE\x81\x94"; - const NUMPAD_PAGE_DOWN = "\xEE\x81\x95"; - const NUMPAD_END = "\xEE\x81\x96"; - const NUMPAD_HOME = "\xEE\x81\x97"; - const NUMPAD_ARROW_LEFT = "\xEE\x81\x98"; - const NUMPAD_ARROW_UP = "\xEE\x81\x99"; - const NUMPAD_ARROW_RIGHT = "\xEE\x81\x9A"; - const NUMPAD_ARROW_DOWN = "\xEE\x81\x9B"; - const NUMPAD_ARROW_INSERT = "\xEE\x81\x9C"; - const NUMPAD_ARROW_DELETE = "\xEE\x81\x9D"; + public const NULL = "\xEE\x80\x80"; + public const CANCEL = "\xEE\x80\x81"; + public const HELP = "\xEE\x80\x82"; + public const BACKSPACE = "\xEE\x80\x83"; + public const TAB = "\xEE\x80\x84"; + public const CLEAR = "\xEE\x80\x85"; + public const RETURN_KEY = "\xEE\x80\x86"; + public const ENTER = "\xEE\x80\x87"; + public const SHIFT = "\xEE\x80\x88"; + public const CONTROL = "\xEE\x80\x89"; + public const ALT = "\xEE\x80\x8A"; + public const PAUSE = "\xEE\x80\x8B"; + public const ESCAPE = "\xEE\x80\x8C"; + public const SPACE = "\xEE\x80\x8D"; + public const PAGE_UP = "\xEE\x80\x8E"; + public const PAGE_DOWN = "\xEE\x80\x8F"; + public const END = "\xEE\x80\x90"; + public const HOME = "\xEE\x80\x91"; + public const ARROW_LEFT = "\xEE\x80\x92"; + public const ARROW_UP = "\xEE\x80\x93"; + public const ARROW_RIGHT = "\xEE\x80\x94"; + public const ARROW_DOWN = "\xEE\x80\x95"; + public const INSERT = "\xEE\x80\x96"; + public const DELETE = "\xEE\x80\x97"; + public const SEMICOLON = "\xEE\x80\x98"; + public const EQUALS = "\xEE\x80\x99"; + public const NUMPAD0 = "\xEE\x80\x9A"; + public const NUMPAD1 = "\xEE\x80\x9B"; + public const NUMPAD2 = "\xEE\x80\x9C"; + public const NUMPAD3 = "\xEE\x80\x9D"; + public const NUMPAD4 = "\xEE\x80\x9E"; + public const NUMPAD5 = "\xEE\x80\x9F"; + public const NUMPAD6 = "\xEE\x80\xA0"; + public const NUMPAD7 = "\xEE\x80\xA1"; + public const NUMPAD8 = "\xEE\x80\xA2"; + public const NUMPAD9 = "\xEE\x80\xA3"; + public const MULTIPLY = "\xEE\x80\xA4"; + public const ADD = "\xEE\x80\xA5"; + public const SEPARATOR = "\xEE\x80\xA6"; + public const SUBTRACT = "\xEE\x80\xA7"; + public const DECIMAL = "\xEE\x80\xA8"; + public const DIVIDE = "\xEE\x80\xA9"; + public const F1 = "\xEE\x80\xB1"; + public const F2 = "\xEE\x80\xB2"; + public const F3 = "\xEE\x80\xB3"; + public const F4 = "\xEE\x80\xB4"; + public const F5 = "\xEE\x80\xB5"; + public const F6 = "\xEE\x80\xB6"; + public const F7 = "\xEE\x80\xB7"; + public const F8 = "\xEE\x80\xB8"; + public const F9 = "\xEE\x80\xB9"; + public const F10 = "\xEE\x80\xBA"; + public const F11 = "\xEE\x80\xBB"; + public const F12 = "\xEE\x80\xBC"; + public const META = "\xEE\x80\xBD"; + public const ZENKAKU_HANKAKU = "\xEE\x80\xC0"; + public const RIGHT_SHIFT = "\xEE\x81\x90"; + public const RIGHT_CONTROL = "\xEE\x81\x91"; + public const RIGHT_ALT = "\xEE\x81\x92"; + public const RIGHT_META = "\xEE\x81\x93"; + public const NUMPAD_PAGE_UP = "\xEE\x81\x94"; + public const NUMPAD_PAGE_DOWN = "\xEE\x81\x95"; + public const NUMPAD_END = "\xEE\x81\x96"; + public const NUMPAD_HOME = "\xEE\x81\x97"; + public const NUMPAD_ARROW_LEFT = "\xEE\x81\x98"; + public const NUMPAD_ARROW_UP = "\xEE\x81\x99"; + public const NUMPAD_ARROW_RIGHT = "\xEE\x81\x9A"; + public const NUMPAD_ARROW_DOWN = "\xEE\x81\x9B"; + public const NUMPAD_ARROW_INSERT = "\xEE\x81\x9C"; + public const NUMPAD_ARROW_DELETE = "\xEE\x81\x9D"; // Aliases - const LEFT_SHIFT = self::SHIFT; - const LEFT_CONTROL = self::CONTROL; - const LEFT_ALT = self::ALT; - const LEFT = self::ARROW_LEFT; - const UP = self::ARROW_UP; - const RIGHT = self::ARROW_RIGHT; - const DOWN = self::ARROW_DOWN; - const COMMAND = self::META; + public const LEFT_SHIFT = self::SHIFT; + public const LEFT_CONTROL = self::CONTROL; + public const LEFT_ALT = self::ALT; + public const LEFT = self::ARROW_LEFT; + public const UP = self::ARROW_UP; + public const RIGHT = self::ARROW_RIGHT; + public const DOWN = self::ARROW_DOWN; + public const COMMAND = self::META; /** * Encode input of `sendKeys()` to appropriate format according to protocol. diff --git a/lib/WebDriverPlatform.php b/lib/WebDriverPlatform.php index d7194e05e..a589f3013 100644 --- a/lib/WebDriverPlatform.php +++ b/lib/WebDriverPlatform.php @@ -9,15 +9,15 @@ */ class WebDriverPlatform { - const ANDROID = 'ANDROID'; + public const ANDROID = 'ANDROID'; /** @deprecated ANY has no meaning in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/731 */ - const ANY = 'ANY'; - const LINUX = 'LINUX'; - const MAC = 'MAC'; - const UNIX = 'UNIX'; - const VISTA = 'VISTA'; - const WINDOWS = 'WINDOWS'; - const XP = 'XP'; + public const ANY = 'ANY'; + public const LINUX = 'LINUX'; + public const MAC = 'MAC'; + public const UNIX = 'UNIX'; + public const VISTA = 'VISTA'; + public const WINDOWS = 'WINDOWS'; + public const XP = 'XP'; private function __construct() { diff --git a/lib/WebDriverTargetLocator.php b/lib/WebDriverTargetLocator.php index 08096bcab..8787f66c5 100644 --- a/lib/WebDriverTargetLocator.php +++ b/lib/WebDriverTargetLocator.php @@ -8,9 +8,9 @@ interface WebDriverTargetLocator { /** @var string */ - const WINDOW_TYPE_WINDOW = 'window'; + public const WINDOW_TYPE_WINDOW = 'window'; /** @var string */ - const WINDOW_TYPE_TAB = 'tab'; + public const WINDOW_TYPE_TAB = 'tab'; /** * Set the current browsing context to the current top-level browsing context. diff --git a/lib/WebDriverWait.php b/lib/WebDriverWait.php index 5b2009b3c..d2176b92a 100644 --- a/lib/WebDriverWait.php +++ b/lib/WebDriverWait.php @@ -28,7 +28,7 @@ class WebDriverWait public function __construct(WebDriver $driver, $timeout_in_second = null, $interval_in_millisecond = null) { $this->driver = $driver; - $this->timeout = isset($timeout_in_second) ? $timeout_in_second : 30; + $this->timeout = $timeout_in_second ?? 30; $this->interval = $interval_in_millisecond ?: 250; } diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php index 7c3bed873..ac71c6035 100644 --- a/tests/functional/ReportSauceLabsStatusListener.php +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -103,7 +103,7 @@ private function submitToSauceLabs($url, array $data) curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($curl, CURLOPT_USERPWD, getenv('SAUCE_USERNAME') . ':' . getenv('SAUCE_ACCESS_KEY')); - curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data, JSON_THROW_ON_ERROR)); // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy curl_setopt($curl, CURLOPT_HTTPHEADER, ['Expect:']); diff --git a/tests/functional/TestPage.php b/tests/functional/TestPage.php index 08cb86bec..bf039c2e2 100644 --- a/tests/functional/TestPage.php +++ b/tests/functional/TestPage.php @@ -4,20 +4,20 @@ class TestPage { - const ALERT = 'alert.html'; - const DELAYED_ELEMENT = 'delayed_element.html'; - const ESCAPE_CSS = 'escape_css.html'; - const EVENTS = 'events.html'; - const FORM = 'form.html'; - const FORM_CHECKBOX_RADIO = 'form_checkbox_radio.html'; - const GECKO_653 = 'gecko653.html'; - const INDEX = 'index.html'; - const OPEN_NEW_WINDOW = 'open_new_window.html'; - const PAGE_WITH_FRAME = 'page_with_frame.html'; - const SLOW_LOADING = 'slow_loading.html'; - const SORTABLE = 'sortable.html'; - const UPLOAD = 'upload.html'; - const WEB_COMPONENTS = 'web_components.html'; + public const ALERT = 'alert.html'; + public const DELAYED_ELEMENT = 'delayed_element.html'; + public const ESCAPE_CSS = 'escape_css.html'; + public const EVENTS = 'events.html'; + public const FORM = 'form.html'; + public const FORM_CHECKBOX_RADIO = 'form_checkbox_radio.html'; + public const GECKO_653 = 'gecko653.html'; + public const INDEX = 'index.html'; + public const OPEN_NEW_WINDOW = 'open_new_window.html'; + public const PAGE_WITH_FRAME = 'page_with_frame.html'; + public const SLOW_LOADING = 'slow_loading.html'; + public const SORTABLE = 'sortable.html'; + public const UPLOAD = 'upload.html'; + public const WEB_COMPONENTS = 'web_components.html'; private function __construct() { diff --git a/tests/unit/Firefox/FirefoxOptionsTest.php b/tests/unit/Firefox/FirefoxOptionsTest.php index 332f55de9..2e937c6e4 100644 --- a/tests/unit/Firefox/FirefoxOptionsTest.php +++ b/tests/unit/Firefox/FirefoxOptionsTest.php @@ -7,7 +7,7 @@ class FirefoxOptionsTest extends TestCase { /** @var array */ - const EXPECTED_DEFAULT_PREFS = [ + public const EXPECTED_DEFAULT_PREFS = [ FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED => false, FirefoxPreferences::DEVTOOLS_JSONVIEW => false, ]; diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index 0d96831aa..17d7c3e55 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -183,8 +183,8 @@ public function testShouldConvertCapabilitiesToW3cCompatible( array $expectedW3cCapabilities ) { $this->assertJsonStringEqualsJsonString( - json_encode($expectedW3cCapabilities), - json_encode($inputJsonWireCapabilities->toW3cCompatibleArray()) + json_encode($expectedW3cCapabilities, JSON_THROW_ON_ERROR), + json_encode($inputJsonWireCapabilities->toW3cCompatibleArray(), JSON_THROW_ON_ERROR) ); } diff --git a/tests/unit/Support/ScreenshotHelperTest.php b/tests/unit/Support/ScreenshotHelperTest.php index edc3f661f..d73df3613 100644 --- a/tests/unit/Support/ScreenshotHelperTest.php +++ b/tests/unit/Support/ScreenshotHelperTest.php @@ -9,7 +9,7 @@ class ScreenshotHelperTest extends TestCase { - const BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='; + public const BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='; public function testShouldSavePageScreenshotToSubdirectoryIfNotExists() { From a332fd1cee0275115519a4160a7c29f8b5d185e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Oct 2022 16:42:01 +0200 Subject: [PATCH 063/130] Use PHPUnit 9 features and new configuration format --- .php-cs-fixer.dist.php | 3 +- composer.json | 2 +- phpunit.xml.dist | 24 ++++------- .../functional/RemoteWebDriverCreateTest.php | 2 +- .../RemoteWebDriverFindElementTest.php | 4 +- tests/functional/RemoteWebDriverTest.php | 10 ++--- tests/functional/RemoteWebElementTest.php | 4 +- tests/unit/Remote/HttpCommandExecutorTest.php | 41 ++++++++++--------- tests/unit/Support/ScreenshotHelperTest.php | 7 ++-- 9 files changed, 47 insertions(+), 50 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index b677785f6..2454b085f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -76,7 +76,8 @@ 'ordered_class_elements' => true, 'ordered_imports' => true, 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => false, + 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert_internal_type' => true, 'php_unit_expectation' => ['target' => '8.4'], 'php_unit_method_casing' => ['case' => 'camel_case'], 'php_unit_mock_short_will_return' => true, diff --git a/composer.json b/composer.json index 23ce0f693..a50de49d6 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "php-coveralls/php-coveralls": "^2.4", "php-mock/php-mock-phpunit": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^9", + "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", "symfony/var-dumper": "^5.0 || ^6.0" }, diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2e81aef6d..c58c72eaa 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,9 @@ - - - - + tests/unit @@ -18,14 +13,13 @@ - - + + ./lib - - + + - diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 3de4b2c93..37426cad3 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -30,7 +30,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); $this->assertNotEmpty($this->driver->getCommandExecutor()->getAddressOfRemoteServer()); - $this->assertTrue(is_string($this->driver->getSessionID())); + $this->assertIsString($this->driver->getSessionID()); $this->assertNotEmpty($this->driver->getSessionID()); $returnedCapabilities = $this->driver->getCapabilities(); diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 5887b4fba..80adf1be1 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -34,7 +34,7 @@ public function testShouldReturnEmptyArrayIfElementsCannotBeFound() $elements = $this->driver->findElements(WebDriverBy::cssSelector('not_existing')); - $this->assertTrue(is_array($elements)); + $this->assertIsArray($elements); $this->assertCount(0, $elements); } @@ -44,7 +44,7 @@ public function testShouldFindMultipleElements() $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); - $this->assertTrue(is_array($elements)); + $this->assertIsArray($elements); $this->assertCount(5, $elements); $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 81dbbd953..262423d2b 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -62,7 +62,7 @@ public function testShouldGetSessionId() $sessionId = $this->driver->getSessionID(); - $this->assertTrue(is_string($sessionId)); + $this->assertIsString($sessionId); $this->assertNotEmpty($sessionId); } @@ -76,7 +76,7 @@ public function testShouldGetAllSessions() $sessions = RemoteWebDriver::getAllSessions($this->serverUrl, 30000); - $this->assertTrue(is_array($sessions)); + $this->assertIsArray($sessions); $this->assertCount(1, $sessions); $this->assertArrayHasKey('capabilities', $sessions[0]); @@ -123,7 +123,7 @@ public function testShouldGetWindowHandles() $windowHandle = $this->driver->getWindowHandle(); $windowHandles = $this->driver->getWindowHandles(); - $this->assertTrue(is_string($windowHandle)); + $this->assertIsString($windowHandle); $this->assertNotEmpty($windowHandle); $this->assertSame([$windowHandle], $windowHandles); @@ -318,8 +318,8 @@ public function testShouldGetRemoteEndStatus() { $status = $this->driver->getStatus(); - $this->assertTrue(is_bool($status->isReady())); - $this->assertTrue(is_array($status->getMeta())); + $this->assertIsBool($status->isReady()); + $this->assertIsArray($status->getMeta()); if (getenv('BROWSER_NAME') !== 'safari') { $this->assertNotEmpty($status->getMessage()); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index b8e9f1cf8..acd8b4a91 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -453,7 +453,7 @@ public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() $childElements = $element->findElements(WebDriverBy::cssSelector('not_existing')); - $this->assertTrue(is_array($childElements)); + $this->assertIsArray($childElements); $this->assertCount(0, $childElements); } @@ -465,7 +465,7 @@ public function testShouldFindMultipleChildElements() $allElements = $this->driver->findElements(WebDriverBy::cssSelector('li')); $childElements = $element->findElements(WebDriverBy::cssSelector('li')); - $this->assertTrue(is_array($childElements)); + $this->assertIsArray($childElements); $this->assertCount(5, $allElements); // there should be 5
  • elements on page $this->assertCount(3, $childElements); // but we should find only subelements of one
      $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $childElements); diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 30e117536..70ebe8013 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -29,31 +29,32 @@ public function testShouldSendRequestToAssembledUrl( $shouldResetExpectHeader, $expectedUrl, $expectedPostData - ) { - $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); - $curlSetoptMock->expects($this->at(0)) - ->with($this->anything(), CURLOPT_URL, $expectedUrl); + ): void { + $expectedCurlSetOptCalls = [ + [$this->anything(), CURLOPT_URL, $expectedUrl], + [$this->anything()], + ]; if ($shouldResetExpectHeader) { - $curlSetoptMock->expects($this->at(2)) - ->with( - $this->anything(), - CURLOPT_HTTPHEADER, - ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json', 'Expect:'] - ); - $curlSetoptMock->expects($this->at(3)) - ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + $expectedCurlSetOptCalls[] = [ + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json', 'Expect:'], + ]; } else { - $curlSetoptMock->expects($this->at(2)) - ->with( - $this->anything(), - CURLOPT_HTTPHEADER, - ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json'] - ); - $curlSetoptMock->expects($this->at(3)) - ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + $expectedCurlSetOptCalls[] = [ + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json'], + ]; } + $expectedCurlSetOptCalls[] = [$this->anything(), CURLOPT_POSTFIELDS, $expectedPostData]; + + $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); + $curlSetoptMock->expects($this->exactly(4)) + ->withConsecutive(...$expectedCurlSetOptCalls); + $curlExecMock = $this->getFunctionMock(__NAMESPACE__, 'curl_exec'); $curlExecMock->expects($this->once()) ->willReturn('{}'); diff --git a/tests/unit/Support/ScreenshotHelperTest.php b/tests/unit/Support/ScreenshotHelperTest.php index d73df3613..6d77b7cad 100644 --- a/tests/unit/Support/ScreenshotHelperTest.php +++ b/tests/unit/Support/ScreenshotHelperTest.php @@ -9,13 +9,14 @@ class ScreenshotHelperTest extends TestCase { - public const BLACK_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='; + public const BLACK_PIXEL = + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='; public function testShouldSavePageScreenshotToSubdirectoryIfNotExists() { $fullFilePath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-', true) . '/screenshot.png'; $directoryPath = dirname($fullFilePath); - $this->assertDirectoryNotExists($directoryPath); + $this->assertDirectoryDoesNotExist($directoryPath); $executorMock = $this->createMock(RemoteExecuteMethod::class); $executorMock->expects($this->once()) @@ -53,7 +54,7 @@ public function testShouldSaveElementScreenshotToSubdirectoryIfNotExists() { $fullFilePath = sys_get_temp_dir() . '/' . uniqid('php-webdriver-', true) . '/screenshot.png'; $directoryPath = dirname($fullFilePath); - $this->assertDirectoryNotExists($directoryPath); + $this->assertDirectoryDoesNotExist($directoryPath); $elementId = 'foo-id'; $executorMock = $this->createMock(RemoteExecuteMethod::class); From f14a0956afb755ddf64778bdde15f5b0d09a7e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 6 Oct 2022 15:10:37 +0200 Subject: [PATCH 064/130] Make code analyze tools natively integrated, remove preinstall composer script --- .github/CONTRIBUTING.md | 2 -- .github/workflows/tests.yaml | 2 +- composer.json | 14 ++++++-------- tools/phpstan/composer.json | 5 +++++ 4 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 tools/phpstan/composer.json diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c10d85094..535d9ad55 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,8 +27,6 @@ Here is the [official documentation](https://www.selenium.dev/documentation/en/) To make sure your code comply with [PSR-2](http://www.php-fig.org/psr/psr-2/) coding style, tests passes and to execute other automated checks, run locally: ```sh -composer preinstall # Run this only on the first run - this will install some additional dependencies - composer all ``` diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ea9ccc99b..f302d168f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,7 +23,7 @@ jobs: extensions: mbstring, intl, zip - name: Install PHP dependencies - run: composer preinstall + run: composer update --no-interaction - name: Lint run: composer lint diff --git a/composer.json b/composer.json index a50de49d6..873db09d6 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "symfony/process": "^5.0 || ^6.0" }, "require-dev": { + "ergebnis/composer-normalize": "^2.20.0", "ondram/ci-detector": "^4.0", "php-coveralls/php-coveralls": "^2.4", "php-mock/php-mock-phpunit": "^2.0", @@ -62,10 +63,12 @@ }, "scripts": { "post-install-cmd": [ - "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction" + "composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction", + "composer install --working-dir=tools/phpstan --no-progress --no-interaction" ], "post-update-cmd": [ - "php -r 'if (PHP_VERSION_ID > 70103) { exit(1); }' || composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction" + "composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction", + "composer update --working-dir=tools/phpstan --no-progress --no-interaction" ], "all": [ "@lint", @@ -73,7 +76,7 @@ "@test" ], "analyze": [ - "vendor/bin/phpstan analyze -c phpstan.neon --ansi", + "tools/phpstan/vendor/bin/phpstan analyze -c phpstan.neon --ansi", "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", "vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], @@ -87,11 +90,6 @@ "@composer validate", "@composer normalize --dry-run" ], - "preinstall": [ - "@composer update --no-progress --no-interaction", - "@composer require --dev phpstan/phpstan", - "@composer require --dev ergebnis/composer-normalize" - ], "test": [ "vendor/bin/phpunit --colors=always" ] diff --git a/tools/phpstan/composer.json b/tools/phpstan/composer.json new file mode 100644 index 000000000..0ac8efe45 --- /dev/null +++ b/tools/phpstan/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpstan/phpstan": "^1.8" + } +} From d9e38af7411c14b5125b265e5a29c062c5d866c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 11 Oct 2022 13:58:10 +0200 Subject: [PATCH 065/130] Encapsulate php and composer calls in composer.json for better portability --- composer.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 873db09d6..d4b5b8a91 100644 --- a/composer.json +++ b/composer.json @@ -63,12 +63,12 @@ }, "scripts": { "post-install-cmd": [ - "composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction", - "composer install --working-dir=tools/phpstan --no-progress --no-interaction" + "@composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction", + "@composer install --working-dir=tools/phpstan --no-progress --no-interaction" ], "post-update-cmd": [ - "composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction", - "composer update --working-dir=tools/phpstan --no-progress --no-interaction" + "@composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction", + "@composer update --working-dir=tools/phpstan --no-progress --no-interaction" ], "all": [ "@lint", @@ -76,22 +76,22 @@ "@test" ], "analyze": [ - "tools/phpstan/vendor/bin/phpstan analyze -c phpstan.neon --ansi", - "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", - "vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/" + "@php tools/phpstan/vendor/bin/phpstan analyze -c phpstan.neon --ansi", + "@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", + "@php vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "fix": [ "@composer normalize", - "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0", - "vendor/bin/phpcbf --standard=PSR2 --ignore=*.js ./lib/ ./tests/" + "@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0", + "@php vendor/bin/phpcbf --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "lint": [ - "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", + "@php vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", "@composer validate", "@composer normalize --dry-run" ], "test": [ - "vendor/bin/phpunit --colors=always" + "@php vendor/bin/phpunit --colors=always" ] } } From 160c23a5bb02f309d061aecdfadcf67e01cbff44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 12 Oct 2022 14:20:29 +0200 Subject: [PATCH 066/130] Update sauce connect action --- .github/workflows/sauce-labs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index c966cbacc..41ebc4d1c 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -46,7 +46,7 @@ jobs: php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - name: Start Sauce Connect - uses: saucelabs/sauce-connect-action@v1 + uses: saucelabs/sauce-connect-action@v2 with: username: ${{ secrets.SAUCE_USERNAME }} accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} From 494fb99648dcb8fc7abf512c673eb746145c4e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 11 Oct 2022 16:41:10 +0200 Subject: [PATCH 067/130] Add common interface for all exceptions thrown from php-webdriver --- CHANGELOG.md | 2 ++ lib/Exception/PhpWebDriverExceptionInterface.php | 10 ++++++++++ lib/Exception/WebDriverException.php | 5 ++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 lib/Exception/PhpWebDriverExceptionInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 828c90f90..44bf6c987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- `PhpWebDriverExceptionInterface` as a common interface to identify all exceptions thrown in php-webdriver ### Changed - Require PHP ^7.3. diff --git a/lib/Exception/PhpWebDriverExceptionInterface.php b/lib/Exception/PhpWebDriverExceptionInterface.php new file mode 100644 index 000000000..04408a63b --- /dev/null +++ b/lib/Exception/PhpWebDriverExceptionInterface.php @@ -0,0 +1,10 @@ + Date: Tue, 11 Oct 2022 17:55:34 +0200 Subject: [PATCH 068/130] Refactor internal exceptions --- .php-cs-fixer.dist.php | 1 - CHANGELOG.md | 3 ++ lib/AbstractWebDriverCheckboxOrRadio.php | 4 +- lib/Cookie.php | 14 +++---- lib/Exception/DriverServerDiedException.php | 15 -------- .../Internal/DriverServerDiedException.php | 16 ++++++++ lib/Exception/Internal/IOException.php | 16 ++++++++ lib/Exception/Internal/LogicException.php | 29 ++++++++++++++ lib/Exception/Internal/RuntimeException.php | 32 ++++++++++++++++ .../Internal/UnexpectedResponseException.php | 29 ++++++++++++++ .../Internal/WebDriverCurlException.php | 22 +++++++++++ lib/Exception/WebDriverCurlException.php | 7 ---- lib/Firefox/FirefoxOptions.php | 7 ++-- lib/Firefox/FirefoxProfile.php | 36 +++++++++++------- .../Internal/WebDriverSingleKeyAction.php | 3 +- lib/Local/LocalWebDriver.php | 11 +++--- lib/Remote/CustomWebDriverCommand.php | 9 +++-- lib/Remote/DesiredCapabilities.php | 6 +-- lib/Remote/HttpCommandExecutor.php | 38 ++++--------------- lib/Remote/RemoteTargetLocator.php | 10 ++--- lib/Remote/RemoteWebDriver.php | 6 +-- lib/Remote/RemoteWebElement.php | 12 +++--- lib/Remote/Service/DriverCommandExecutor.php | 2 +- lib/Remote/Service/DriverService.php | 23 ++++------- lib/Support/ScreenshotHelper.php | 10 +++-- lib/WebDriverCheckboxes.php | 4 +- lib/WebDriverExpectedCondition.php | 3 +- lib/WebDriverOptions.php | 4 +- lib/WebDriverRadios.php | 4 +- lib/WebDriverWindow.php | 5 +-- tests/functional/RemoteTargetLocatorTest.php | 3 +- tests/unit/CookieTest.php | 6 +-- .../DriverServerDiedExceptionTest.php | 4 +- .../Exception/Internal/IOExceptionTest.php | 15 ++++++++ .../Exception/Internal/LogicExceptionTest.php | 26 +++++++++++++ .../Internal/RuntimeExceptionTest.php | 34 +++++++++++++++++ .../UnexpectedResponseExceptionTest.php | 30 +++++++++++++++ .../Internal/WebDriverCurlExceptionTest.php | 34 +++++++++++++++++ tests/unit/Firefox/FirefoxOptionsTest.php | 5 ++- .../Internal/WebDriverSingleKeyActionTest.php | 3 +- .../Remote/CustomWebDriverCommandTest.php | 6 +-- tests/unit/Remote/RemoteWebDriverTest.php | 7 +++- tests/unit/Support/ScreenshotHelperTest.php | 4 +- tests/unit/WebDriverOptionsTest.php | 3 +- 44 files changed, 409 insertions(+), 152 deletions(-) delete mode 100644 lib/Exception/DriverServerDiedException.php create mode 100644 lib/Exception/Internal/DriverServerDiedException.php create mode 100644 lib/Exception/Internal/IOException.php create mode 100644 lib/Exception/Internal/LogicException.php create mode 100644 lib/Exception/Internal/RuntimeException.php create mode 100644 lib/Exception/Internal/UnexpectedResponseException.php create mode 100644 lib/Exception/Internal/WebDriverCurlException.php delete mode 100644 lib/Exception/WebDriverCurlException.php rename tests/unit/Exception/{ => Internal}/DriverServerDiedExceptionTest.php (70%) create mode 100644 tests/unit/Exception/Internal/IOExceptionTest.php create mode 100644 tests/unit/Exception/Internal/LogicExceptionTest.php create mode 100644 tests/unit/Exception/Internal/RuntimeExceptionTest.php create mode 100644 tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php create mode 100644 tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 2454b085f..23496f768 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -26,7 +26,6 @@ 'implode_call' => true, 'is_null' => true, 'lambda_not_used_import' => true, - 'linebreak_after_opening_tag' => true, 'list_syntax' => true, 'lowercase_cast' => true, 'lowercase_static_reference' => true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 44bf6c987..e466ab9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Require PHP ^7.3. +- Throw `UnexpectedResponseException` instead of `UnknownErrorException` in `findElement()` and `findElements()` methods. +- Throw custom php-webdriver exceptions instead of native PHP SPL exceptions. +- Do not mix internal non-W3C WebDriver exceptions, separate them into own namespace. ## 1.13.0 - 2022-10-03 ### Added diff --git a/lib/AbstractWebDriverCheckboxOrRadio.php b/lib/AbstractWebDriverCheckboxOrRadio.php index 450bc387e..00aee242a 100644 --- a/lib/AbstractWebDriverCheckboxOrRadio.php +++ b/lib/AbstractWebDriverCheckboxOrRadio.php @@ -2,9 +2,9 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\InvalidElementStateException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnexpectedTagNameException; -use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Support\XPathEscaper; /** @@ -30,7 +30,7 @@ public function __construct(WebDriverElement $element) $this->name = $element->getAttribute('name'); if ($this->name === null) { - throw new WebDriverException('The input does not have a "name" attribute.'); + throw new InvalidElementStateException('The input does not have a "name" attribute.'); } $this->element = $element; diff --git a/lib/Cookie.php b/lib/Cookie.php index 77b04687c..85a93df79 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver; -use InvalidArgumentException; +use Facebook\WebDriver\Exception\Internal\LogicException; /** * Set values of an cookie. @@ -36,10 +36,10 @@ public function __construct($name, $value) public static function createFromArray(array $cookieArray) { if (!isset($cookieArray['name'])) { - throw new InvalidArgumentException('Cookie name should be set'); + throw LogicException::forError('Cookie name should be set'); } if (!isset($cookieArray['value'])) { - throw new InvalidArgumentException('Cookie value should be set'); + throw LogicException::forError('Cookie value should be set'); } $cookie = new self($cookieArray['name'], $cookieArray['value']); @@ -107,7 +107,7 @@ public function getPath() public function setDomain($domain) { if (mb_strpos($domain, ':') !== false) { - throw new InvalidArgumentException(sprintf('Cookie domain "%s" should not contain a port', $domain)); + throw LogicException::forError(sprintf('Cookie domain "%s" should not contain a port', $domain)); } $this->offsetSet('domain', $domain); @@ -258,11 +258,11 @@ public function offsetUnset($offset) protected function validateCookieName($name) { if ($name === null || $name === '') { - throw new InvalidArgumentException('Cookie name should be non-empty'); + throw LogicException::forError('Cookie name should be non-empty'); } if (mb_strpos($name, ';') !== false) { - throw new InvalidArgumentException('Cookie name should not contain a ";"'); + throw LogicException::forError('Cookie name should not contain a ";"'); } } @@ -272,7 +272,7 @@ protected function validateCookieName($name) protected function validateCookieValue($value) { if ($value === null) { - throw new InvalidArgumentException('Cookie value is required when setting a cookie'); + throw LogicException::forError('Cookie value is required when setting a cookie'); } } } diff --git a/lib/Exception/DriverServerDiedException.php b/lib/Exception/DriverServerDiedException.php deleted file mode 100644 index c3eff3b9e..000000000 --- a/lib/Exception/DriverServerDiedException.php +++ /dev/null @@ -1,15 +0,0 @@ -getMessage(), $this->getCode(), $previous); - } -} diff --git a/lib/Exception/Internal/DriverServerDiedException.php b/lib/Exception/Internal/DriverServerDiedException.php new file mode 100644 index 000000000..43c848551 --- /dev/null +++ b/lib/Exception/Internal/DriverServerDiedException.php @@ -0,0 +1,16 @@ +getCommandLine(), + $process->getErrorOutput() + ) + ); + } +} diff --git a/lib/Exception/Internal/UnexpectedResponseException.php b/lib/Exception/Internal/UnexpectedResponseException.php new file mode 100644 index 000000000..dddde41bf --- /dev/null +++ b/lib/Exception/Internal/UnexpectedResponseException.php @@ -0,0 +1,29 @@ +options[$name] = $value; diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index 6e06db6be..95e8004a8 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -2,7 +2,9 @@ namespace Facebook\WebDriver\Firefox; -use Facebook\WebDriver\Exception\WebDriverException; +use Facebook\WebDriver\Exception\Internal\IOException; +use Facebook\WebDriver\Exception\Internal\LogicException; +use Facebook\WebDriver\Exception\Internal\RuntimeException; use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -71,7 +73,7 @@ public function setRdfFile($rdf_file) /** * @param string $key * @param string|bool|int $value - * @throws WebDriverException + * @throws LogicException * @return FirefoxProfile */ public function setPreference($key, $value) @@ -85,7 +87,7 @@ public function setPreference($key, $value) if (is_bool($value)) { $value = $value ? 'true' : 'false'; } else { - throw new WebDriverException( + throw LogicException::forError( 'The value of the preference should be either a string, int or bool.' ); } @@ -185,8 +187,7 @@ public function encode() /** * @param string $extension The path to the extension. * @param string $profileDir The path to the profile directory. - * @throws \Exception - * @throws WebDriverException + * @throws IOException */ private function installExtension($extension, $profileDir) { @@ -195,18 +196,24 @@ private function installExtension($extension, $profileDir) // install extension to profile directory $extensionDir = $profileDir . '/extensions/'; if (!is_dir($extensionDir) && !mkdir($extensionDir, 0777, true) && !is_dir($extensionDir)) { - throw new WebDriverException('Cannot install Firefox extension - cannot create directory'); + throw IOException::forFileError( + 'Cannot install Firefox extension - cannot create directory', + $extensionDir + ); } if (!copy($extension, $extensionDir . $extensionCommonName . '.xpi')) { - throw new WebDriverException('Cannot install Firefox extension - cannot copy file'); + throw IOException::forFileError( + 'Cannot install Firefox extension - cannot copy file', + $extension + ); } } /** * @param string $prefix Prefix of the temp directory. * - * @throws WebDriverException + * @throws IOException * @return string The path to the temp directory created. */ private function createTempDirectory($prefix = '') @@ -216,7 +223,10 @@ private function createTempDirectory($prefix = '') unlink($temp_dir); mkdir($temp_dir); if (!is_dir($temp_dir)) { - throw new WebDriverException('Cannot create firefox profile.'); + throw IOException::forFileError( + 'Cannot install Firefox extension - cannot create directory', + $temp_dir + ); } } @@ -246,7 +256,7 @@ private function deleteDirectory($directory) * @param string $xpi The path to the .xpi extension. * @param string $target_dir The path to the unzip directory. * - * @throws \Exception + * @throws IOException * @return FirefoxProfile */ private function extractTo($xpi, $target_dir) @@ -257,10 +267,10 @@ private function extractTo($xpi, $target_dir) $zip->extractTo($target_dir); $zip->close(); } else { - throw new \Exception("Failed to open the firefox extension. '$xpi'"); + throw IOException::forFileError('Failed to open the firefox extension.', $xpi); } } else { - throw new \Exception("Firefox extension doesn't exist. '$xpi'"); + throw IOException::forFileError('Firefox extension doesn\'t exist.', $xpi); } return $this; @@ -287,7 +297,7 @@ private function parseExtensionName($extensionPath) strpos($mozillaRsaHex, $objectIdentifierHexMarker, $firstMarkerPosInHex + 2); // phpcs:ignore if ($secondMarkerPosInHexString === false) { - throw new WebDriverException('Cannot install extension. Cannot fetch extension commonName'); + throw RuntimeException::forError('Cannot install extension. Cannot fetch extension commonName'); } // phpcs:ignore diff --git a/lib/Interactions/Internal/WebDriverSingleKeyAction.php b/lib/Interactions/Internal/WebDriverSingleKeyAction.php index 7adc2784d..0cc4d193a 100644 --- a/lib/Interactions/Internal/WebDriverSingleKeyAction.php +++ b/lib/Interactions/Internal/WebDriverSingleKeyAction.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Interactions\Internal; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverKeyboard; @@ -41,7 +42,7 @@ public function __construct( parent::__construct($keyboard, $mouse, $location_provider); if (!in_array($key, self::MODIFIER_KEYS, true)) { - throw new \InvalidArgumentException( + throw LogicException::forError( sprintf( 'keyDown / keyUp actions can only be used for modifier keys, but "%s" was given', $key diff --git a/lib/Local/LocalWebDriver.php b/lib/Local/LocalWebDriver.php index d695ff016..1c396c5c7 100644 --- a/lib/Local/LocalWebDriver.php +++ b/lib/Local/LocalWebDriver.php @@ -2,11 +2,12 @@ namespace Facebook\WebDriver\Local; -use Facebook\WebDriver\Exception\WebDriverException; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; /** + * @codeCoverageIgnore * @todo Break inheritance from RemoteWebDriver in next major version. (Composition over inheritance!) */ abstract class LocalWebDriver extends RemoteWebDriver @@ -19,7 +20,7 @@ abstract class LocalWebDriver extends RemoteWebDriver * @param null $http_proxy * @param null $http_proxy_port * @param DesiredCapabilities|null $required_capabilities - * @throws WebDriverException + * @throws LogicException * @return RemoteWebDriver * @todo Remove in next major version (should not be inherited) */ @@ -32,7 +33,7 @@ public static function create( $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { - throw new WebDriverException('Use start() method to start local WebDriver.'); + throw LogicException::forError('Use start() method to start local WebDriver.'); } /** @@ -40,7 +41,7 @@ public static function create( * @param string $selenium_server_url * @param null $connection_timeout_in_ms * @param null $request_timeout_in_ms - * @throws WebDriverException + * @throws LogicException * @return RemoteWebDriver * @todo Remove in next major version (should not be inherited) */ @@ -50,6 +51,6 @@ public static function createBySessionID( $connection_timeout_in_ms = null, $request_timeout_in_ms = null ) { - throw new WebDriverException('Use start() method to start local WebDriver.'); + throw LogicException::forError('Use start() method to start local WebDriver.'); } } diff --git a/lib/Remote/CustomWebDriverCommand.php b/lib/Remote/CustomWebDriverCommand.php index 94de4fd28..54e2afdee 100644 --- a/lib/Remote/CustomWebDriverCommand.php +++ b/lib/Remote/CustomWebDriverCommand.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\WebDriverException; class CustomWebDriverCommand extends WebDriverCommand @@ -34,7 +35,7 @@ public function __construct($session_id, $url, $method, array $parameters) public function getCustomUrl() { if ($this->customUrl === null) { - throw new WebDriverException('URL of custom command is not set'); + throw LogicException::forError('URL of custom command is not set'); } return $this->customUrl; @@ -47,7 +48,7 @@ public function getCustomUrl() public function getCustomMethod() { if ($this->customMethod === null) { - throw new WebDriverException('Method of custom command is not set'); + throw LogicException::forError('Method of custom command is not set'); } return $this->customMethod; @@ -62,7 +63,7 @@ protected function setCustomRequestParameters($custom_url, $custom_method) { $allowedMethods = [static::METHOD_GET, static::METHOD_POST]; if (!in_array($custom_method, $allowedMethods, true)) { - throw new WebDriverException( + throw LogicException::forError( sprintf( 'Invalid custom method "%s", must be one of [%s]', $custom_method, @@ -73,7 +74,7 @@ protected function setCustomRequestParameters($custom_url, $custom_method) $this->customMethod = $custom_method; if (mb_strpos($custom_url, '/') !== 0) { - throw new WebDriverException( + throw LogicException::forError( sprintf('URL of custom command has to start with / but is "%s"', $custom_url) ); } diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 4c854fac2..c016a7f4f 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -2,8 +2,8 @@ namespace Facebook\WebDriver\Remote; -use Exception; use Facebook\WebDriver\Chrome\ChromeOptions; +use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Firefox\FirefoxDriver; use Facebook\WebDriver\Firefox\FirefoxOptions; use Facebook\WebDriver\Firefox\FirefoxProfile; @@ -152,7 +152,7 @@ public function isJavascriptEnabled() * This is a htmlUnit-only option. * * @param bool $enabled - * @throws Exception + * @throws UnsupportedOperationException * @return DesiredCapabilities * @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities */ @@ -160,7 +160,7 @@ public function setJavascriptEnabled($enabled) { $browser = $this->getBrowserName(); if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { - throw new Exception( + throw new UnsupportedOperationException( 'isJavascriptEnabled() is a htmlunit-only option. ' . 'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.' ); diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 6dbfd0c0e..d4602ac26 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -2,11 +2,11 @@ namespace Facebook\WebDriver\Remote; -use BadMethodCallException; -use Facebook\WebDriver\Exception\WebDriverCurlException; +use Facebook\WebDriver\Exception\Internal\LogicException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; +use Facebook\WebDriver\Exception\Internal\WebDriverCurlException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\WebDriverCommandExecutor; -use InvalidArgumentException; /** * Command executor talking to the standalone server via HTTP. @@ -272,7 +272,6 @@ public function setRequestTimeout($timeout_in_ms) /** * @param WebDriverCommand $command * - * @throws WebDriverException * @return WebDriverResponse */ public function execute(WebDriverCommand $command) @@ -292,13 +291,7 @@ public function execute(WebDriverCommand $command) } if (is_array($params) && !empty($params) && $http_method !== 'POST') { - throw new BadMethodCallException(sprintf( - 'The http method called for %s is %s but it has to be POST' . - ' if you want to pass the JSON params %s', - $url, - $http_method, - json_encode($params) - )); + throw LogicException::forInvalidHttpMethod($url, $http_method, $params); } curl_setopt($this->curl, CURLOPT_URL, $this->url . $url); @@ -334,30 +327,13 @@ public function execute(WebDriverCommand $command) $raw_results = trim(curl_exec($this->curl)); if ($error = curl_error($this->curl)) { - $msg = sprintf( - 'Curl error thrown for http %s to %s', - $http_method, - $url - ); - if (is_array($params) && !empty($params)) { - $msg .= sprintf(' with params: %s', json_encode($params, JSON_UNESCAPED_SLASHES)); - } - - throw new WebDriverCurlException($msg . "\n\n" . $error); + throw WebDriverCurlException::forCurlError($http_method, $url, $error, is_array($params) ? $params : null); } $results = json_decode($raw_results, true); if ($results === null && json_last_error() !== JSON_ERROR_NONE) { - throw new WebDriverException( - sprintf( - "JSON decoding of remote response failed.\n" . - "Error code: %d\n" . - "The response: '%s'\n", - json_last_error(), - $raw_results - ) - ); + throw UnexpectedResponseException::forJsonDecodingError(json_last_error(), $raw_results); } $value = null; @@ -414,7 +390,7 @@ protected function getCommandHttpOptions(WebDriverCommand $command) $commandName = $command->getName(); if (!isset(self::$commands[$commandName])) { if ($this->isW3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { - throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); + throw LogicException::forError($command->getName() . ' is not a valid command.'); } } diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index 5b9881371..979b9c4f6 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\UnsupportedOperationException; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\WebDriverAlert; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverTargetLocator; @@ -53,7 +53,7 @@ public function frame($frame) } elseif (is_int($frame)) { $id = $frame; } else { - throw new \InvalidArgumentException( + throw LogicException::forError( 'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null' ); } @@ -111,17 +111,17 @@ public function window($handle) * @param string $windowType The type of a new browser window that should be created. One of [tab, window]. * The created window is not guaranteed to be of the requested type; if the driver does not support the requested * type, a new browser window will be created of whatever type the driver does support. - * @throws UnsupportedOperationException + * @throws LogicException * @return RemoteWebDriver This driver focused on the given window */ public function newWindow($windowType = self::WINDOW_TYPE_TAB) { if ($windowType !== self::WINDOW_TYPE_TAB && $windowType !== self::WINDOW_TYPE_WINDOW) { - throw new \InvalidArgumentException('Window type must by either "tab" or "window"'); + throw LogicException::forError('Window type must by either "tab" or "window"'); } if (!$this->isW3cCompliant) { - throw new UnsupportedOperationException('New window is only supported in W3C mode'); + throw LogicException::forError('New window is only supported in W3C mode'); } $response = $this->executor->execute(DriverCommand::NEW_WINDOW, ['type' => $windowType]); diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 9c18cb886..5db0af1c2 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\UnknownErrorException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\Support\IsElementDisplayedAtom; @@ -212,7 +212,7 @@ public function findElement(WebDriverBy $by) ); if ($raw_element === null) { - throw new UnknownErrorException('Unexpected server response to findElement command'); + throw UnexpectedResponseException::forError('Unexpected server response to findElement command'); } return $this->newElement(JsonWireCompat::getElement($raw_element)); @@ -233,7 +233,7 @@ public function findElements(WebDriverBy $by) ); if ($raw_elements === null) { - throw new UnknownErrorException('Unexpected server response to findElements command'); + throw UnexpectedResponseException::forError('Unexpected server response to findElements command'); } $elements = []; diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 89b38752a..bf8ceeff0 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -3,8 +3,10 @@ namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\ElementNotInteractableException; +use Facebook\WebDriver\Exception\Internal\IOException; +use Facebook\WebDriver\Exception\Internal\LogicException; +use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; use Facebook\WebDriver\Exception\UnsupportedOperationException; -use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\Support\ScreenshotHelper; @@ -408,7 +410,7 @@ public function sendKeys($value) // This is so far non-W3C compliant method, so it may fail - if so, we just ignore the exception. // @see https://github.com/w3c/webdriver/issues/1355 $fileName = $this->upload($local_file); - } catch (WebDriverException $e) { + } catch (PhpWebDriverExceptionInterface $e) { $fileName = $local_file; } @@ -602,13 +604,13 @@ protected function newElement($id) * * @param string $local_file * - * @throws WebDriverException + * @throws LogicException * @return string The remote path of the file. */ protected function upload($local_file) { if (!is_file($local_file)) { - throw new WebDriverException('You may only upload files: ' . $local_file); + throw LogicException::forError('You may only upload files: ' . $local_file); } $temp_zip_path = $this->createTemporaryZipArchive($local_file); @@ -635,7 +637,7 @@ protected function createTemporaryZipArchive($fileToZip) $zip = new ZipArchive(); if (($errorCode = $zip->open($tempZipPath, ZipArchive::CREATE)) !== true) { - throw new WebDriverException(sprintf('Error creating zip archive: %s', $errorCode)); + throw IOException::forFileError(sprintf('Error creating zip archive: %s', $errorCode), $tempZipPath); } $info = pathinfo($fileToZip); diff --git a/lib/Remote/Service/DriverCommandExecutor.php b/lib/Remote/Service/DriverCommandExecutor.php index d16ec943d..3aadfea58 100644 --- a/lib/Remote/Service/DriverCommandExecutor.php +++ b/lib/Remote/Service/DriverCommandExecutor.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Remote\Service; -use Facebook\WebDriver\Exception\DriverServerDiedException; +use Facebook\WebDriver\Exception\Internal\DriverServerDiedException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\HttpCommandExecutor; diff --git a/lib/Remote/Service/DriverService.php b/lib/Remote/Service/DriverService.php index 237ca46b9..bb863bc82 100644 --- a/lib/Remote/Service/DriverService.php +++ b/lib/Remote/Service/DriverService.php @@ -2,7 +2,8 @@ namespace Facebook\WebDriver\Remote\Service; -use Exception; +use Facebook\WebDriver\Exception\Internal\IOException; +use Facebook\WebDriver\Exception\Internal\RuntimeException; use Facebook\WebDriver\Net\URLChecker; use Symfony\Component\Process\Process; use Symfony\Component\Process\ProcessBuilder; @@ -123,7 +124,7 @@ protected static function checkExecutable($executable) /** * @param string $executable - * @throws Exception + * @throws IOException */ protected function setExecutable($executable) { @@ -133,12 +134,10 @@ protected function setExecutable($executable) return; } - throw new Exception( - sprintf( - '"%s" is not executable. Make sure the path is correct or use environment variable to specify' - . ' location of the executable.', - $executable - ) + throw IOException::forFileError( + 'File is not executable. Make sure the path is correct or use environment variable to specify' + . ' location of the executable.', + $executable ); } @@ -150,13 +149,7 @@ protected function checkWasStarted($process) usleep(10000); // wait 10ms, otherwise the asynchronous process failure may not yet be propagated if (!$process->isRunning()) { - throw new Exception( - sprintf( - 'Error starting driver executable "%s": %s', - $process->getCommandLine(), - $process->getErrorOutput() - ) - ); + throw RuntimeException::forDriverError($process); } } diff --git a/lib/Support/ScreenshotHelper.php b/lib/Support/ScreenshotHelper.php index 5fe18efd1..956f56147 100644 --- a/lib/Support/ScreenshotHelper.php +++ b/lib/Support/ScreenshotHelper.php @@ -2,6 +2,8 @@ namespace Facebook\WebDriver\Support; +use Facebook\WebDriver\Exception\Internal\IOException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\RemoteExecuteMethod; @@ -43,13 +45,15 @@ private function takeScreenshot(array $commandToExecute, $saveAs = null) $response = $this->executor->execute(...$commandToExecute); if (!is_string($response)) { - throw new WebDriverException('Error taking screenshot, no data received from the remote end'); + throw UnexpectedResponseException::forError( + 'Error taking screenshot, no data received from the remote end' + ); } $screenshot = base64_decode($response, true); if ($screenshot === false) { - throw new WebDriverException('Error decoding screenshot data'); + throw UnexpectedResponseException::forError('Error decoding screenshot data'); } if ($saveAs !== null) { @@ -70,7 +74,7 @@ private function createDirectoryIfNotExists($directoryPath) { if (!file_exists($directoryPath)) { if (!mkdir($directoryPath, 0777, true) && !is_dir($directoryPath)) { - throw new WebDriverException(sprintf('Directory "%s" was not created', $directoryPath)); + throw IOException::forFileError('Directory cannot be not created', $directoryPath); } } } diff --git a/lib/WebDriverCheckboxes.php b/lib/WebDriverCheckboxes.php index 5467589c8..abc8cc46c 100644 --- a/lib/WebDriverCheckboxes.php +++ b/lib/WebDriverCheckboxes.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver; -use Facebook\WebDriver\Exception\WebDriverException; +use Facebook\WebDriver\Exception\InvalidElementStateException; /** * Provides helper methods for checkboxes. @@ -15,7 +15,7 @@ public function __construct(WebDriverElement $element) $this->type = $element->getAttribute('type'); if ($this->type !== 'checkbox') { - throw new WebDriverException('The input must be of type "checkbox".'); + throw new InvalidElementStateException('The input must be of type "checkbox".'); } } diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 5794d2580..188f3c4d8 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\NoSuchAlertException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\NoSuchFrameException; @@ -522,7 +523,7 @@ function (WebDriver $driver) use ($element_or_by, $selected) { ); } - throw new \InvalidArgumentException('Instance of either WebDriverElement or WebDriverBy must be given'); + throw LogicException::forError('Instance of either WebDriverElement or WebDriverBy must be given'); } /** diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index 84f2270eb..c84757fe8 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -2,10 +2,10 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\NoSuchCookieException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; -use InvalidArgumentException; /** * Managing stuff you would do in a browser. @@ -40,7 +40,7 @@ public function addCookie($cookie) $cookie = Cookie::createFromArray($cookie); } if (!$cookie instanceof Cookie) { - throw new InvalidArgumentException('Cookie must be set from instance of Cookie class or from array.'); + throw LogicException::forError('Cookie must be set from instance of Cookie class or from array.'); } $this->executor->execute( diff --git a/lib/WebDriverRadios.php b/lib/WebDriverRadios.php index e1687983e..aeaaaecac 100644 --- a/lib/WebDriverRadios.php +++ b/lib/WebDriverRadios.php @@ -2,8 +2,8 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\InvalidElementStateException; use Facebook\WebDriver\Exception\UnsupportedOperationException; -use Facebook\WebDriver\Exception\WebDriverException; /** * Provides helper methods for radio buttons. @@ -16,7 +16,7 @@ public function __construct(WebDriverElement $element) $this->type = $element->getAttribute('type'); if ($this->type !== 'radio') { - throw new WebDriverException('The input must be of type "radio".'); + throw new InvalidElementStateException('The input must be of type "radio".'); } } diff --git a/lib/WebDriverWindow.php b/lib/WebDriverWindow.php index 57754eb76..9d2a6e752 100644 --- a/lib/WebDriverWindow.php +++ b/lib/WebDriverWindow.php @@ -3,6 +3,7 @@ namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\IndexOutOfBoundsException; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; @@ -176,9 +177,7 @@ public function setScreenOrientation($orientation) { $orientation = mb_strtoupper($orientation); if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'], true)) { - throw new IndexOutOfBoundsException( - 'Orientation must be either PORTRAIT, or LANDSCAPE' - ); + throw LogicException::forError('Orientation must be either PORTRAIT, or LANDSCAPE'); } $this->executor->execute( diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index f3a098be4..4f62f6d7b 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Remote\RemoteWebElement; /** @@ -158,7 +159,7 @@ public function testShouldNotAcceptStringAsFrameIdInW3cMode() $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage( 'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null' ); diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index d58419a9a..5b99088ce 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -2,11 +2,9 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\LogicException; use PHPUnit\Framework\TestCase; -/** - * @covers \Facebook\WebDriver\Cookie - */ class CookieTest extends TestCase { public function testShouldSetAllProperties() @@ -168,7 +166,7 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() public function testShouldValidateCookieOnConstruction($name, $value, $domain, $expectedMessage) { if ($expectedMessage) { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage($expectedMessage); } diff --git a/tests/unit/Exception/DriverServerDiedExceptionTest.php b/tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php similarity index 70% rename from tests/unit/Exception/DriverServerDiedExceptionTest.php rename to tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php index 3c98e6e00..7ada0a6de 100644 --- a/tests/unit/Exception/DriverServerDiedExceptionTest.php +++ b/tests/unit/Exception/Internal/DriverServerDiedExceptionTest.php @@ -1,6 +1,6 @@ assertSame('Error message ("/file/path.txt")', $exception->getMessage()); + } +} diff --git a/tests/unit/Exception/Internal/LogicExceptionTest.php b/tests/unit/Exception/Internal/LogicExceptionTest.php new file mode 100644 index 000000000..ba09d58d8 --- /dev/null +++ b/tests/unit/Exception/Internal/LogicExceptionTest.php @@ -0,0 +1,26 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForInvalidHttpMethod(): void + { + $exception = LogicException::forInvalidHttpMethod('http://foo.bar/', 'FOO', ['key' => 'val']); + + $this->assertSame( + 'The http method called for "http://foo.bar/" is "FOO", but it has to be POST if you want to pass' + . ' the JSON params {"key":"val"}', + $exception->getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/RuntimeExceptionTest.php b/tests/unit/Exception/Internal/RuntimeExceptionTest.php new file mode 100644 index 000000000..97adf87b3 --- /dev/null +++ b/tests/unit/Exception/Internal/RuntimeExceptionTest.php @@ -0,0 +1,34 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForDriverError(): void + { + $processMock = $this->createConfiguredMock( + Process::class, + [ + 'getCommandLine' => '/bin/true --force', + 'getErrorOutput' => 'may the force be with you', + ] + ); + + $exception = RuntimeException::forDriverError($processMock); + + $this->assertSame( + 'Error starting driver executable "/bin/true --force": may the force be with you', + $exception->getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php b/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php new file mode 100644 index 000000000..80f4b4a0d --- /dev/null +++ b/tests/unit/Exception/Internal/UnexpectedResponseExceptionTest.php @@ -0,0 +1,30 @@ +assertSame('Error message', $exception->getMessage()); + } + + public function testShouldCreateExceptionForJsonDecodingError(): void + { + $exception = UnexpectedResponseException::forJsonDecodingError(JSON_ERROR_SYNTAX, 'foo'); + + $this->assertSame( + <<getMessage() + ); + } +} diff --git a/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php b/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php new file mode 100644 index 000000000..acc94cb88 --- /dev/null +++ b/tests/unit/Exception/Internal/WebDriverCurlExceptionTest.php @@ -0,0 +1,34 @@ +assertSame( + <<getMessage() + ); + } + + public function provideParams(): array + { + return [ + 'null params' => [null, ''], + 'empty params' => [[], ''], + 'array of params' => [['bar' => 'foo', 'baz' => 'bat'], ' with params: {"bar":"foo","baz":"bat"}'], + ]; + } +} diff --git a/tests/unit/Firefox/FirefoxOptionsTest.php b/tests/unit/Firefox/FirefoxOptionsTest.php index 2e937c6e4..83e71e53f 100644 --- a/tests/unit/Firefox/FirefoxOptionsTest.php +++ b/tests/unit/Firefox/FirefoxOptionsTest.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Firefox; +use Facebook\WebDriver\Exception\Internal\LogicException; use PHPUnit\Framework\TestCase; class FirefoxOptionsTest extends TestCase @@ -104,7 +105,7 @@ public function testShouldNotAllowToSetArgumentsOptionDirectly() { $options = new FirefoxOptions(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Use addArguments() method to add Firefox arguments'); $options->setOption('args', []); } @@ -113,7 +114,7 @@ public function testShouldNotAllowToSetPreferencesOptionDirectly() { $options = new FirefoxOptions(); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Use setPreference() method to set Firefox preferences'); $options->setOption('prefs', []); } diff --git a/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php b/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php index ddded93bb..fbd85a0ba 100644 --- a/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverSingleKeyActionTest.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Interactions\Internal; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; @@ -14,7 +15,7 @@ class WebDriverSingleKeyActionTest extends TestCase { public function testShouldThrowExceptionWhenNotUsedForModifier() { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage( 'keyDown / keyUp actions can only be used for modifier keys, but "foo" was given' ); diff --git a/tests/unit/Remote/CustomWebDriverCommandTest.php b/tests/unit/Remote/CustomWebDriverCommandTest.php index b56bc4aea..8de7320f2 100644 --- a/tests/unit/Remote/CustomWebDriverCommandTest.php +++ b/tests/unit/Remote/CustomWebDriverCommandTest.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\WebDriverException; +use Facebook\WebDriver\Exception\Internal\LogicException; use PHPUnit\Framework\TestCase; class CustomWebDriverCommandTest extends TestCase @@ -22,7 +22,7 @@ public function testShouldSetOptionsUsingConstructor() public function testCustomCommandInvalidUrlExceptionInit() { - $this->expectException(WebDriverException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('URL of custom command has to start with / but is "url-without-leading-slash"'); new CustomWebDriverCommand('session-id-123', 'url-without-leading-slash', 'POST', []); @@ -30,7 +30,7 @@ public function testCustomCommandInvalidUrlExceptionInit() public function testCustomCommandInvalidMethodExceptionInit() { - $this->expectException(WebDriverException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Invalid custom method "invalid-method", must be one of [GET, POST]'); new CustomWebDriverCommand('session-id-123', '/some-url', 'invalid-method', []); diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 198fd3997..4db77d687 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\UnknownErrorException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverNavigation; @@ -106,6 +106,9 @@ public function testShouldCreateWebDriverWaitInstance() } /** + * @covers ::findElement + * @covers ::findElements + * @covers \Facebook\WebDriver\Exception\Internal\UnexpectedResponseException * @param string $method * @param string $expectedExceptionMessage * @dataProvider provideMethods @@ -120,7 +123,7 @@ public function testShouldThrowExceptionOnUnexpectedNullValueFromRemoteEnd($meth $this->driver->setCommandExecutor($executorMock); - $this->expectException(UnknownErrorException::class); + $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage($expectedExceptionMessage); call_user_func([$this->driver, $method], $this->createMock(WebDriverBy::class)); } diff --git a/tests/unit/Support/ScreenshotHelperTest.php b/tests/unit/Support/ScreenshotHelperTest.php index 6d77b7cad..4b843414a 100644 --- a/tests/unit/Support/ScreenshotHelperTest.php +++ b/tests/unit/Support/ScreenshotHelperTest.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver\Support; -use Facebook\WebDriver\Exception\WebDriverException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\RemoteExecuteMethod; use PHPUnit\Framework\TestCase; @@ -89,7 +89,7 @@ public function testShouldThrowExceptionWhenInvalidDataReceived($data, $expected $helper = new ScreenshotHelper($executorMock); - $this->expectException(WebDriverException::class); + $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage($expectedExceptionMessage); $helper->takePageScreenshot(); } diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 7b4b8f45e..12e645b4e 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; use PHPUnit\Framework\TestCase; @@ -76,7 +77,7 @@ public function testShouldNotAllowToCreateCookieFromDifferentObjectThanCookie() $options = new WebDriverOptions($this->executor); - $this->expectException(\InvalidArgumentException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Cookie must be set from instance of Cookie class or from array.'); $options->addCookie($notCookie); } From a521f856b93a8eb4e173417590b0da1560ff3d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Oct 2022 15:29:04 +0200 Subject: [PATCH 069/130] Capabilities must be provided when resuing session, do not allow them to be null Null capabilities causes issues and inconsistencies in various places. --- lib/Remote/RemoteWebDriver.php | 20 +++++++++++++------- phpstan.neon | 2 ++ tests/unit/Remote/RemoteWebDriverTest.php | 9 ++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 5db0af1c2..f5908faf9 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -56,22 +56,19 @@ class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInpu /** * @param HttpCommandExecutor $commandExecutor * @param string $sessionId - * @param WebDriverCapabilities|null $capabilities + * @param WebDriverCapabilities $capabilities * @param bool $isW3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec */ protected function __construct( HttpCommandExecutor $commandExecutor, $sessionId, - WebDriverCapabilities $capabilities = null, + WebDriverCapabilities $capabilities, $isW3cCompliant = false ) { $this->executor = $commandExecutor; $this->sessionID = $sessionId; $this->isW3cCompliant = $isW3cCompliant; - - if ($capabilities !== null) { - $this->capabilities = $capabilities; - } + $this->capabilities = $capabilities; } /** @@ -147,6 +144,8 @@ public static function create( * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server * @param bool $isW3cCompliant True to use W3C WebDriver (default), false to use the legacy JsonWire protocol + * @param WebDriverCapabilities|null $existingCapabilities Provide capabilities of the existing previously created + * session. If not provided, we will attempt to read them, but this will only work when using Selenium Grid. * @return static */ public static function createBySessionID( @@ -157,6 +156,7 @@ public static function createBySessionID( ) { // BC layer to not break the method signature $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : true; + $existingCapabilities = func_num_args() > 5 ? func_get_arg(5) : null; $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { @@ -170,7 +170,13 @@ public static function createBySessionID( $executor->disableW3cCompliance(); } - return new static($executor, $session_id, null, $isW3cCompliant); + if ($existingCapabilities === null) { + throw new UnknownErrorException( + 'Existing capabilities must be provided when reusing previous session.' + ); + } + + return new static($executor, $session_id, $existingCapabilities, $isW3cCompliant); } /** diff --git a/phpstan.neon b/phpstan.neon index 747193ddf..b4c1e307d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -23,5 +23,7 @@ parameters: # Parameter is intentionally not part of signature to not break BC - message: '#PHPDoc tag \@param references unknown parameter: \$isW3cCompliant#' path: 'lib/Remote/RemoteWebDriver.php' + - message: '#PHPDoc tag \@param references unknown parameter: \$existingCapabilities#' + path: 'lib/Remote/RemoteWebDriver.php' inferPrivatePropertyTypeFromConstructor: true diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 4db77d687..48feec3fa 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -22,7 +22,14 @@ class RemoteWebDriverTest extends TestCase protected function setUp(): void { - $this->driver = RemoteWebDriver::createBySessionID('session-id', 'http://foo.bar:4444/'); + $this->driver = RemoteWebDriver::createBySessionID( + 'session-id', + 'http://foo.bar:4444/', + null, + null, + true, + new DesiredCapabilities([]) + ); } /** From f998f324b33942a92a477cf2a097a7c3d5c56bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 17 Oct 2022 18:45:02 +0200 Subject: [PATCH 070/130] Read capabilities of the reused session from the Selenium Grid --- .github/workflows/tests.yaml | 1 + CHANGELOG.md | 3 +- .../Internal/UnexpectedResponseException.php | 12 +++++ lib/Remote/RemoteWebDriver.php | 37 ++++++++++++-- .../functional/RemoteWebDriverCreateTest.php | 51 ++++++++++++++++++- tests/functional/WebDriverTestCase.php | 5 ++ tests/unit/Remote/RemoteWebDriverTest.php | 3 +- 7 files changed, 103 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f302d168f..1f0216d14 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -162,6 +162,7 @@ jobs: CHROMEDRIVER_PATH: "${{ (matrix.browser == 'chrome' && !matrix.selenium-server) && '/usr/local/share/chrome_driver/chromedriver' || '' }}" GECKODRIVER_PATH: "${{ (matrix.browser == 'firefox' && !matrix.selenium-server) && '/usr/local/share/gecko_driver/geckodriver' || '' }}" DISABLE_W3C_PROTOCOL: "${{ matrix.w3c && '0' || '1' }}" + SELENIUM_SERVER: "${{ matrix.selenium-server && '1' || '0' }}" run: | if [ "$BROWSER_NAME" = "chrome" ]; then EXCLUDE_GROUP+="exclude-chrome,"; fi if [ "$BROWSER_NAME" = "firefox" ]; then EXCLUDE_GROUP+="exclude-firefox,"; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index e466ab9c4..159676e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Require PHP ^7.3. +- Capabilities must be either explicitly provided or retrievable from Selenium Grid when resuing session with `createBySessionID()`. - Throw `UnexpectedResponseException` instead of `UnknownErrorException` in `findElement()` and `findElements()` methods. - Throw custom php-webdriver exceptions instead of native PHP SPL exceptions. -- Do not mix internal non-W3C WebDriver exceptions, separate them into own namespace. +- Do not mix internal non-W3C WebDriver exceptions, separate them into own namespaces. ## 1.13.0 - 2022-10-03 ### Added diff --git a/lib/Exception/Internal/UnexpectedResponseException.php b/lib/Exception/Internal/UnexpectedResponseException.php index dddde41bf..13597c8f7 100644 --- a/lib/Exception/Internal/UnexpectedResponseException.php +++ b/lib/Exception/Internal/UnexpectedResponseException.php @@ -26,4 +26,16 @@ public static function forJsonDecodingError(int $jsonLastError, string $rawResul ) ); } + + public static function forCapabilitiesRetrievalError(\Exception $previousException): self + { + return new self( + sprintf( + 'Existing Capabilities were not provided, and they also cannot be read from Selenium Grid' + . ' (error: "%s"). You are probably not using Selenium Grid, so to reuse the previous session,' + . ' Capabilities must be explicitly provided to createBySessionID() method.', + $previousException->getMessage() + ) + ); + } } diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index f5908faf9..b7aac41fd 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -136,8 +136,14 @@ public static function create( /** * [Experimental] Construct the RemoteWebDriver by an existing session. * - * This constructor can boost the performance a lot by reusing the same browser for the whole test suite. - * You cannot pass the desired capabilities because the session was created before. + * This constructor can boost the performance by reusing the same browser for the whole test suite. On the other + * hand, because the browser is not pristine, this may lead to flaky and dependent tests. So carefully + * consider the tradeoffs. + * + * To create the instance, we need to know Capabilities of the previously created session. You can either + * pass them in $existingCapabilities parameter, or we will attempt to receive them from the Selenium Grid server. + * However, if Capabilities were not provided and the attempt to get them was not successful, + * exception will be thrown. * * @param string $session_id The existing session id * @param string $selenium_server_url The url of the remote Selenium WebDriver server @@ -170,10 +176,9 @@ public static function createBySessionID( $executor->disableW3cCompliance(); } + // if capabilities were not provided, attempt to read them from the Selenium Grid API if ($existingCapabilities === null) { - throw new UnknownErrorException( - 'Existing capabilities must be provided when reusing previous session.' - ); + $existingCapabilities = self::readExistingCapabilitiesFromSeleniumGrid($session_id, $executor); } return new static($executor, $session_id, $existingCapabilities, $isW3cCompliant); @@ -733,4 +738,26 @@ protected static function castToDesiredCapabilitiesObject($desired_capabilities return $desired_capabilities; } + + protected static function readExistingCapabilitiesFromSeleniumGrid( + string $session_id, + HttpCommandExecutor $executor + ): DesiredCapabilities { + $getCapabilitiesCommand = new CustomWebDriverCommand($session_id, '/se/grid/session/:sessionId', 'GET', []); + + try { + $capabilitiesResponse = $executor->execute($getCapabilitiesCommand); + + $existingCapabilities = DesiredCapabilities::createFromW3cCapabilities( + $capabilitiesResponse->getValue()['capabilities'] + ); + if ($existingCapabilities === null) { + throw UnexpectedResponseException::forError('Empty capabilities received'); + } + } catch (\Exception $e) { + throw UnexpectedResponseException::forCapabilitiesRetrievalError($e); + } + + return $existingCapabilities; + } } diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 37426cad3..b37af2d38 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -2,12 +2,14 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\HttpCommandExecutor; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\WebDriverBrowserType; /** + * @covers \Facebook\WebDriver\Exception\Internal\UnexpectedResponseException * @covers \Facebook\WebDriver\Remote\HttpCommandExecutor * @covers \Facebook\WebDriver\Remote\RemoteWebDriver */ @@ -111,12 +113,41 @@ public function testShouldCreateInstanceFromExistingSessionId() $originalDriver->get($this->getTestPageUrl(TestPage::INDEX)); $this->compatAssertStringContainsString('/index.html', $originalDriver->getCurrentURL()); - // Store session ID + // Store session attributes $sessionId = $originalDriver->getSessionID(); $isW3cCompliant = $originalDriver->isW3cCompliant(); + $originalCapabilities = $originalDriver->getCapabilities(); + + $capabilitiesForSessionReuse = $originalCapabilities; + if ($this->isSeleniumServerUsed()) { + // do not provide capabilities when selenium server is used, to test they are read from selenium server + $capabilitiesForSessionReuse = null; + } // Create new RemoteWebDriver instance based on the session ID - $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl, null, null, $isW3cCompliant); + $this->driver = RemoteWebDriver::createBySessionID( + $sessionId, + $this->serverUrl, + null, + null, + $isW3cCompliant, + $capabilitiesForSessionReuse + ); + + // Capabilities should be retrieved and be set to the driver instance + $returnedCapabilities = $this->driver->getCapabilities(); + $this->assertInstanceOf(WebDriverCapabilities::class, $returnedCapabilities); + + $expectedBrowserName = $this->desiredCapabilities->getBrowserName(); + + if ($this->isSauceLabsBuild() && $expectedBrowserName === 'MicrosoftEdge') { + $expectedBrowserName = 'msedge'; // SauceLabs for some reason reports MicrosoftEdge as msedge + } + $this->assertEqualsIgnoringCase( + $expectedBrowserName, + $returnedCapabilities->getBrowserName() + ); + $this->assertEqualsCanonicalizing($originalCapabilities, $this->driver->getCapabilities()); // Check we reused the previous instance (window) and it has the same URL $this->compatAssertStringContainsString('/index.html', $this->driver->getCurrentURL()); @@ -125,6 +156,22 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->assertNotEmpty($this->driver->findElement(WebDriverBy::id('id_test'))->getText()); } + public function testShouldRequireCapabilitiesToBeSetToReuseExistingSession() + { + $this->expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage( + 'Existing Capabilities were not provided, and they also cannot be read from Selenium Grid' + ); + + // Do not provide capabilities, they also cannot be retrieved from the Selenium Grid + RemoteWebDriver::createBySessionID( + 'sessionId', + 'http://localhost:332/', // nothing should be running there + null, + null + ); + } + protected function createWebDriver() { } diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index dbd6606ac..685615970 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -110,6 +110,11 @@ public static function isW3cProtocolBuild() return getenv('DISABLE_W3C_PROTOCOL') !== '1'; } + public static function isSeleniumServerUsed(): bool + { + return getenv('SELENIUM_SERVER') === '1'; + } + public static function skipForW3cProtocol($message = 'Not supported by W3C specification') { if (static::isW3cProtocolBuild()) { diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 48feec3fa..76d6d1428 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; /** - * Unit part of RemoteWebDriver tests. Ie. tests for behavior which do not interact with the real remote server. + * Unit part of RemoteWebDriver tests. I.e. tests for behavior which do not interact with the real remote server. * * @coversDefaultClass \Facebook\WebDriver\Remote\RemoteWebDriver */ @@ -22,6 +22,7 @@ class RemoteWebDriverTest extends TestCase protected function setUp(): void { + // `createBySessionID()` is used because it is the simplest way to instantiate real RemoteWebDriver $this->driver = RemoteWebDriver::createBySessionID( 'session-id', 'http://foo.bar:4444/', From 1015581a7b770b0a3f4a165f559eaaec211e088a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 15 Nov 2022 12:28:33 +0100 Subject: [PATCH 071/130] Remove no longer needed compatibility layer with old PHPUnit --- tests/functional/RemoteTargetLocatorTest.php | 32 +++++++++---------- .../functional/RemoteWebDriverCreateTest.php | 4 +-- tests/functional/RemoteWebDriverTest.php | 4 +-- tests/functional/WebDriverTestCase.php | 16 ---------- 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index 4f62f6d7b..2ef90803c 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -24,7 +24,7 @@ public function testShouldSwitchToWindow() ); // At first the window should not be switched - $this->compatAssertStringContainsString('open_new_window.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('open_new_window.html', $this->driver->getCurrentURL()); $this->assertSame($originalWindowHandle, $this->driver->getWindowHandle()); /** @@ -44,7 +44,7 @@ public function testShouldSwitchToWindow() }); // After switchTo() is called, the active window should be changed - $this->compatAssertStringContainsString('index.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('index.html', $this->driver->getCurrentURL()); $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); } @@ -70,25 +70,25 @@ public function testShouldSwitchToFrameByItsId() $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(1); - $this->compatAssertStringContainsString($secondChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($secondChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->defaultContent(); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } public function testShouldSwitchToParentFrame() @@ -98,13 +98,13 @@ public function testShouldSwitchToParentFrame() $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); + $this->assertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->parent(); - $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); + $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } public function testShouldSwitchToFrameByElement() @@ -114,7 +114,7 @@ public function testShouldSwitchToFrameByElement() $element = $this->driver->findElement(WebDriverBy::id('iframe_content')); $this->driver->switchTo()->frame($element); - $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); + $this->assertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } /** @@ -129,8 +129,8 @@ public function testShouldCreateNewWindow() $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $this->assertEquals($this->getTestPageUrl(TestPage::INDEX), $this->driver->getCurrentUrl()); $source = $this->driver->getPageSource(); - $this->compatAssertStringContainsString('

      ', $source); - $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); + $this->assertStringContainsString('

      ', $source); + $this->assertStringContainsString('Welcome to the php-webdriver testing page.', $source); $windowHandles = $this->driver->getWindowHandles(); $this->assertCount(1, $windowHandles); @@ -178,6 +178,6 @@ public function testShouldAcceptStringAsFrameIdInJsonWireMode() $this->driver->switchTo()->frame('iframe_content'); - $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); + $this->assertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } } diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index b37af2d38..dcb883846 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -111,7 +111,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->requestTimeout ); $originalDriver->get($this->getTestPageUrl(TestPage::INDEX)); - $this->compatAssertStringContainsString('/index.html', $originalDriver->getCurrentURL()); + $this->assertStringContainsString('/index.html', $originalDriver->getCurrentURL()); // Store session attributes $sessionId = $originalDriver->getSessionID(); @@ -150,7 +150,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->assertEqualsCanonicalizing($originalCapabilities, $this->driver->getCapabilities()); // Check we reused the previous instance (window) and it has the same URL - $this->compatAssertStringContainsString('/index.html', $this->driver->getCurrentURL()); + $this->assertStringContainsString('/index.html', $this->driver->getCurrentURL()); // Do some interaction with the new driver $this->assertNotEmpty($this->driver->findElement(WebDriverBy::id('id_test'))->getText()); diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 262423d2b..07da0a0ae 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -42,8 +42,8 @@ public function testShouldGetPageSource() $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $source = $this->driver->getPageSource(); - $this->compatAssertStringContainsString('

      ', $source); - $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); + $this->assertStringContainsString('

      ', $source); + $this->assertStringContainsString('Welcome to the php-webdriver testing page.', $source); } /** diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 685615970..5db9e7894 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -240,22 +240,6 @@ protected function setUpSauceLabs() } } - /** - * Uses assertStringContainsString when it is available or uses assertContains for old phpunit versions - * @param string $needle - * @param string $haystack - * @param string $message - */ - protected function compatAssertStringContainsString($needle, $haystack, $message = '') - { - if (method_exists($this, 'assertStringContainsString')) { - parent::assertStringContainsString($needle, $haystack, $message); - - return; - } - parent::assertContains($needle, $haystack, $message); - } - protected function createWebDriver() { $this->driver = RemoteWebDriver::create( From 1321cc40c2dc4f15208eecc9b321913c24f12c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 3 Jan 2023 11:18:15 +0100 Subject: [PATCH 072/130] Use new shields.io badges URL See https://github.com/badges/shields/issues/8671 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f2432095..f3e8d3801 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # php-webdriver – Selenium WebDriver bindings for PHP [![Latest stable version](https://img.shields.io/packagist/v/php-webdriver/webdriver.svg?style=flat-square&label=Packagist)](https://packagist.org/packages/php-webdriver/webdriver) -[![GitHub Actions build status](https://img.shields.io/github/workflow/status/php-webdriver/php-webdriver/Tests?style=flat-square&label=GitHub%20Actions)](https://github.com/php-webdriver/php-webdriver/actions) -[![SauceLabs test status](https://img.shields.io/github/workflow/status/php-webdriver/php-webdriver/Sauce%20Labs?style=flat-square&label=SauceLabs)](https://saucelabs.com/u/php-webdriver) +[![GitHub Actions build status](https://img.shields.io/github/actions/workflow/status/php-webdriver/php-webdriver/tests.yaml?style=flat-square&label=GitHub%20Actions)](https://github.com/php-webdriver/php-webdriver/actions) +[![SauceLabs test status](https://img.shields.io/github/actions/workflow/status/php-webdriver/php-webdriver/sauce-labs.yaml?style=flat-square&label=SauceLabs)](https://saucelabs.com/u/php-webdriver) [![Total downloads](https://img.shields.io/packagist/dd/php-webdriver/webdriver.svg?style=flat-square&label=Downloads)](https://packagist.org/packages/php-webdriver/webdriver) ## Description From f335837f50b838d865557c31b5ee0a5844757cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 3 Jan 2023 13:00:49 +0100 Subject: [PATCH 073/130] Declare job step timout for fast fail when cannot start browser driver --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1f0216d14..101364c8c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -149,6 +149,7 @@ jobs: php -S 127.0.0.1:8000 -t tests/functional/web/ &> ./logs/php-server.log & - name: Wait for browser & PHP to start + timeout-minutes: 1 run: | while ! nc -z localhost 4444 Date: Mon, 30 Jan 2023 15:22:41 +0100 Subject: [PATCH 074/130] Test: Declare return types in test methods --- .php-cs-fixer.dist.php | 2 + .../Chrome/ChromeDevToolsDriverTest.php | 4 +- .../Chrome/ChromeDriverServiceTest.php | 8 +- tests/functional/Chrome/ChromeDriverTest.php | 8 +- tests/functional/FileUploadTest.php | 4 +- .../Firefox/FirefoxDriverServiceTest.php | 6 +- .../functional/Firefox/FirefoxDriverTest.php | 6 +- .../functional/Firefox/FirefoxProfileTest.php | 10 +-- tests/functional/RemoteKeyboardTest.php | 2 +- tests/functional/RemoteTargetLocatorTest.php | 16 ++-- .../functional/RemoteWebDriverCreateTest.php | 14 ++-- .../RemoteWebDriverFindElementTest.php | 10 +-- tests/functional/RemoteWebDriverTest.php | 28 +++---- tests/functional/RemoteWebElementTest.php | 50 ++++++------ .../ReportSauceLabsStatusListener.php | 4 +- tests/functional/RetrieveEventsTrait.php | 4 +- tests/functional/ShadowDomTest.php | 8 +- tests/functional/WebDriverActionsTest.php | 20 ++--- tests/functional/WebDriverAlertTest.php | 8 +- tests/functional/WebDriverByTest.php | 4 +- tests/functional/WebDriverCheckboxesTest.php | 36 ++++----- tests/functional/WebDriverNavigationTest.php | 6 +- .../WebDriverOptionsCookiesTest.php | 2 +- tests/functional/WebDriverRadiosTest.php | 36 ++++----- tests/functional/WebDriverSelectTest.php | 64 +++++++-------- tests/functional/WebDriverTestCase.php | 16 ++-- tests/functional/WebDriverTimeoutsTest.php | 6 +- tests/functional/WebDriverWindowTest.php | 14 ++-- tests/unit/CookieTest.php | 16 ++-- .../DriverServerDiedExceptionTest.php | 2 +- .../unit/Exception/WebDriverExceptionTest.php | 78 +++++++++---------- tests/unit/Firefox/FirefoxOptionsTest.php | 16 ++-- .../WebDriverButtonReleaseActionTest.php | 2 +- .../Internal/WebDriverClickActionTest.php | 2 +- .../WebDriverClickAndHoldActionTest.php | 2 +- .../WebDriverContextClickActionTest.php | 2 +- .../Internal/WebDriverCoordinatesTest.php | 2 +- .../WebDriverDoubleClickActionTest.php | 2 +- .../Internal/WebDriverKeyDownActionTest.php | 2 +- .../Internal/WebDriverKeyUpActionTest.php | 2 +- .../Internal/WebDriverMouseMoveActionTest.php | 2 +- .../WebDriverMouseToOffsetActionTest.php | 2 +- .../Internal/WebDriverSendKeysActionTest.php | 2 +- .../Internal/WebDriverSingleKeyActionTest.php | 2 +- .../Remote/CustomWebDriverCommandTest.php | 6 +- tests/unit/Remote/DesiredCapabilitiesTest.php | 26 +++---- tests/unit/Remote/HttpCommandExecutorTest.php | 2 +- tests/unit/Remote/LocalFileDetectorTest.php | 4 +- tests/unit/Remote/RemoteWebDriverTest.php | 20 ++--- tests/unit/Remote/RemoteWebElementTest.php | 2 +- tests/unit/Remote/WebDriverCommandTest.php | 4 +- tests/unit/Support/ScreenshotHelperTest.php | 10 +-- tests/unit/Support/XPathEscaperTest.php | 4 +- tests/unit/WebDriverExpectedConditionTest.php | 42 +++++----- tests/unit/WebDriverKeysTest.php | 4 +- tests/unit/WebDriverOptionsTest.php | 16 ++-- 56 files changed, 337 insertions(+), 335 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 23496f768..6c0bacf2f 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -98,6 +98,7 @@ 'phpdoc_trim' => true, 'phpdoc_types' => true, 'phpdoc_var_annotation_correct_order' => true, + //'pow_to_exponentiation' => true, 'psr_autoloading' => true, 'random_api_migration' => true, 'self_accessor' => true, @@ -118,6 +119,7 @@ 'trim_array_spaces' => true, 'unary_operator_spaces' => true, 'visibility_required' => ['elements' => ['method', 'property', 'const']], + //'void_return' => true, 'whitespace_after_comma_in_array' => true, 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], ]) diff --git a/tests/functional/Chrome/ChromeDevToolsDriverTest.php b/tests/functional/Chrome/ChromeDevToolsDriverTest.php index b927c73d6..28e01965c 100644 --- a/tests/functional/Chrome/ChromeDevToolsDriverTest.php +++ b/tests/functional/Chrome/ChromeDevToolsDriverTest.php @@ -22,7 +22,7 @@ protected function setUp(): void } } - public function testShouldExecuteDevToolsCommandWithoutParameters() + public function testShouldExecuteDevToolsCommandWithoutParameters(): void { $devTools = new ChromeDevToolsDriver($this->driver); @@ -31,7 +31,7 @@ public function testShouldExecuteDevToolsCommandWithoutParameters() $this->assertSame([], $result); } - public function testShouldExecuteDevToolsCommandWithParameters() + public function testShouldExecuteDevToolsCommandWithParameters(): void { $devTools = new ChromeDevToolsDriver($this->driver); diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php index d83ca6af4..33526c451 100644 --- a/tests/functional/Chrome/ChromeDriverServiceTest.php +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -30,7 +30,7 @@ protected function tearDown(): void } } - public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() + public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor(): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(ChromeDriverService::CHROME_DRIVER_EXECUTABLE . '=' . getenv('CHROMEDRIVER_PATH')); @@ -50,7 +50,7 @@ public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() $this->assertInstanceOf(ChromeDriverService::class, $this->driverService->stop()); } - public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() + public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor(): void { $this->driverService = new ChromeDriverService(getenv('CHROMEDRIVER_PATH'), 9515, ['--port=9515']); @@ -63,7 +63,7 @@ public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() $this->assertFalse($this->driverService->isRunning()); } - public function testShouldThrowExceptionIfExecutableIsNotExecutable() + public function testShouldThrowExceptionIfExecutableIsNotExecutable(): void { putenv(ChromeDriverService::CHROME_DRIVER_EXECUTABLE . '=' . __FILE__); @@ -72,7 +72,7 @@ public function testShouldThrowExceptionIfExecutableIsNotExecutable() ChromeDriverService::createDefaultService(); } - public function testShouldUseDefaultExecutableIfNoneProvided() + public function testShouldUseDefaultExecutableIfNoneProvided(): void { // Put path where ChromeDriver binary is actually located to system PATH, to make sure we can locate it putenv('PATH=' . getenv('PATH') . ':' . dirname(getenv('CHROMEDRIVER_PATH'))); diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index 695fa57e3..7fac8c1b4 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -37,7 +37,7 @@ protected function tearDown(): void * @dataProvider provideDialect * @param bool $isW3cDialect */ - public function testShouldStartChromeDriver($isW3cDialect) + public function testShouldStartChromeDriver($isW3cDialect): void { $this->startChromeDriver($isW3cDialect); $this->assertInstanceOf(ChromeDriver::class, $this->driver); @@ -55,7 +55,7 @@ public function testShouldStartChromeDriver($isW3cDialect) /** * @return array[] */ - public function provideDialect() + public function provideDialect(): array { return [ 'w3c' => [true], @@ -63,7 +63,7 @@ public function provideDialect() ]; } - public function testShouldInstantiateDevTools() + public function testShouldInstantiateDevTools(): void { $this->startChromeDriver(); @@ -81,7 +81,7 @@ public function testShouldInstantiateDevTools() $this->assertSame(['result' => ['type' => 'string', 'value' => 'http://localhost:8000/']], $cdpResult); } - private function startChromeDriver($w3cDialect = true) + private function startChromeDriver($w3cDialect = true): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(ChromeDriverService::CHROME_DRIVER_EXECUTABLE . '=' . getenv('CHROMEDRIVER_PATH')); diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index 4ca5e1e60..c1d60603c 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -16,7 +16,7 @@ class FileUploadTest extends WebDriverTestCase * @group exclude-saucelabs * W3C protocol does not support remote file upload: https://github.com/w3c/webdriver/issues/1355 */ - public function testShouldUploadAFile() + public function testShouldUploadAFile(): void { $this->driver->get($this->getTestPageUrl(TestPage::UPLOAD)); @@ -43,7 +43,7 @@ public function testShouldUploadAFile() $this->assertSame('10', $uploadedFileSize); } - private function getTestFilePath() + private function getTestFilePath(): string { return __DIR__ . '/Fixtures/FileUploadTestFile.txt'; } diff --git a/tests/functional/Firefox/FirefoxDriverServiceTest.php b/tests/functional/Firefox/FirefoxDriverServiceTest.php index 190286847..d9e60a26e 100644 --- a/tests/functional/Firefox/FirefoxDriverServiceTest.php +++ b/tests/functional/Firefox/FirefoxDriverServiceTest.php @@ -32,7 +32,7 @@ protected function tearDown(): void } } - public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() + public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor(): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); @@ -52,7 +52,7 @@ public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() $this->assertInstanceOf(FirefoxDriverService::class, $this->driverService->stop()); } - public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() + public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor(): void { $this->driverService = new FirefoxDriverService(getenv('GECKODRIVER_PATH'), 9515, ['-p=9515']); @@ -65,7 +65,7 @@ public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() $this->assertFalse($this->driverService->isRunning()); } - public function testShouldUseDefaultExecutableIfNoneProvided() + public function testShouldUseDefaultExecutableIfNoneProvided(): void { // Put path where geckodriver binary is actually located to system PATH, to make sure we can locate it putenv('PATH=' . getenv('PATH') . ':' . dirname(getenv('GECKODRIVER_PATH'))); diff --git a/tests/functional/Firefox/FirefoxDriverTest.php b/tests/functional/Firefox/FirefoxDriverTest.php index 7c7b91370..42ee45f69 100644 --- a/tests/functional/Firefox/FirefoxDriverTest.php +++ b/tests/functional/Firefox/FirefoxDriverTest.php @@ -37,7 +37,7 @@ protected function tearDown(): void } } - public function testShouldStartFirefoxDriver() + public function testShouldStartFirefoxDriver(): void { $this->startFirefoxDriver(); $this->assertInstanceOf(FirefoxDriver::class, $this->driver); @@ -53,7 +53,7 @@ public function testShouldStartFirefoxDriver() $this->assertSame('http://localhost:8000/', $this->driver->getCurrentURL()); } - public function testShouldSetPreferenceWithFirefoxOptions() + public function testShouldSetPreferenceWithFirefoxOptions(): void { $firefoxOptions = new FirefoxOptions(); $firefoxOptions->setPreference('javascript.enabled', false); @@ -69,7 +69,7 @@ public function testShouldSetPreferenceWithFirefoxOptions() ); } - private function startFirefoxDriver(FirefoxOptions $firefoxOptions = null) + private function startFirefoxDriver(FirefoxOptions $firefoxOptions = null): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); diff --git a/tests/functional/Firefox/FirefoxProfileTest.php b/tests/functional/Firefox/FirefoxProfileTest.php index 59d0f6bfa..982ebfbcc 100644 --- a/tests/functional/Firefox/FirefoxProfileTest.php +++ b/tests/functional/Firefox/FirefoxProfileTest.php @@ -64,7 +64,7 @@ protected function tearDown(): void } } - public function testShouldStartDriverWithEmptyProfile() + public function testShouldStartDriverWithEmptyProfile(): void { $firefoxProfile = new FirefoxProfile(); $this->startFirefoxDriverWithProfile($firefoxProfile); @@ -77,7 +77,7 @@ public function testShouldStartDriverWithEmptyProfile() ); } - public function testShouldInstallExtension() + public function testShouldInstallExtension(): void { $firefoxProfile = new FirefoxProfile(); $firefoxProfile->addExtension($this->firefoxTestExtensionFilename); @@ -95,7 +95,7 @@ public function testShouldInstallExtension() $this->assertEquals('This element was added by browser extension', $element->getText()); } - public function testShouldUseProfilePreferences() + public function testShouldUseProfilePreferences(): void { $firefoxProfile = new FirefoxProfile(); @@ -115,7 +115,7 @@ public function testShouldUseProfilePreferences() ); } - protected function getTestPageUrl($path) + protected function getTestPageUrl($path): string { $host = 'http://localhost:8000/'; if ($alternateHost = getenv('FIXTURES_HOST')) { @@ -125,7 +125,7 @@ protected function getTestPageUrl($path) return $host . '/' . $path; } - private function startFirefoxDriverWithProfile(FirefoxProfile $firefoxProfile) + private function startFirefoxDriverWithProfile(FirefoxProfile $firefoxProfile): void { // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(FirefoxDriverService::WEBDRIVER_FIREFOX_DRIVER . '=' . getenv('GECKODRIVER_PATH')); diff --git a/tests/functional/RemoteKeyboardTest.php b/tests/functional/RemoteKeyboardTest.php index dfab3a687..09a6430f1 100644 --- a/tests/functional/RemoteKeyboardTest.php +++ b/tests/functional/RemoteKeyboardTest.php @@ -19,7 +19,7 @@ class RemoteKeyboardTest extends WebDriverTestCase * @group exclude-safari * https://feedbackassistant.apple.com/feedback/9051272 */ - public function testShouldPressSendAndReleaseKeys() + public function testShouldPressSendAndReleaseKeys(): void { $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index 2ef90803c..8aa965181 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -10,7 +10,7 @@ */ class RemoteTargetLocatorTest extends WebDriverTestCase { - public function testShouldSwitchToWindow() + public function testShouldSwitchToWindow(): void { $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $originalWindowHandle = $this->driver->getWindowHandle(); @@ -48,7 +48,7 @@ public function testShouldSwitchToWindow() $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); } - public function testActiveElement() + public function testActiveElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -62,7 +62,7 @@ public function testActiveElement() $this->assertSame('test_name', $activeElement->getAttribute('name')); } - public function testShouldSwitchToFrameByItsId() + public function testShouldSwitchToFrameByItsId(): void { $parentPage = 'This is the host page which contains an iFrame'; $firstChildFrame = 'This is the content of the iFrame'; @@ -91,7 +91,7 @@ public function testShouldSwitchToFrameByItsId() $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } - public function testShouldSwitchToParentFrame() + public function testShouldSwitchToParentFrame(): void { $parentPage = 'This is the host page which contains an iFrame'; $firstChildFrame = 'This is the content of the iFrame'; @@ -107,7 +107,7 @@ public function testShouldSwitchToParentFrame() $this->assertStringContainsString($parentPage, $this->driver->getPageSource()); } - public function testShouldSwitchToFrameByElement() + public function testShouldSwitchToFrameByElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::PAGE_WITH_FRAME)); @@ -120,7 +120,7 @@ public function testShouldSwitchToFrameByElement() /** * @group exclude-saucelabs */ - public function testShouldCreateNewWindow() + public function testShouldCreateNewWindow(): void { self::skipForJsonWireProtocol('Create new window is not supported in JsonWire protocol'); @@ -153,7 +153,7 @@ public function testShouldCreateNewWindow() /** * @group exclude-saucelabs */ - public function testShouldNotAcceptStringAsFrameIdInW3cMode() + public function testShouldNotAcceptStringAsFrameIdInW3cMode(): void { self::skipForJsonWireProtocol(); @@ -170,7 +170,7 @@ public function testShouldNotAcceptStringAsFrameIdInW3cMode() /** * @group exclude-saucelabs */ - public function testShouldAcceptStringAsFrameIdInJsonWireMode() + public function testShouldAcceptStringAsFrameIdInJsonWireMode(): void { self::skipForW3cProtocol(); diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index dcb883846..d55e1f681 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -15,7 +15,7 @@ */ class RemoteWebDriverCreateTest extends WebDriverTestCase { - public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() + public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver(): void { $this->driver = RemoteWebDriver::create( $this->serverUrl, @@ -51,7 +51,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertNotEmpty($returnedCapabilities->getVersion()); } - public function testShouldAcceptCapabilitiesAsAnArray() + public function testShouldAcceptCapabilitiesAsAnArray(): void { // Method has a side-effect of converting whole content of desiredCapabilities to an array $this->desiredCapabilities->toArray(); @@ -66,7 +66,7 @@ public function testShouldAcceptCapabilitiesAsAnArray() $this->assertNotNull($this->driver->getCapabilities()); } - public function testShouldCreateWebDriverWithRequiredCapabilities() + public function testShouldCreateWebDriverWithRequiredCapabilities(): void { $requiredCapabilities = new DesiredCapabilities(); @@ -89,7 +89,7 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() * However the the browser driver must be able to create non-headless instance (eg. inside xvfb). * @group exclude-saucelabs */ - public function testShouldCreateWebDriverWithoutCapabilities() + public function testShouldCreateWebDriverWithoutCapabilities(): void { if (getenv('GECKODRIVER') !== '1' && empty(getenv('CHROMEDRIVER_PATH'))) { $this->markTestSkipped('This test makes sense only when run directly via specific browser driver'); @@ -101,7 +101,7 @@ public function testShouldCreateWebDriverWithoutCapabilities() $this->assertNotEmpty($this->driver->getSessionID()); } - public function testShouldCreateInstanceFromExistingSessionId() + public function testShouldCreateInstanceFromExistingSessionId(): void { // Create driver instance and load page "index.html" $originalDriver = RemoteWebDriver::create( @@ -156,7 +156,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->assertNotEmpty($this->driver->findElement(WebDriverBy::id('id_test'))->getText()); } - public function testShouldRequireCapabilitiesToBeSetToReuseExistingSession() + public function testShouldRequireCapabilitiesToBeSetToReuseExistingSession(): void { $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage( @@ -172,7 +172,7 @@ public function testShouldRequireCapabilitiesToBeSetToReuseExistingSession() ); } - protected function createWebDriver() + protected function createWebDriver(): void { } } diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 80adf1be1..699442d7c 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -11,7 +11,7 @@ */ class RemoteWebDriverFindElementTest extends WebDriverTestCase { - public function testShouldThrowExceptionIfElementCannotBeFound() + public function testShouldThrowExceptionIfElementCannotBeFound(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -19,7 +19,7 @@ public function testShouldThrowExceptionIfElementCannotBeFound() $this->driver->findElement(WebDriverBy::id('not_existing')); } - public function testShouldFindElementIfExistsOnAPage() + public function testShouldFindElementIfExistsOnAPage(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -28,7 +28,7 @@ public function testShouldFindElementIfExistsOnAPage() $this->assertInstanceOf(RemoteWebElement::class, $element); } - public function testShouldReturnEmptyArrayIfElementsCannotBeFound() + public function testShouldReturnEmptyArrayIfElementsCannotBeFound(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -38,7 +38,7 @@ public function testShouldReturnEmptyArrayIfElementsCannotBeFound() $this->assertCount(0, $elements); } - public function testShouldFindMultipleElements() + public function testShouldFindMultipleElements(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -52,7 +52,7 @@ public function testShouldFindMultipleElements() /** * @group exclude-saucelabs */ - public function testEscapeCssSelector() + public function testEscapeCssSelector(): void { self::skipForJsonWireProtocol( 'CSS selectors containing special characters are not supported by the legacy protocol' diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 07da0a0ae..213dce0c5 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -13,7 +13,7 @@ class RemoteWebDriverTest extends WebDriverTestCase /** * @covers ::getTitle */ - public function testShouldGetPageTitle() + public function testShouldGetPageTitle(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -27,7 +27,7 @@ public function testShouldGetPageTitle() * @covers ::get * @covers ::getCurrentURL */ - public function testShouldGetCurrentUrl() + public function testShouldGetCurrentUrl(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -37,7 +37,7 @@ public function testShouldGetCurrentUrl() /** * @covers ::getPageSource */ - public function testShouldGetPageSource() + public function testShouldGetPageSource(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -50,7 +50,7 @@ public function testShouldGetPageSource() * @covers ::getSessionID * @covers ::isW3cCompliant */ - public function testShouldGetSessionId() + public function testShouldGetSessionId(): void { // This tests is intentionally included in another test, to not slow down build. // @TODO Remove following in 2.0 @@ -70,7 +70,7 @@ public function testShouldGetSessionId() * @group exclude-saucelabs * @covers ::getAllSessions */ - public function testShouldGetAllSessions() + public function testShouldGetAllSessions(): void { self::skipForW3cProtocol(); @@ -89,7 +89,7 @@ public function testShouldGetAllSessions() * @covers ::getCommandExecutor * @covers ::quit */ - public function testShouldQuitAndUnsetExecutor() + public function testShouldQuitAndUnsetExecutor(): void { self::skipForW3cProtocol(); @@ -116,7 +116,7 @@ public function testShouldQuitAndUnsetExecutor() * @covers ::getWindowHandle * @covers ::getWindowHandles */ - public function testShouldGetWindowHandles() + public function testShouldGetWindowHandles(): void { $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); @@ -140,7 +140,7 @@ public function testShouldGetWindowHandles() /** * @covers ::close */ - public function testShouldCloseWindow() + public function testShouldCloseWindow(): void { $this->driver->get($this->getTestPageUrl(TestPage::OPEN_NEW_WINDOW)); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); @@ -158,7 +158,7 @@ public function testShouldCloseWindow() * @covers ::executeScript * @group exclude-saucelabs */ - public function testShouldExecuteScriptAndDoNotBlockExecution() + public function testShouldExecuteScriptAndDoNotBlockExecution(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -188,7 +188,7 @@ function(){document.getElementById("id_test").innerHTML = "Text changed by scrip * @covers ::executeAsyncScript * @covers \Facebook\WebDriver\WebDriverTimeouts::setScriptTimeout */ - public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() + public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished(): void { $this->driver->manage()->timeouts()->setScriptTimeout(1); @@ -228,7 +228,7 @@ function(){ * @covers ::prepareScriptArguments * @group exclude-saucelabs */ - public function testShouldExecuteScriptWithParamsAndReturnValue() + public function testShouldExecuteScriptWithParamsAndReturnValue(): void { $this->driver->manage()->timeouts()->setScriptTimeout(1); @@ -252,7 +252,7 @@ public function testShouldExecuteScriptWithParamsAndReturnValue() * @covers ::takeScreenshot * @covers \Facebook\WebDriver\Support\ScreenshotHelper */ - public function testShouldTakeScreenshot() + public function testShouldTakeScreenshot(): void { if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); @@ -274,7 +274,7 @@ public function testShouldTakeScreenshot() * @group exclude-safari * Safari is returning different color profile and it does not have way to configure "force-color-profile" */ - public function testShouldSaveScreenshotToFile() + public function testShouldSaveScreenshotToFile(): void { if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); @@ -314,7 +314,7 @@ public function testShouldSaveScreenshotToFile() * @group exclude-saucelabs * Status endpoint is not supported on Sauce Labs */ - public function testShouldGetRemoteEndStatus() + public function testShouldGetRemoteEndStatus(): void { $status = $this->driver->getStatus(); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index acd8b4a91..0baff69f1 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -19,7 +19,7 @@ class RemoteWebElementTest extends WebDriverTestCase * @group exclude-safari * Safari does not normalize white-spaces */ - public function testShouldGetText() + public function testShouldGetText(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $elementWithSimpleText = $this->driver->findElement(WebDriverBy::id('text-simple')); @@ -33,7 +33,7 @@ public function testShouldGetText() /** * @covers ::getAttribute */ - public function testShouldGetAttributeValue() + public function testShouldGetAttributeValue(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -48,7 +48,7 @@ public function testShouldGetAttributeValue() /** * @covers ::getDomProperty */ - public function testShouldGetDomPropertyValue() + public function testShouldGetDomPropertyValue(): void { self::skipForJsonWireProtocol(); @@ -70,7 +70,7 @@ public function testShouldGetDomPropertyValue() /** * @covers ::getLocation */ - public function testShouldGetLocation() + public function testShouldGetLocation(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -85,7 +85,7 @@ public function testShouldGetLocation() /** * @covers ::getLocationOnScreenOnceScrolledIntoView */ - public function testShouldGetLocationOnScreenOnceScrolledIntoView() + public function testShouldGetLocationOnScreenOnceScrolledIntoView(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -110,7 +110,7 @@ public function testShouldGetLocationOnScreenOnceScrolledIntoView() /** * @covers ::getSize */ - public function testShouldGetSize() + public function testShouldGetSize(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -125,7 +125,7 @@ public function testShouldGetSize() /** * @covers ::getCSSValue */ - public function testShouldGetCssValue() + public function testShouldGetCssValue(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -145,7 +145,7 @@ public function testShouldGetCssValue() /** * @covers ::getTagName */ - public function testShouldGetTagName() + public function testShouldGetTagName(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -157,7 +157,7 @@ public function testShouldGetTagName() /** * @covers ::click */ - public function testShouldClick() + public function testShouldClick(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); @@ -180,7 +180,7 @@ public function testShouldClick() * @group exclude-chrome * @group exclude-edge */ - public function testGeckoDriverShouldClickOnBlockLevelElement() + public function testGeckoDriverShouldClickOnBlockLevelElement(): void { self::skipForUnmatchedBrowsers(['firefox']); @@ -209,7 +209,7 @@ public function testGeckoDriverShouldClickOnBlockLevelElement() * @group exclude-chrome * @group exclude-edge */ - public function testGeckoDriverShouldClickNotInteractable() + public function testGeckoDriverShouldClickNotInteractable(): void { self::skipForUnmatchedBrowsers(['firefox']); @@ -237,7 +237,7 @@ public function testGeckoDriverShouldClickNotInteractable() /** * @covers ::clear */ - public function testShouldClearFormElementText() + public function testShouldClearFormElementText(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -256,7 +256,7 @@ public function testShouldClearFormElementText() /** * @covers ::sendKeys */ - public function testShouldSendKeysToFormElement() + public function testShouldSendKeysToFormElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -294,7 +294,7 @@ public function testShouldSendKeysToFormElement() * @covers \Facebook\WebDriver\Remote\RemoteWebDriver::execute * @covers \Facebook\WebDriver\Support\IsElementDisplayedAtom */ - public function testShouldDetectElementDisplayedness() + public function testShouldDetectElementDisplayedness(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -310,7 +310,7 @@ public function testShouldDetectElementDisplayedness() /** * @covers ::isEnabled */ - public function testShouldDetectEnabledInputs() + public function testShouldDetectEnabledInputs(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -324,7 +324,7 @@ public function testShouldDetectEnabledInputs() /** * @covers ::isSelected */ - public function testShouldSelectedInputsOrOptions() + public function testShouldSelectedInputsOrOptions(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -352,7 +352,7 @@ public function testShouldSelectedInputsOrOptions() * @covers ::submit * @group exclude-edge */ - public function testShouldSubmitFormBySubmitEventOnForm() + public function testShouldSubmitFormBySubmitEventOnForm(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -370,7 +370,7 @@ public function testShouldSubmitFormBySubmitEventOnForm() /** * @covers ::submit */ - public function testShouldSubmitFormBySubmitEventOnFormInputElement() + public function testShouldSubmitFormBySubmitEventOnFormInputElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -388,7 +388,7 @@ public function testShouldSubmitFormBySubmitEventOnFormInputElement() /** * @covers ::click */ - public function testShouldSubmitFormByClickOnSubmitInput() + public function testShouldSubmitFormByClickOnSubmitInput(): void { $this->driver->get($this->getTestPageUrl(TestPage::FORM)); @@ -406,7 +406,7 @@ public function testShouldSubmitFormByClickOnSubmitInput() /** * @covers ::equals */ - public function testShouldCompareEqualsElement() + public function testShouldCompareEqualsElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); @@ -425,7 +425,7 @@ public function testShouldCompareEqualsElement() /** * @covers ::findElement */ - public function testShouldThrowExceptionIfChildElementCannotBeFound() + public function testShouldThrowExceptionIfChildElementCannotBeFound(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); @@ -434,7 +434,7 @@ public function testShouldThrowExceptionIfChildElementCannotBeFound() $element->findElement(WebDriverBy::id('not_existing')); } - public function testShouldFindChildElementIfExistsOnAPage() + public function testShouldFindChildElementIfExistsOnAPage(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); @@ -446,7 +446,7 @@ public function testShouldFindChildElementIfExistsOnAPage() $this->assertSame('First', $childElement->getText()); } - public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() + public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); @@ -457,7 +457,7 @@ public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() $this->assertCount(0, $childElements); } - public function testShouldFindMultipleChildElements() + public function testShouldFindMultipleChildElements(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); @@ -476,7 +476,7 @@ public function testShouldFindMultipleChildElements() * @covers \Facebook\WebDriver\Support\ScreenshotHelper * @group exclude-saucelabs */ - public function testShouldTakeAndSaveElementScreenshot() + public function testShouldTakeAndSaveElementScreenshot(): void { self::skipForJsonWireProtocol('Take element screenshot is only part of W3C protocol'); diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php index ac71c6035..96db8f27b 100644 --- a/tests/functional/ReportSauceLabsStatusListener.php +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -82,7 +82,7 @@ public function startTest(\PHPUnit\Framework\Test $test): void * @param int $testStatus * @return bool */ - private function testWasSkippedOrIncomplete($testStatus) + private function testWasSkippedOrIncomplete($testStatus): bool { if ($testStatus === \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED || $testStatus === \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) { @@ -96,7 +96,7 @@ private function testWasSkippedOrIncomplete($testStatus) * @param string $url * @param array $data */ - private function submitToSauceLabs($url, array $data) + private function submitToSauceLabs($url, array $data): void { $curl = curl_init($url); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); diff --git a/tests/functional/RetrieveEventsTrait.php b/tests/functional/RetrieveEventsTrait.php index 32653e05a..8188070e0 100644 --- a/tests/functional/RetrieveEventsTrait.php +++ b/tests/functional/RetrieveEventsTrait.php @@ -12,7 +12,7 @@ trait RetrieveEventsTrait /** * @return array */ - private function retrieveLoggedKeyboardEvents() + private function retrieveLoggedKeyboardEvents(): array { return $this->retrieveLoggerEvents(WebDriverBy::id('keyboardEventsLog')); } @@ -20,7 +20,7 @@ private function retrieveLoggedKeyboardEvents() /** * @return array */ - private function retrieveLoggedMouseEvents() + private function retrieveLoggedMouseEvents(): array { return $this->retrieveLoggerEvents(WebDriverBy::id('mouseEventsLog')); } diff --git a/tests/functional/ShadowDomTest.php b/tests/functional/ShadowDomTest.php index ccd144bc9..ed442874e 100644 --- a/tests/functional/ShadowDomTest.php +++ b/tests/functional/ShadowDomTest.php @@ -20,7 +20,7 @@ protected function setUp(): void parent::setUp(); } - public function testShouldGetShadowRoot() + public function testShouldGetShadowRoot(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); @@ -31,7 +31,7 @@ public function testShouldGetShadowRoot() $this->assertInstanceOf(ShadowRoot::class, $shadowRoot); } - public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavingShadowRoot() + public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavingShadowRoot(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); @@ -46,7 +46,7 @@ public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavin * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 * Finding elements in shadow DOM is not implemented in Geckodriver */ - public function testShouldFindElementUnderShadowRoot() + public function testShouldFindElementUnderShadowRoot(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); @@ -66,7 +66,7 @@ public function testShouldFindElementUnderShadowRoot() * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 * Finding elements in shadow DOM is not implemented in Geckodriver */ - public function testShouldReferenceTheSameShadowRootAsFromExecuteScript() + public function testShouldReferenceTheSameShadowRootAsFromExecuteScript(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 7b71d2a17..95d359cd5 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -25,7 +25,7 @@ class WebDriverActionsTest extends WebDriverTestCase { use RetrieveEventsTrait; - public function testShouldClickOnElement() + public function testShouldClickOnElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); @@ -47,7 +47,7 @@ public function testShouldClickOnElement() $this->assertSame($logs, $loggedEvents); } - public function testShouldClickAndHoldOnElementAndRelease() + public function testShouldClickAndHoldOnElementAndRelease(): void { $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); @@ -72,7 +72,7 @@ public function testShouldClickAndHoldOnElementAndRelease() /** * @group exclude-saucelabs */ - public function testShouldContextClickOnElement() + public function testShouldContextClickOnElement(): void { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::MICROSOFT_EDGE) { $this->markTestSkipped('Getting stuck in EdgeDriver'); @@ -97,7 +97,7 @@ public function testShouldContextClickOnElement() * @group exclude-safari * https://github.com/webdriverio/webdriverio/issues/231 */ - public function testShouldDoubleClickOnElement() + public function testShouldDoubleClickOnElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); @@ -113,7 +113,7 @@ public function testShouldDoubleClickOnElement() /** * @group exclude-saucelabs */ - public function testShouldSendKeysUpAndDown() + public function testShouldSendKeysUpAndDown(): void { $this->driver->get($this->getTestPageUrl(TestPage::EVENTS)); @@ -142,7 +142,7 @@ public function testShouldSendKeysUpAndDown() * @group exclude-safari * https://developer.apple.com/forums/thread/662677 */ - public function testShouldMoveToElement() + public function testShouldMoveToElement(): void { $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); @@ -165,7 +165,7 @@ public function testShouldMoveToElement() * @group exclude-safari * https://developer.apple.com/forums/thread/662677 */ - public function testShouldMoveByOffset() + public function testShouldMoveByOffset(): void { $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); @@ -188,7 +188,7 @@ public function testShouldMoveByOffset() * https://developer.apple.com/forums/thread/662677 * @group exclude-saucelabs */ - public function testShouldDragAndDrop() + public function testShouldDragAndDrop(): void { $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); @@ -221,7 +221,7 @@ public function testShouldDragAndDrop() * https://developer.apple.com/forums/thread/662677 * it does not work even with Python Selenium, looks like Safaridriver does not implements Interaction API */ - public function testShouldDragAndDropBy() + public function testShouldDragAndDropBy(): void { $this->driver->get($this->getTestPageUrl(TestPage::SORTABLE)); @@ -253,7 +253,7 @@ public function testShouldDragAndDropBy() /** * @return array */ - private function retrieveListContent() + private function retrieveListContent(): array { return [ $this->retrieveLoggerEvents(WebDriverBy::cssSelector('#sortable1')), diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 622dd10dd..34c4255b5 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -18,7 +18,7 @@ protected function setUp(): void $this->driver->get($this->getTestPageUrl(TestPage::ALERT)); } - public function testShouldAcceptAlert() + public function testShouldAcceptAlert(): void { // Open alert (it is delayed for 1 second, to make sure following wait for alertIsPresent works properly) $this->driver->findElement(WebDriverBy::id('open-alert-delayed'))->click(); @@ -39,7 +39,7 @@ public function testShouldAcceptAlert() $this->driver->switchTo()->alert()->accept(); } - public function testShouldAcceptAndDismissConfirmation() + public function testShouldAcceptAndDismissConfirmation(): void { // Open confirmation $this->driver->findElement(WebDriverBy::id('open-confirm'))->click(); @@ -61,7 +61,7 @@ public function testShouldAcceptAndDismissConfirmation() $this->assertSame('dismissed', $this->getResultText()); } - public function testShouldSubmitPromptText() + public function testShouldSubmitPromptText(): void { // Open confirmation $this->driver->findElement(WebDriverBy::id('open-prompt'))->click(); @@ -77,7 +77,7 @@ public function testShouldSubmitPromptText() $this->assertSame('Text entered to prompt', $this->getResultText()); } - private function getResultText() + private function getResultText(): string { return $this->driver ->findElement(WebDriverBy::id('result')) diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 27ee04755..87ad8a417 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -22,7 +22,7 @@ public function testShouldFindTextElementByLocator( $webDriverByLocatorValue, $expectedText = null, $expectedAttributeValue = null - ) { + ): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $by = call_user_func([WebDriverBy::class, $webDriverByLocatorMethod], $webDriverByLocatorValue); @@ -42,7 +42,7 @@ public function testShouldFindTextElementByLocator( /** * @return array[] */ - public function provideTextElements() + public function provideTextElements(): array { return [ 'id' => ['id', 'id_test', 'Test by ID'], diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index 4d7f82b52..e22c54002 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -18,7 +18,7 @@ protected function setUp(): void $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } - public function testIsMultiple() + public function testIsMultiple(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -27,7 +27,7 @@ public function testIsMultiple() $this->assertTrue($checkboxes->isMultiple()); } - public function testGetOptions() + public function testGetOptions(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//form[2]//input[@type="checkbox"]')) @@ -36,7 +36,7 @@ public function testGetOptions() $this->assertNotEmpty($checkboxes->getOptions()); } - public function testGetFirstSelectedOption() + public function testGetFirstSelectedOption(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -47,7 +47,7 @@ public function testGetFirstSelectedOption() $this->assertSame('j2a', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5b"]')) @@ -56,7 +56,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j5b', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5d"]')) @@ -65,7 +65,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j5c', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testSelectByValue() + public function testSelectByValue(): void { $selectedOptions = ['j2b', 'j2c']; @@ -83,7 +83,7 @@ public function testSelectByValue() $this->assertSame($selectedOptions, $selectedValues); } - public function testSelectByValueInvalid() + public function testSelectByValueInvalid(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -94,7 +94,7 @@ public function testSelectByValueInvalid() $checkboxes->selectByValue('notexist'); } - public function testSelectByIndex() + public function testSelectByIndex(): void { $selectedOptions = [1 => 'j2b', 2 => 'j2c']; @@ -112,7 +112,7 @@ public function testSelectByIndex() $this->assertSame(array_values($selectedOptions), $selectedValues); } - public function testSelectByIndexInvalid() + public function testSelectByIndexInvalid(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -129,7 +129,7 @@ public function testSelectByIndexInvalid() * @param string $text * @param string $value */ - public function testSelectByVisibleText($text, $value) + public function testSelectByVisibleText($text, $value): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -143,7 +143,7 @@ public function testSelectByVisibleText($text, $value) /** * @return array[] */ - public function provideSelectByVisibleTextData() + public function provideSelectByVisibleTextData(): array { return [ ['J 2 B', 'j2b'], @@ -157,7 +157,7 @@ public function provideSelectByVisibleTextData() * @param string $text * @param string $value */ - public function testSelectByVisiblePartialText($text, $value) + public function testSelectByVisiblePartialText($text, $value): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -171,7 +171,7 @@ public function testSelectByVisiblePartialText($text, $value) /** * @return array[] */ - public function provideSelectByVisiblePartialTextData() + public function provideSelectByVisiblePartialTextData(): array { return [ ['2 B', 'j2b'], @@ -179,7 +179,7 @@ public function provideSelectByVisiblePartialTextData() ]; } - public function testDeselectAll() + public function testDeselectAll(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -191,7 +191,7 @@ public function testDeselectAll() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByIndex() + public function testDeselectByIndex(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -203,7 +203,7 @@ public function testDeselectByIndex() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByValue() + public function testDeselectByValue(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -215,7 +215,7 @@ public function testDeselectByValue() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByVisibleText() + public function testDeselectByVisibleText(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) @@ -227,7 +227,7 @@ public function testDeselectByVisibleText() $this->assertEmpty($checkboxes->getAllSelectedOptions()); } - public function testDeselectByVisiblePartialText() + public function testDeselectByVisiblePartialText(): void { $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php index f3fea87e8..1a36244bd 100644 --- a/tests/functional/WebDriverNavigationTest.php +++ b/tests/functional/WebDriverNavigationTest.php @@ -11,7 +11,7 @@ class WebDriverNavigationTest extends WebDriverTestCase * @covers ::__construct * @covers ::to */ - public function testShouldNavigateToUrl() + public function testShouldNavigateToUrl(): void { $this->driver->navigate()->to($this->getTestPageUrl(TestPage::INDEX)); @@ -22,7 +22,7 @@ public function testShouldNavigateToUrl() * @covers ::back * @covers ::forward */ - public function testShouldNavigateBackAndForward() + public function testShouldNavigateBackAndForward(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); @@ -51,7 +51,7 @@ public function testShouldNavigateBackAndForward() /** * @covers ::refresh */ - public function testShouldRefreshPage() + public function testShouldRefreshPage(): void { $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); diff --git a/tests/functional/WebDriverOptionsCookiesTest.php b/tests/functional/WebDriverOptionsCookiesTest.php index 31b915fd9..ec83e65ab 100644 --- a/tests/functional/WebDriverOptionsCookiesTest.php +++ b/tests/functional/WebDriverOptionsCookiesTest.php @@ -16,7 +16,7 @@ protected function setUp(): void $this->driver->get($this->getTestPageUrl(TestPage::INDEX)); } - public function testShouldSetGetAndDeleteCookies() + public function testShouldSetGetAndDeleteCookies(): void { $cookie1 = new Cookie('cookie1', 'cookie1Value'); $cookie2 = new Cookie('cookie2', 'cookie2Value'); diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index d2c28d085..ffc25c67a 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -19,14 +19,14 @@ protected function setUp(): void $this->driver->get($this->getTestPageUrl(TestPage::FORM_CHECKBOX_RADIO)); } - public function testIsMultiple() + public function testIsMultiple(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->assertFalse($radios->isMultiple()); } - public function testGetOptions() + public function testGetOptions(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $values = []; @@ -37,7 +37,7 @@ public function testGetOptions() $this->assertSame(['j3a', 'j3b', 'j3c'], $values); } - public function testGetFirstSelectedOption() + public function testGetFirstSelectedOption(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -46,14 +46,14 @@ public function testGetFirstSelectedOption() $this->assertSame('j3a', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); $this->assertEquals('j4b', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId(): void { $radios = new WebDriverRadios( $this->driver->findElement(WebDriverBy::xpath('//input[@id="j4c"]')) @@ -62,7 +62,7 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j4c', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testSelectByValue() + public function testSelectByValue(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByValue('j3b'); @@ -73,7 +73,7 @@ public function testSelectByValue() $this->assertSame('j3b', $selectedOptions[0]->getAttribute('value')); } - public function testSelectByValueInvalid() + public function testSelectByValueInvalid(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -82,7 +82,7 @@ public function testSelectByValueInvalid() $radios->selectByValue('notexist'); } - public function testSelectByIndex() + public function testSelectByIndex(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByIndex(1); @@ -92,7 +92,7 @@ public function testSelectByIndex() $this->assertSame('j3b', $allSelectedOptions[0]->getAttribute('value')); } - public function testSelectByIndexInvalid() + public function testSelectByIndexInvalid(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -107,7 +107,7 @@ public function testSelectByIndexInvalid() * @param string $text * @param string $value */ - public function testSelectByVisibleText($text, $value) + public function testSelectByVisibleText($text, $value): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByVisibleText($text); @@ -117,7 +117,7 @@ public function testSelectByVisibleText($text, $value) /** * @return array[] */ - public function provideSelectByVisibleTextData() + public function provideSelectByVisibleTextData(): array { return [ ['J 3 B', 'j3b'], @@ -131,7 +131,7 @@ public function provideSelectByVisibleTextData() * @param string $text * @param string $value */ - public function testSelectByVisiblePartialText($text, $value) + public function testSelectByVisiblePartialText($text, $value): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $radios->selectByVisiblePartialText($text); @@ -141,7 +141,7 @@ public function testSelectByVisiblePartialText($text, $value) /** * @return array[] */ - public function provideSelectByVisiblePartialTextData() + public function provideSelectByVisiblePartialTextData(): array { return [ ['3 B', 'j3b'], @@ -149,7 +149,7 @@ public function provideSelectByVisiblePartialTextData() ]; } - public function testDeselectAllRadio() + public function testDeselectAllRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -158,7 +158,7 @@ public function testDeselectAllRadio() $radios->deselectAll(); } - public function testDeselectByIndexRadio() + public function testDeselectByIndexRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -167,7 +167,7 @@ public function testDeselectByIndexRadio() $radios->deselectByIndex(0); } - public function testDeselectByValueRadio() + public function testDeselectByValueRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -176,7 +176,7 @@ public function testDeselectByValueRadio() $radios->deselectByValue('val'); } - public function testDeselectByVisibleTextRadio() + public function testDeselectByVisibleTextRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); @@ -185,7 +185,7 @@ public function testDeselectByVisibleTextRadio() $radios->deselectByVisibleText('AB'); } - public function testDeselectByVisiblePartialTextRadio() + public function testDeselectByVisiblePartialTextRadio(): void { $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index 01c075188..b688a10cb 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -20,7 +20,7 @@ protected function setUp(): void $this->driver->get($this->getTestPageUrl(TestPage::FORM)); } - public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple() + public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple(): void { $originalElement = $this->driver->findElement(WebDriverBy::cssSelector('#select')); $originalMultipleElement = $this->driver->findElement(WebDriverBy::cssSelector('#select-multiple')); @@ -35,7 +35,7 @@ public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultip $this->assertTrue($selectMultiple->isMultiple()); } - public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() + public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement(): void { $notSelectElement = $this->driver->findElement(WebDriverBy::cssSelector('textarea')); @@ -48,7 +48,7 @@ public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() * @dataProvider provideSelectSelector * @param string $selector */ - public function testShouldGetOptionsOfSelect($selector) + public function testShouldGetOptionsOfSelect($selector): void { $originalElement = $this->driver->findElement(WebDriverBy::cssSelector($selector)); $select = new WebDriverSelect($originalElement); @@ -62,7 +62,7 @@ public function testShouldGetOptionsOfSelect($selector) /** * @return array[] */ - public function provideSelectSelector() + public function provideSelectSelector(): array { return [ 'simple + + + +
      From 4543539aea54a59efff69b7622f8b15892c86fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 27 Jul 2023 13:28:33 +0200 Subject: [PATCH 084/130] Fix path to chromedriver in tests on latest Ubuntu https://github.com/actions/runner-images/blob/ubuntu22/20230724.1/images/linux/Ubuntu2204-Readme.md#environment-variables-1 --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 101364c8c..7fa8f0385 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -160,7 +160,7 @@ jobs: # When running directly against Firefox, we test only using geckodriver (not against legacy Firefox =<45), so we must declare GECKODRIVER=1 GECKODRIVER: "${{ (matrix.browser == 'firefox' && !matrix.selenium-server) && '1' || '0' }}" # Provide CHROMEDRIVER_PATH and GECKODRIVER_PATH so that tests for local web drivers are able to start the browser - CHROMEDRIVER_PATH: "${{ (matrix.browser == 'chrome' && !matrix.selenium-server) && '/usr/local/share/chrome_driver/chromedriver' || '' }}" + CHROMEDRIVER_PATH: "${{ (matrix.browser == 'chrome' && !matrix.selenium-server) && '/usr/local/share/chromedriver-linux64/chromedriver' || '' }}" GECKODRIVER_PATH: "${{ (matrix.browser == 'firefox' && !matrix.selenium-server) && '/usr/local/share/gecko_driver/geckodriver' || '' }}" DISABLE_W3C_PROTOCOL: "${{ matrix.w3c && '0' || '1' }}" SELENIUM_SERVER: "${{ matrix.selenium-server && '1' || '0' }}" From 2c1b0e86a5911440b96eccecef55d51c896631b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 22 Aug 2023 11:20:04 +0200 Subject: [PATCH 085/130] Fix codestyle with latest php-cs-fixer --- .../Internal/WebDriverCoordinates.php | 5 +++-- lib/Local/LocalWebDriver.php | 22 ++----------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/lib/Interactions/Internal/WebDriverCoordinates.php b/lib/Interactions/Internal/WebDriverCoordinates.php index 91ad29f7c..3a92f0a64 100644 --- a/lib/Interactions/Internal/WebDriverCoordinates.php +++ b/lib/Interactions/Internal/WebDriverCoordinates.php @@ -11,7 +11,8 @@ class WebDriverCoordinates { /** - * @var null + * @var mixed + * @todo remove in next major version (if it is unused) */ private $onScreen; /** @@ -28,7 +29,7 @@ class WebDriverCoordinates private $auxiliary; /** - * @param null $on_screen + * @param mixed $on_screen * @param string $auxiliary */ public function __construct($on_screen, callable $in_view_port, callable $on_page, $auxiliary) diff --git a/lib/Local/LocalWebDriver.php b/lib/Local/LocalWebDriver.php index c0ab32a42..1b02bff9b 100644 --- a/lib/Local/LocalWebDriver.php +++ b/lib/Local/LocalWebDriver.php @@ -12,17 +12,7 @@ */ abstract class LocalWebDriver extends RemoteWebDriver { - /** - * @param string $selenium_server_url - * @param null $desired_capabilities - * @param null $connection_timeout_in_ms - * @param null $request_timeout_in_ms - * @param null $http_proxy - * @param null $http_proxy_port - * @throws LogicException - * @return RemoteWebDriver - * @todo Remove in next major version (should not be inherited) - */ + // @todo Remove in next major version (should not be inherited) public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, @@ -35,15 +25,7 @@ public static function create( throw LogicException::forError('Use start() method to start local WebDriver.'); } - /** - * @param string $session_id - * @param string $selenium_server_url - * @param null $connection_timeout_in_ms - * @param null $request_timeout_in_ms - * @throws LogicException - * @return RemoteWebDriver - * @todo Remove in next major version (should not be inherited) - */ + // @todo Remove in next major version (should not be inherited) public static function createBySessionID( $session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', From ce824604045b244b5f5f34404d016bf9dad44535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 22 Aug 2023 13:14:40 +0200 Subject: [PATCH 086/130] Update PHP versions in test builds, run tests primarily on PHP 8 --- .github/workflows/sauce-labs.yaml | 2 +- .github/workflows/tests.yaml | 7 +++---- tests/functional/RemoteWebElementTest.php | 7 +++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index 41ebc4d1c..0880cbc00 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -34,7 +34,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.2' extensions: mbstring, intl, zip coverage: none diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 7fa8f0385..f2c94bc1d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,7 +19,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.2' extensions: mbstring, intl, zip - name: Install PHP dependencies @@ -46,11 +46,10 @@ jobs: strategy: matrix: - php-version: ['7.3', '7.4', '8.0'] + php-version: ['7.3', '7.4', '8.0', '8.1', '8.2'] dependencies: [''] include: - { php-version: '7.3', dependencies: '--prefer-lowest' } - - { php-version: '8.1', dependencies: '--ignore-platform-req=php' } steps: - uses: actions/checkout@v3 @@ -105,7 +104,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.2 extensions: mbstring, intl, zip coverage: xdebug diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 7c51e09ce..6a21cb36c 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -515,8 +515,11 @@ public function testShouldTakeAndSaveElementScreenshot(): void // Assert string output $imageFromString = imagecreatefromstring($outputPngString); - $this->assertNotFalse($imageFromString); - $this->assertTrue(is_resource($imageFromString)); + if (version_compare(phpversion(), '8.0.0', '>=')) { + $this->assertInstanceOf(\GdImage::class, $imageFromString); + } else { + $this->assertTrue(is_resource($imageFromString)); + } if ($isSafari && !$isCi) { $this->assertEquals(10, imagesx($imageFromString)); From 15dde790acb44f5a5d06940cbea4ed29e1121323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 25 Apr 2023 11:39:03 +0200 Subject: [PATCH 087/130] Do not fail on fatal error on unexpected response when getting element(s) --- CHANGELOG.md | 5 ++++- .../Internal/UnexpectedResponseException.php | 10 ++++++++++ lib/Remote/JsonWireCompat.php | 12 ++++++++++- lib/Remote/RemoteWebDriver.php | 8 ++------ lib/Remote/RemoteWebElement.php | 5 +++++ lib/Remote/ShadowRoot.php | 7 +++++++ .../functional/Remote/JsonWireCompatTest.php | 17 ++++++++++++++++ tests/unit/Remote/RemoteWebDriverTest.php | 20 ++++--------------- 8 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 tests/functional/Remote/JsonWireCompatTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 819493d4a..3653e9e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,12 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Capability key `ChromeOptions::CAPABILITY_W3C` used to set ChromeOptions is now deprecated in favor of `ChromeOptions::CAPABILITY`, which now also contains the W3C compatible value (`goog:chromeOptions`). - ChromeOptions are now passed to the driver always as a W3C compatible key `goog:chromeOptions`, even in the deprecated OSS JsonWire payload (as ChromeDriver [supports](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1786) this since 2017). +### Fixed +- Handle unexpected response when getting element(s) by throwing an exception, not triggering fatal error. + ## 1.14.0 - 2023-02-09 ### Added -- `PhpWebDriverExceptionInterface` as a common interface to identify all exceptions thrown in php-webdriver +- `PhpWebDriverExceptionInterface` as a common interface to identify all exceptions thrown in php-webdriver. ### Changed - Require PHP ^7.3. diff --git a/lib/Exception/Internal/UnexpectedResponseException.php b/lib/Exception/Internal/UnexpectedResponseException.php index 13597c8f7..18cdc88cc 100644 --- a/lib/Exception/Internal/UnexpectedResponseException.php +++ b/lib/Exception/Internal/UnexpectedResponseException.php @@ -14,6 +14,16 @@ public static function forError(string $message): self return new self($message); } + public static function forElementNotArray($response): self + { + return new self( + sprintf( + "Unexpected server response for getting an element. Expected array, but the response was: '%s'\n", + print_r($response, true) + ) + ); + } + public static function forJsonDecodingError(int $jsonLastError, string $rawResults): self { return new self( diff --git a/lib/Remote/JsonWireCompat.php b/lib/Remote/JsonWireCompat.php index 4ba791e92..ec8944816 100644 --- a/lib/Remote/JsonWireCompat.php +++ b/lib/Remote/JsonWireCompat.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\WebDriverBy; /** @@ -18,8 +19,17 @@ abstract class JsonWireCompat */ public const WEB_DRIVER_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf'; - public static function getElement(array $rawElement) + /** + * @param mixed $rawElement Value is validated to by an array, exception is thrown otherwise + * @throws UnexpectedResponseException When value of other type than array is given + */ + public static function getElement($rawElement) { + // The method intentionally accept mixed, so that assertion of the rawElement format could be done on one place + if (!is_array($rawElement)) { + throw UnexpectedResponseException::forElementNotArray($rawElement); + } + if (array_key_exists(self::WEB_DRIVER_ELEMENT_IDENTIFIER, $rawElement)) { // W3C's WebDriver return $rawElement[self::WEB_DRIVER_ELEMENT_IDENTIFIER]; diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index e53abd38c..313312e59 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -219,10 +219,6 @@ public function findElement(WebDriverBy $by) JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); - if ($raw_element === null) { - throw UnexpectedResponseException::forError('Unexpected server response to findElement command'); - } - return $this->newElement(JsonWireCompat::getElement($raw_element)); } @@ -239,8 +235,8 @@ public function findElements(WebDriverBy $by) JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); - if ($raw_elements === null) { - throw UnexpectedResponseException::forError('Unexpected server response to findElements command'); + if (!is_array($raw_elements)) { + throw UnexpectedResponseException::forError('Server response to findElements command is not an array'); } $elements = []; diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index fa09ddc64..641f32ad8 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -5,6 +5,7 @@ use Facebook\WebDriver\Exception\ElementNotInteractableException; use Facebook\WebDriver\Exception\Internal\IOException; use Facebook\WebDriver\Exception\Internal\LogicException; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; @@ -131,6 +132,10 @@ public function findElements(WebDriverBy $by) $params ); + if (!is_array($raw_elements)) { + throw UnexpectedResponseException::forError('Server response to findChildElements command is not an array'); + } + $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); diff --git a/lib/Remote/ShadowRoot.php b/lib/Remote/ShadowRoot.php index 1baed693a..5d419b19a 100644 --- a/lib/Remote/ShadowRoot.php +++ b/lib/Remote/ShadowRoot.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\UnknownErrorException; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverElement; @@ -73,6 +74,12 @@ public function findElements(WebDriverBy $locator) $params ); + if (!is_array($rawElements)) { + throw UnexpectedResponseException::forError( + 'Server response to findElementsFromShadowRoot command is not an array' + ); + } + $elements = []; foreach ($rawElements as $rawElement) { $elements[] = new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true); diff --git a/tests/functional/Remote/JsonWireCompatTest.php b/tests/functional/Remote/JsonWireCompatTest.php new file mode 100644 index 000000000..8dfb5e29a --- /dev/null +++ b/tests/functional/Remote/JsonWireCompatTest.php @@ -0,0 +1,17 @@ +expectException(UnexpectedResponseException::class); + $this->expectExceptionMessage('Unexpected server response for getting an element. Expected array'); + + JsonWireCompat::getElement(null); + } +} diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index cbb4fc30d..666ac9e5c 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -114,15 +114,11 @@ public function testShouldCreateWebDriverWaitInstance(): void } /** - * @covers ::findElement * @covers ::findElements * @covers \Facebook\WebDriver\Exception\Internal\UnexpectedResponseException - * @dataProvider provideMethods */ - public function testShouldThrowExceptionOnUnexpectedNullValueFromRemoteEnd( - string $method, - string $expectedExceptionMessage - ): void { + public function testShouldThrowExceptionOnUnexpectedValueFromRemoteEndWhenFindingElements(): void + { $executorMock = $this->createMock(HttpCommandExecutor::class); $executorMock->expects($this->once()) ->method('execute') @@ -132,15 +128,7 @@ public function testShouldThrowExceptionOnUnexpectedNullValueFromRemoteEnd( $this->driver->setCommandExecutor($executorMock); $this->expectException(UnexpectedResponseException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - call_user_func([$this->driver, $method], $this->createMock(WebDriverBy::class)); - } - - public function provideMethods(): array - { - return [ - ['findElement', 'Unexpected server response to findElement command'], - ['findElements', 'Unexpected server response to findElements command'], - ]; + $this->expectExceptionMessage('Server response to findElements command is not an array'); + $this->driver->findElements($this->createMock(WebDriverBy::class)); } } From 22242281132fca0a236a7fdd4d7cf3cb52aa3bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 29 Aug 2023 15:51:40 +0200 Subject: [PATCH 088/130] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3653e9e16..c36231ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Capability key `ChromeOptions::CAPABILITY_W3C` used to set ChromeOptions is now deprecated in favor of `ChromeOptions::CAPABILITY`, which now also contains the W3C compatible value (`goog:chromeOptions`). - ChromeOptions are now passed to the driver always as a W3C compatible key `goog:chromeOptions`, even in the deprecated OSS JsonWire payload (as ChromeDriver [supports](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1786) this since 2017). +- Improve Safari compatibility for ` +

      Element out of Shadow DOM

      From 590cf7192bc4b34606ada3d4a0e7b7a3d88ddebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 19 Nov 2024 14:34:18 +0100 Subject: [PATCH 110/130] test: Enable shadow dom tests in geckodriver --- tests/functional/ShadowDomTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/functional/ShadowDomTest.php b/tests/functional/ShadowDomTest.php index f98359883..dc95eaa84 100644 --- a/tests/functional/ShadowDomTest.php +++ b/tests/functional/ShadowDomTest.php @@ -41,11 +41,6 @@ public function testShouldThrowExceptionWhenGettingShadowRootWithElementNotHavin $element->getShadowRoot(); } - /** - * @group exclude-firefox - * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 - * Finding elements in shadow DOM is not implemented in Geckodriver - */ public function testShouldFindElementUnderShadowRoot(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); @@ -61,11 +56,6 @@ public function testShouldFindElementUnderShadowRoot(): void $this->assertCount(2, $elementsInShadow); } - /** - * @group exclude-firefox - * https://bugzilla.mozilla.org/show_bug.cgi?id=1700097 - * Finding elements in shadow DOM is not implemented in Geckodriver - */ public function testShouldReferenceTheSameShadowRootAsFromExecuteScript(): void { $this->driver->get($this->getTestPageUrl(TestPage::WEB_COMPONENTS)); From 38c892dff7e396a46f8c452fc31cd3caa0aff428 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 30 Oct 2024 10:49:28 +0100 Subject: [PATCH 111/130] test: Allow running tests in headfull (not headless) mode --- .github/CONTRIBUTING.md | 9 ++++++++- .github/workflows/tests.yaml | 1 + tests/functional/WebDriverTestCase.php | 13 +++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 26ee86b2f..a79b02b1f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -69,7 +69,7 @@ composer all composer test -- --testsuite functional ``` -If you want to run tests in different browser then "htmlunit" (Chrome or Firefox), you need to setup the browser driver (Chromedriver/Geckodriver), as it is [explained in wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome) +If you want to run tests in different browser then "htmlunit" (Chrome or Firefox), you need to set up the browser driver (Chromedriver/Geckodriver), as it is [explained in wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome) and then the `BROWSER_NAME` environment variable: ```sh @@ -85,3 +85,10 @@ export GECKODRIVER=1 export BROWSER_NAME="firefox" composer all ``` + +To see the tests as they are happening (in the browser window), you can disable headless mode. This is useful eg. when debugging the tests or writing a new one: + +```sh +export DISABLE_HEADLESS="1" +composer all +``` diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 08dcc6239..870d1d0f2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -162,6 +162,7 @@ jobs: CHROMEDRIVER_PATH: "${{ (matrix.browser == 'chrome' && !matrix.selenium-server) && '/usr/local/share/chromedriver-linux64/chromedriver' || '' }}" GECKODRIVER_PATH: "${{ (matrix.browser == 'firefox' && !matrix.selenium-server) && '/usr/local/share/gecko_driver/geckodriver' || '' }}" DISABLE_W3C_PROTOCOL: "${{ matrix.w3c && '0' || '1' }}" + DISABLE_HEADLESS: '0' # We always run headless tests on GitHub Actions SELENIUM_SERVER: "${{ matrix.selenium-server && '1' || '0' }}" run: | if [ "$BROWSER_NAME" = "chrome" ]; then EXCLUDE_GROUP+="exclude-chrome,"; fi diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 16e8613a4..4d7088d01 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -38,6 +38,7 @@ protected function setUp(): void $this->setUpSauceLabs(); } else { $browserName = getenv('BROWSER_NAME'); + $disableHeadless = filter_var(getenv('DISABLE_HEADLESS') ?: '', FILTER_VALIDATE_BOOLEAN); if ($browserName === '' || $browserName === false) { $this->markTestSkipped( 'To execute functional tests browser name must be provided in BROWSER_NAME environment variable' @@ -46,14 +47,18 @@ protected function setUp(): void if ($browserName === WebDriverBrowserType::CHROME) { $chromeOptions = new ChromeOptions(); + $chromeOptions->addArguments([ - '--headless=new', '--window-size=1024,768', '--no-sandbox', // workaround for https://github.com/SeleniumHQ/selenium/issues/4961 '--force-color-profile=srgb', '--disable-search-engine-choice-screen', ]); + if (!$disableHeadless) { + $chromeOptions->addArguments(['--headless=new']); + } + if (!static::isW3cProtocolBuild()) { $chromeOptions->setExperimentalOption('w3c', false); } @@ -61,7 +66,11 @@ protected function setUp(): void $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); } elseif ($browserName === WebDriverBrowserType::FIREFOX) { $firefoxOptions = new FirefoxOptions(); - $firefoxOptions->addArguments(['-headless']); + + if (!$disableHeadless) { + $firefoxOptions->addArguments(['-headless']); + } + $this->desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); } From 5a73bc6834430fe5314c632871995fb547ca88aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 21 Nov 2024 16:02:09 +0100 Subject: [PATCH 112/130] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9dc399ad..8035eaf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,18 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- PHP 8.4 deprecation notices, especially in nullable return types. +- Docs: Fix static return types in RemoteWebElement phpDoc. +- Tests: Disable chrome 127+ search engine pop-up in tests +- Tests: Enable Shadow DOM tests in Geckodriver + +### Added +- Tests: Allow running tests in headfull (not headless) mode using `DISABLE_HEADLESS` environment variable. + +### Changed +- Docs: Update selenium server host URL in example. + ## 1.15.1 - 2023-10-20 - Update `symfony/process` dependency to support upcoming Symfony 7. From 998e499b786805568deaf8cbf06f4044f05d91bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 21 Nov 2024 16:11:13 +0100 Subject: [PATCH 113/130] Release version 1.15.2 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8035eaf34..a468c26f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## 1.15.2 - 2024-11-21 ### Fixed -- PHP 8.4 deprecation notices, especially in nullable return types. +- PHP 8.4 deprecation notices, especially in nullable type-hints. - Docs: Fix static return types in RemoteWebElement phpDoc. - Tests: Disable chrome 127+ search engine pop-up in tests - Tests: Enable Shadow DOM tests in Geckodriver From dadaa12f76a322dae98b794b67507913616e7e5e Mon Sep 17 00:00:00 2001 From: Andrew Alyamovsky Date: Thu, 13 Feb 2025 16:52:32 +0100 Subject: [PATCH 114/130] Add Throwable extension to PhpWebDriverExceptionInterface --- lib/Exception/PhpWebDriverExceptionInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Exception/PhpWebDriverExceptionInterface.php b/lib/Exception/PhpWebDriverExceptionInterface.php index 04408a63b..58ebc6065 100644 --- a/lib/Exception/PhpWebDriverExceptionInterface.php +++ b/lib/Exception/PhpWebDriverExceptionInterface.php @@ -5,6 +5,6 @@ /** * Common interface to identify all exceptions thrown in php-webdriver (both those of WebDriver protocol and internal). */ -interface PhpWebDriverExceptionInterface +interface PhpWebDriverExceptionInterface extends \Throwable { } From 18a07d951b6c3eaa398d7149851b6f7115ad004a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 18:24:03 +0100 Subject: [PATCH 115/130] ci: Update markdown link check to ignore blocked urls --- .gitattributes | 1 + .github/workflows/tests.yaml | 2 +- mlc_config.json | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 mlc_config.json diff --git a/.gitattributes b/.gitattributes index 7800fad58..2fdc4ff88 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,3 +12,4 @@ /phpstan.neon export-ignore /.coveralls.yml export-ignore /logs export-ignore +/mlc_config.json export-ignore diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 870d1d0f2..06927b284 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gaurav-nelson/github-action-markdown-link-check@v1 + - uses: tcort/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' diff --git a/mlc_config.json b/mlc_config.json new file mode 100644 index 000000000..5d70a03e0 --- /dev/null +++ b/mlc_config.json @@ -0,0 +1,7 @@ +{ + "ignorePatterns": [ + { + "pattern": "^https://stackoverflow\\.com/questions/tagged/php\\+selenium-webdriver" + } + ] +} From a7827e372abf59d9ebbf7c480a8a6c6e41e8060e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 18:32:38 +0100 Subject: [PATCH 116/130] style: Normalize php extensions in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bd2184262..5aab53c63 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "facebook/webdriver": "*" }, "suggest": { - "ext-SimpleXML": "For Firefox profile creation" + "ext-simplexml": "For Firefox profile creation" }, "minimum-stability": "dev", "autoload": { From 1766e85cb6e22a12635badb22088f64efcb68f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 18:35:15 +0100 Subject: [PATCH 117/130] chore: Update to Selenium 4.38 in tests --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 06927b284..3784db7d6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -82,7 +82,7 @@ jobs: functional-tests: runs-on: ${{ matrix.os }} env: - SELENIUM_SERVER_DOWNLOAD_URL: https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.26.0/selenium-server-4.26.0.jar + SELENIUM_SERVER_DOWNLOAD_URL: https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.38.0/selenium-server-4.38.0.jar strategy: fail-fast: false From f37e67d3c743af97da3eeba5afadcdd2bc209818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 18:51:25 +0100 Subject: [PATCH 118/130] chore: Update args to properly set window size for new headless chrome See https://github.com/SeleniumHQ/selenium/issues/15827 https://chromium.googlesource.com/chromium/src/+/main/components/headless/screen_info/README.md --- tests/functional/WebDriverTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 4d7088d01..d7e729825 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -49,7 +49,7 @@ protected function setUp(): void $chromeOptions = new ChromeOptions(); $chromeOptions->addArguments([ - '--window-size=1024,768', + '--screen-info={1280x720}', '--no-sandbox', // workaround for https://github.com/SeleniumHQ/selenium/issues/4961 '--force-color-profile=srgb', '--disable-search-engine-choice-screen', From 487886747568132c625ae2f270a3e4606bb659f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 18:59:18 +0100 Subject: [PATCH 119/130] chore: Remove new flag from headless chrome, as it is already default since Chromedriver 132 --- tests/functional/Chrome/ChromeDriverTest.php | 2 +- tests/functional/WebDriverTestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index bdabb7f9e..407b65bd8 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -87,7 +87,7 @@ private function startChromeDriver($w3cDialect = true): void // Add --no-sandbox as a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 $chromeOptions = new ChromeOptions(); - $chromeOptions->addArguments(['--no-sandbox', '--headless=new', '--disable-search-engine-choice-screen']); + $chromeOptions->addArguments(['--no-sandbox', '--headless', '--disable-search-engine-choice-screen']); $chromeOptions->setExperimentalOption('w3c', $w3cDialect); $desiredCapabilities = DesiredCapabilities::chrome(); $desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index d7e729825..e6b97e1a9 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -56,7 +56,7 @@ protected function setUp(): void ]); if (!$disableHeadless) { - $chromeOptions->addArguments(['--headless=new']); + $chromeOptions->addArguments(['--headless']); } if (!static::isW3cProtocolBuild()) { From 61a9e9dc75b3ec3988e0b759dc96cf703716d7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 11 Dec 2025 19:23:14 +0100 Subject: [PATCH 120/130] test: Skip fullscren command test broken in Safari --- tests/functional/WebDriverWindowTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php index 93c441748..219126a7e 100644 --- a/tests/functional/WebDriverWindowTest.php +++ b/tests/functional/WebDriverWindowTest.php @@ -50,6 +50,7 @@ public function testShouldMaximizeWindow(): void /** * @group exclude-edge + * @group exclude-safari * @group exclude-saucelabs */ public function testShouldFullscreenWindow(): void From b51cbcb1f9531f60e7cc972decb56f1fdb265623 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:00:56 +0000 Subject: [PATCH 121/130] Bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs-lint.yml | 2 +- .github/workflows/docs-publish.yml | 2 +- .github/workflows/sauce-labs.yaml | 2 +- .github/workflows/tests.yaml | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docs-lint.yml b/.github/workflows/docs-lint.yml index fd1c41b4f..42baa0000 100644 --- a/.github/workflows/docs-lint.yml +++ b/.github/workflows/docs-lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index 287c1a75f..d0da12f8b 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 ssh-key: ${{ secrets.SSH_KEY_DEPLOY }} diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index 802f4ed8d..a90e17c90 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -29,7 +29,7 @@ jobs: name: ${{ matrix.name }} (${{ matrix.tunnel-id }}) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3784db7d6..3b0056b33 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -14,7 +14,7 @@ jobs: name: "Code style and static analysis" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -35,7 +35,7 @@ jobs: name: "Markdown link check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: tcort/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' @@ -52,7 +52,7 @@ jobs: - { php-version: '7.3', dependencies: '--prefer-lowest' } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -99,7 +99,7 @@ jobs: name: "Functional tests (${{ matrix.browser }}, Selenium server: ${{ matrix.selenium-server }}, W3C: ${{ matrix.w3c }})" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 From c5427c19fef20f49b041c52649d27dbff0549cc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:34:15 +0000 Subject: [PATCH 122/130] Bump actions/github-script from 7 to 8 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coveralls-workaround.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coveralls-workaround.yaml b/.github/workflows/coveralls-workaround.yaml index 5ca45b1fe..d6044861d 100644 --- a/.github/workflows/coveralls-workaround.yaml +++ b/.github/workflows/coveralls-workaround.yaml @@ -21,7 +21,7 @@ jobs: steps: # see https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - name: 'Download artifact' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ From d784f95f00a8abc1f4caad4257bac54b7d508664 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 04:10:59 +0000 Subject: [PATCH 123/130] chore(deps): bump actions/upload-artifact from 4 to 6 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3b0056b33..598264585 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -208,7 +208,7 @@ jobs: run: | mkdir -p ./data echo ${{ github.run_id }} > ./data/run_id - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 with: name: data path: data/ From 6a011bb4813db85173122fd5f43b26dd39a61eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 15 Dec 2025 16:22:50 +0100 Subject: [PATCH 124/130] chore(deps): bump saucelabs/sauce-connect-action from 2 to 3, upgrade saucelabs platform versions --- .github/workflows/sauce-labs.yaml | 16 +++++++++------- tests/functional/WebDriverTestCase.php | 16 ++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index a90e17c90..9ef25fbc7 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -23,11 +23,11 @@ jobs: w3c: [true] include: # Chrome 74 is the last version which doesn't use W3C WebDriver by default and rather use OSS protocol - - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 10", w3c: false, tunnel-id: "gh-1" } - - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-2" } - - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-3" } + - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 11", w3c: false, tunnel-name: "gh-1-chrome-oss-legacy" } + - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 11", tunnel-name: "gh-2-chrome-w3c" } + - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 11", tunnel-name: "gh-3-MicrosoftEdge" } - name: ${{ matrix.name }} (${{ matrix.tunnel-id }}) + name: ${{ matrix.name }} (${{ matrix.tunnel-name }}) steps: - uses: actions/checkout@v6 @@ -46,11 +46,13 @@ jobs: php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - name: Start Sauce Connect - uses: saucelabs/sauce-connect-action@v2 + uses: saucelabs/sauce-connect-action@v3 with: username: ${{ secrets.SAUCE_USERNAME }} accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} - tunnelIdentifier: ${{ matrix.tunnel-id }} + tunnelName: ${{ matrix.tunnel-name }} + proxyLocalhost: allow + region: 'us-west-1' - name: Run tests env: @@ -58,7 +60,7 @@ jobs: VERSION: ${{ matrix.VERSION }} PLATFORM: ${{ matrix.PLATFORM }} DISABLE_W3C_PROTOCOL: "${{ matrix.w3c && '0' || '1' }}" - SAUCE_TUNNEL_IDENTIFIER: ${{ matrix.tunnel-id }} + SAUCE_TUNNEL_NAME: ${{ matrix.tunnel-name }} run: | if [ -n "$SAUCELABS" ]; then EXCLUDE_GROUP+="exclude-saucelabs,"; fi if [ "$BROWSER_NAME" = "MicrosoftEdge" ]; then EXCLUDE_GROUP+="exclude-edge,"; fi diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index e6b97e1a9..db0fd8b0f 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -198,14 +198,14 @@ protected function setUpSauceLabs(): void if ($ciDetector->isCiDetected()) { $ci = $ciDetector->detect(); if (!empty($ci->getBuildNumber())) { - // SAUCE_TUNNEL_IDENTIFIER appended as a workaround for GH actions not having environment value + // SAUCE_TUNNEL_NAME appended as a workaround for GH actions not having environment value // to distinguish runs of the matrix - $build = $ci->getBuildNumber() . '.' . getenv('SAUCE_TUNNEL_IDENTIFIER'); + $build = $ci->getBuildNumber() . '.' . getenv('SAUCE_TUNNEL_NAME'); } } - if (getenv('SAUCE_TUNNEL_IDENTIFIER')) { - $tunnelIdentifier = getenv('SAUCE_TUNNEL_IDENTIFIER'); + if (getenv('SAUCE_TUNNEL_NAME')) { + $tunnelName = getenv('SAUCE_TUNNEL_NAME'); } if (static::isW3cProtocolBuild()) { @@ -216,16 +216,16 @@ protected function setUpSauceLabs(): void if (isset($build)) { $sauceOptions['build'] = $build; } - if (isset($tunnelIdentifier)) { - $sauceOptions['tunnelIdentifier'] = $tunnelIdentifier; + if (isset($tunnelName)) { + $sauceOptions['tunnelName'] = $tunnelName; } $this->desiredCapabilities->setCapability('sauce:options', (object) $sauceOptions); } else { $this->desiredCapabilities->setCapability('name', $name); $this->desiredCapabilities->setCapability('tags', $tags); - if (isset($tunnelIdentifier)) { - $this->desiredCapabilities->setCapability('tunnel-identifier', $tunnelIdentifier); + if (isset($tunnelName)) { + $this->desiredCapabilities->setCapability('tunnel-identifier', $tunnelName); } if (isset($build)) { $this->desiredCapabilities->setCapability('build', $build); From f68a1c9c2b823deb44c027cc4e5b3ad23a94e58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 16 Dec 2025 13:13:23 +0100 Subject: [PATCH 125/130] ci: Reenable unintentionally skipped saucelabs test --- .github/workflows/sauce-labs.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml index 9ef25fbc7..812fbfe25 100644 --- a/.github/workflows/sauce-labs.yaml +++ b/.github/workflows/sauce-labs.yaml @@ -20,12 +20,11 @@ jobs: strategy: fail-fast: false matrix: - w3c: [true] include: # Chrome 74 is the last version which doesn't use W3C WebDriver by default and rather use OSS protocol - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 11", w3c: false, tunnel-name: "gh-1-chrome-oss-legacy" } - - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 11", tunnel-name: "gh-2-chrome-w3c" } - - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 11", tunnel-name: "gh-3-MicrosoftEdge" } + - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 11", w3c: true, tunnel-name: "gh-2-chrome-w3c" } + - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 11", w3c: true, tunnel-name: "gh-3-MicrosoftEdge" } name: ${{ matrix.name }} (${{ matrix.tunnel-name }}) steps: From d32a252c07586021a54c486d297241a92a406137 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Mon, 8 Dec 2025 09:14:20 +0100 Subject: [PATCH 126/130] ci: add tests for PHP 8.5 --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 598264585..d215e92ce 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -46,7 +46,7 @@ jobs: strategy: matrix: - php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] + php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5'] dependencies: [''] include: - { php-version: '7.3', dependencies: '--prefer-lowest' } From 0e5cbf07609d3ad441b90e6358ef6f7daa5f94e8 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 15 Dec 2025 16:01:18 +0100 Subject: [PATCH 127/130] chore(deps): Allow Symfony 8 --- composer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5aab53c63..f44663100 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "ext-json": "*", "ext-zip": "*", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^5.0 || ^6.0 || ^7.0" + "symfony/process": "^5.0 || ^6.0 || ^7.0 || ^8.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.20.0", @@ -27,7 +27,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0" + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0 || ^8.0" }, "replace": { "facebook/webdriver": "*" @@ -36,6 +36,7 @@ "ext-simplexml": "For Firefox profile creation" }, "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "Facebook\\WebDriver\\": "lib/" From c65b9fc07713641e79c61e81838b3119dc22b7dd Mon Sep 17 00:00:00 2001 From: Ezra Date: Tue, 15 Jul 2025 17:03:54 +1200 Subject: [PATCH 128/130] Fix incorrect link to driver capabilities docs --- lib/Chrome/ChromeOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index 2e2f6d2c2..daea44c67 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -9,7 +9,7 @@ /** * The class manages the capabilities in ChromeDriver. * - * @see https://sites.google.com/a/chromium.org/chromedriver/capabilities + * @see https://sites.google.com/chromium.org/driver/capabilities */ class ChromeOptions implements JsonSerializable { From e5bf60f7a42c65a86fbfa3e2830fe99055540337 Mon Sep 17 00:00:00 2001 From: Jose' Pedro Saraiva Date: Mon, 2 Jun 2025 11:29:22 +0200 Subject: [PATCH 129/130] Fix file upload endpoint for selenium 4.x --- lib/Remote/HttpCommandExecutor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 96b0ccc70..3e3ef1719 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -175,6 +175,8 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_WINDOW_SIZE => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], DriverCommand::SET_WINDOW_POSITION => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], + // Selenium extension of W3C protocol + DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/se/file'], ]; /** * @var string From a7f16c101fd9c85106ddfb0b73c161979e75140b Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 17 Dec 2025 14:10:05 +0100 Subject: [PATCH 130/130] Clarify and fix timeout value type (#1120) * Clarify and fix timeout value type --- lib/WebDriverTimeouts.php | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/WebDriverTimeouts.php b/lib/WebDriverTimeouts.php index add6a0900..73a5cddf8 100644 --- a/lib/WebDriverTimeouts.php +++ b/lib/WebDriverTimeouts.php @@ -28,7 +28,7 @@ public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) /** * Specify the amount of time the driver should wait when searching for an element if it is not immediately present. * - * @param int $seconds Wait time in second. + * @param null|int|float $seconds Wait time in seconds. * @return WebDriverTimeouts The current instance. */ public function implicitlyWait($seconds) @@ -36,15 +36,18 @@ public function implicitlyWait($seconds) if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, - ['implicit' => $seconds * 1000] + ['implicit' => $seconds === null ? null : floor($seconds * 1000)] ); return $this; } + if ($seconds === null) { + throw new \InvalidArgumentException('JsonWire Protocol implicit-wait-timeout value cannot be null'); + } $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, - ['ms' => $seconds * 1000] + ['ms' => floor($seconds * 1000)] ); return $this; @@ -53,7 +56,7 @@ public function implicitlyWait($seconds) /** * Set the amount of time to wait for an asynchronous script to finish execution before throwing an error. * - * @param int $seconds Wait time in second. + * @param null|int|float $seconds Wait time in seconds. * @return WebDriverTimeouts The current instance. */ public function setScriptTimeout($seconds) @@ -61,15 +64,18 @@ public function setScriptTimeout($seconds) if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, - ['script' => $seconds * 1000] + ['script' => $seconds === null ? null : floor($seconds * 1000)] ); return $this; } + if ($seconds === null) { + throw new \InvalidArgumentException('JsonWire Protocol script-timeout value cannot be null'); + } $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, - ['ms' => $seconds * 1000] + ['ms' => floor($seconds * 1000)] ); return $this; @@ -78,7 +84,7 @@ public function setScriptTimeout($seconds) /** * Set the amount of time to wait for a page load to complete before throwing an error. * - * @param int $seconds Wait time in second. + * @param null|int|float $seconds Wait time in seconds. * @return WebDriverTimeouts The current instance. */ public function pageLoadTimeout($seconds) @@ -86,15 +92,18 @@ public function pageLoadTimeout($seconds) if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, - ['pageLoad' => $seconds * 1000] + ['pageLoad' => $seconds === null ? null : floor($seconds * 1000)] ); return $this; } + if ($seconds === null) { + throw new \InvalidArgumentException('JsonWire Protocol page-load-timeout value cannot be null'); + } $this->executor->execute(DriverCommand::SET_TIMEOUT, [ 'type' => 'page load', - 'ms' => $seconds * 1000, + 'ms' => floor($seconds * 1000), ]); return $this;