From a806290e992f320a92b693d3ba069e7b23541409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 24 Jan 2020 15:52:33 +0100 Subject: [PATCH 001/265] Change community branch to master #739 --- .github/CONTRIBUTING.md | 4 ++-- README.md | 2 +- composer.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b7fa587cc..2fdbb677f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,7 +2,7 @@ We love to have your help to make php-webdriver better! -Feel free to open an [issue](https://github.com/facebook/php-webdriver/issues) if you run into any problem, or +Feel free to open an [issue](https://github.com/php-webdriver/php-webdriver/issues) if you run into any problem, or send a pull request (see bellow) with your contribution. ## Code of Conduct @@ -14,7 +14,7 @@ The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) 2. Implement your code changes into separate branch 3. Make sure all PHPUnit tests passes and code-style matches PSR-2 (see below). We also have Travis CI build which will automatically run tests on your pull request. 4. When implementing notable change, fix or a new feature, add record to Unreleased section of [CHANGELOG.md](CHANGELOG.md) -5. Submit your [pull request](https://github.com/facebook/php-webdriver/pulls) against community branch +5. Submit your [pull request](https://github.com/php-webdriver/php-webdriver/pulls) against `master` branch When you are going to contribute, please keep in mind that this webdriver client aims to be as close as possible to other languages Java/Ruby/Python/C#. FYI, here is the overview of [the official Java API](http://seleniumhq.github.io/selenium/docs/api/java/) diff --git a/README.md b/README.md index e05ff8526..199d210e5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # php-webdriver – Selenium WebDriver bindings for PHP [![Latest Stable Version](https://img.shields.io/packagist/v/php-webdriver/webdriver.svg?style=flat-square)](https://packagist.org/packages/php-webdriver/webdriver) -[![Travis Build](https://img.shields.io/travis/php-webdriver/php-webdriver/community.svg?style=flat-square)](https://travis-ci.com/php-webdriver/php-webdriver) +[![Travis Build](https://img.shields.io/travis/php-webdriver/php-webdriver/master.svg?style=flat-square)](https://travis-ci.com/php-webdriver/php-webdriver) [![Sauce Test Status](https://saucelabs.com/buildstatus/php-webdriver)](https://saucelabs.com/u/php-webdriver) [![Total Downloads](https://img.shields.io/packagist/dd/php-webdriver/webdriver.svg?style=flat-square)](https://packagist.org/packages/php-webdriver/webdriver) diff --git a/composer.json b/composer.json index f370c2fb0..aa8ab5a1d 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,7 @@ }, "extra": { "branch-alias": { - "dev-community": "1.8.x-dev" + "dev-master": "1.8.x-dev" } } } From 95becf6a53f921b1ec2ff2f85a3b90405968b907 Mon Sep 17 00:00:00 2001 From: Maxim Leontyev Date: Fri, 26 Oct 2018 14:52:28 +0300 Subject: [PATCH 002/265] Support of custom http WebDriver commands --- lib/Remote/DriverCommand.php | 2 + lib/Remote/HttpCommandExecutor.php | 50 +++++++++++++----- lib/Remote/RemoteWebDriver.php | 25 +++++++++ lib/Remote/WebDriverCommand.php | 52 +++++++++++++++++++ tests/unit/Remote/HttpCommandExecutorTest.php | 33 +++++++++++- tests/unit/Remote/WebDriverCommandTest.php | 24 +++++++++ 6 files changed, 171 insertions(+), 15 deletions(-) diff --git a/lib/Remote/DriverCommand.php b/lib/Remote/DriverCommand.php index 34daf3e42..3f1e8392d 100644 --- a/lib/Remote/DriverCommand.php +++ b/lib/Remote/DriverCommand.php @@ -133,6 +133,8 @@ class DriverCommand // Mobile API const GET_NETWORK_CONNECTION = 'getNetworkConnection'; const SET_NETWORK_CONNECTION = 'setNetworkConnection'; + // Custom command + const CUSTOM_COMMAND = 'customCommand'; // W3C specific const ACTIONS = 'actions'; diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index dcaa1374c..9510ab491 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -130,6 +130,7 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'], DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], + DriverCommand::CUSTOM_COMMAND => false, ]; /** * @var array Will be merged with $commands @@ -262,21 +263,10 @@ public function setRequestTimeout($timeout_in_ms) */ public function execute(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.'); - } - } + $http_options = $this->getCommandHttpOptions($command); + $http_method = $http_options['method']; + $url = $http_options['url']; - if ($this->isW3cCompliant) { - $raw = self::$w3cCompliantCommands[$command->getName()]; - } else { - $raw = self::$commands[$command->getName()]; - } - - $http_method = $raw['method']; - $url = $raw['url']; $url = str_replace(':sessionId', $command->getSessionID(), $url); $params = $command->getParameters(); foreach ($params as $name => $value) { @@ -400,4 +390,36 @@ public function getAddressOfRemoteServer() { return $this->url; } + + /** + * @return array + */ + 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.'); + } + } + + if ($this->isW3cCompliant) { + $raw = self::$w3cCompliantCommands[$command->getName()]; + } else { + $raw = self::$commands[$command->getName()]; + } + + if ($raw === false) { + $url = $command->getCustomUrl(); + $method = $command->getCustomMethod(); + } else { + $url = $raw['url']; + $method = $raw['method']; + } + + return [ + 'url' => $url, + 'method' => $method, + ]; + } } diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 5cac97247..358e31405 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -589,6 +589,31 @@ public function execute($command_name, $params = []) return null; } + /** + * @param string $url + * @param string $method + * @param array $params + * @return mixed|null + */ + public function executeCustomCommand($url, $method = 'GET', $params = []) + { + $command = new WebDriverCommand( + $this->sessionID, + DriverCommand::CUSTOM_COMMAND, + $params + ); + + $command->setCustomRequestParameters($url, $method); + + if ($this->executor) { + $response = $this->executor->execute($command); + + return $response->getValue(); + } + + return null; + } + /** * @internal * @return bool diff --git a/lib/Remote/WebDriverCommand.php b/lib/Remote/WebDriverCommand.php index 9b893697a..e1a693c7f 100644 --- a/lib/Remote/WebDriverCommand.php +++ b/lib/Remote/WebDriverCommand.php @@ -2,14 +2,22 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\WebDriverException; + class WebDriverCommand { + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; /** @var string */ private $sessionID; /** @var string */ private $name; /** @var array */ private $parameters; + /** @var string */ + private $customUrl; + /** @var string */ + private $customMethod; /** * @param string $session_id @@ -47,4 +55,48 @@ public function getParameters() { return $this->parameters; } + + /** + * @param string $custom_url + * @param string $custom_method + * @throws WebDriverException + */ + public function setCustomRequestParameters($custom_url, $custom_method) + { + if (!in_array($custom_method, [static::METHOD_GET, static::METHOD_POST])) { + throw new WebDriverException('Invalid custom method'); + } + $this->customMethod = $custom_method; + + if (mb_substr($custom_url, 0, 1) !== '/') { + throw new WebDriverException('Custom url should start with /'); + } + $this->customUrl = $custom_url; + } + + /** + * @throws WebDriverException + * @return string + */ + public function getCustomUrl() + { + if ($this->customUrl === null) { + throw new WebDriverException('Custom url is not set'); + } + + return $this->customUrl; + } + + /** + * @throws WebDriverException + * @return string + */ + public function getCustomMethod() + { + if ($this->customUrl === null) { + throw new WebDriverException('Custom url is not set'); + } + + return $this->customMethod; + } } diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index af7670b06..43c5859c9 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -24,16 +24,25 @@ protected function setUp() * @param bool $shouldResetExpectHeader * @param string $expectedUrl * @param string $expectedPostData + * @param null|array $customCommandParameters */ public function testShouldSendRequestToAssembledUrl( $command, array $params, $shouldResetExpectHeader, $expectedUrl, - $expectedPostData + $expectedPostData, + $customCommandParameters = null ) { $command = new WebDriverCommand('foo-123', $command, $params); + if ($customCommandParameters !== null) { + $command->setCustomRequestParameters( + $customCommandParameters['url'], + $customCommandParameters['method'] + ); + } + $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); $curlSetoptMock->expects($this->at(0)) ->with($this->anything(), CURLOPT_URL, $expectedUrl); @@ -106,6 +115,28 @@ public function provideCommand() 'http://localhost:4444/sessions', null, ], + 'Custom GET command' => [ + DriverCommand::CUSTOM_COMMAND, + [':someParameter' => 'someValue'], + false, + 'http://localhost:4444/session/foo-123/custom-command/someValue', + null, + [ + 'url' => '/session/:sessionId/custom-command/:someParameter', + 'method' => 'GET', + ], + ], + 'Custom POST command' => [ + DriverCommand::CUSTOM_COMMAND, + ['someParameter' => 'someValue'], + true, + 'http://localhost:4444/session/foo-123/custom-post-command/', + '{"someParameter":"someValue"}', + [ + 'url' => '/session/:sessionId/custom-post-command/', + 'method' => 'POST', + ], + ], ]; } } diff --git a/tests/unit/Remote/WebDriverCommandTest.php b/tests/unit/Remote/WebDriverCommandTest.php index 0bf6efb20..39177dca1 100644 --- a/tests/unit/Remote/WebDriverCommandTest.php +++ b/tests/unit/Remote/WebDriverCommandTest.php @@ -2,6 +2,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Exception\WebDriverException; use PHPUnit\Framework\TestCase; class WebDriverCommandTest extends TestCase @@ -14,4 +15,27 @@ public function testShouldSetOptionsUsingConstructor() $this->assertSame('bar-baz-name', $command->getName()); $this->assertSame(['foo' => 'bar'], $command->getParameters()); } + + public function testCustomCommandInit() + { + $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); + $command->setCustomRequestParameters('/some-url', 'POST'); + + $this->assertSame('/some-url', $command->getCustomUrl()); + $this->assertSame('POST', $command->getCustomMethod()); + } + + public function testCustomCommandInvalidUrlExceptionInit() + { + $this->expectException(WebDriverException::class); + $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); + $command->setCustomRequestParameters('url-without-leading-slash', 'POST'); + } + + public function testCustomCommandInvalidMethodExceptionInit() + { + $this->expectException(WebDriverException::class); + $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); + $command->setCustomRequestParameters('/some-url', 'invalid-method'); + } } From 5e0d2071214796a76b2aadf0b9efe06e57bc1f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 22 Jan 2020 14:05:36 +0100 Subject: [PATCH 003/265] Refactor custom commands --- lib/Remote/CustomWebDriverCommand.php | 82 +++++++++++++++++++ lib/Remote/HttpCommandExecutor.php | 4 +- lib/Remote/RemoteWebDriver.php | 11 ++- lib/Remote/WebDriverCommand.php | 58 +------------ .../Remote/CustomWebDriverCommandTest.php | 38 +++++++++ tests/unit/Remote/HttpCommandExecutorTest.php | 76 +++++++---------- tests/unit/Remote/WebDriverCommandTest.php | 24 ------ 7 files changed, 161 insertions(+), 132 deletions(-) create mode 100644 lib/Remote/CustomWebDriverCommand.php create mode 100644 tests/unit/Remote/CustomWebDriverCommandTest.php diff --git a/lib/Remote/CustomWebDriverCommand.php b/lib/Remote/CustomWebDriverCommand.php new file mode 100644 index 000000000..157902199 --- /dev/null +++ b/lib/Remote/CustomWebDriverCommand.php @@ -0,0 +1,82 @@ +setCustomRequestParameters($url, $method); + + parent::__construct($session_id, DriverCommand::CUSTOM_COMMAND, $parameters); + } + + /** + * @throws WebDriverException + * @return string + */ + public function getCustomUrl() + { + if ($this->customUrl === null) { + throw new WebDriverException('URL of custom command is not set'); + } + + return $this->customUrl; + } + + /** + * @throws WebDriverException + * @return string + */ + public function getCustomMethod() + { + if ($this->customMethod === null) { + throw new WebDriverException('Method of custom command is not set'); + } + + return $this->customMethod; + } + + /** + * @param string $custom_url + * @param string $custom_method + * @throws WebDriverException + */ + protected function setCustomRequestParameters($custom_url, $custom_method) + { + $allowedMethods = [static::METHOD_GET, static::METHOD_POST]; + if (!in_array($custom_method, $allowedMethods, true)) { + throw new WebDriverException( + sprintf( + 'Invalid custom method "%s", must be one of [%s]', + $custom_method, + implode(', ', $allowedMethods) + ) + ); + } + $this->customMethod = $custom_method; + + if (mb_strpos($custom_url, '/') !== 0) { + throw new WebDriverException( + sprintf('URL of custom command has to start with / but is "%s"', $custom_url) + ); + } + $this->customUrl = $custom_url; + } +} diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 9510ab491..6bc942adf 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -130,7 +130,7 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'], DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], - DriverCommand::CUSTOM_COMMAND => false, + DriverCommand::CUSTOM_COMMAND => [], ]; /** * @var array Will be merged with $commands @@ -409,7 +409,7 @@ protected function getCommandHttpOptions(WebDriverCommand $command) $raw = self::$commands[$command->getName()]; } - if ($raw === false) { + if ($command instanceof CustomWebDriverCommand) { $url = $command->getCustomUrl(); $method = $command->getCustomMethod(); } else { diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 358e31405..f157fdf06 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -590,21 +590,20 @@ public function execute($command_name, $params = []) } /** - * @param string $url + * @param string $endpointUrl * @param string $method * @param array $params * @return mixed|null */ - public function executeCustomCommand($url, $method = 'GET', $params = []) + public function executeCustomCommand($endpointUrl, $method = 'GET', $params = []) { - $command = new WebDriverCommand( + $command = new CustomWebDriverCommand( $this->sessionID, - DriverCommand::CUSTOM_COMMAND, + $endpointUrl, + $method, $params ); - $command->setCustomRequestParameters($url, $method); - if ($this->executor) { $response = $this->executor->execute($command); diff --git a/lib/Remote/WebDriverCommand.php b/lib/Remote/WebDriverCommand.php index e1a693c7f..e21fbdaae 100644 --- a/lib/Remote/WebDriverCommand.php +++ b/lib/Remote/WebDriverCommand.php @@ -2,22 +2,14 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\WebDriverException; - class WebDriverCommand { - const METHOD_GET = 'GET'; - const METHOD_POST = 'POST'; /** @var string */ - private $sessionID; + protected $sessionID; /** @var string */ - private $name; + protected $name; /** @var array */ - private $parameters; - /** @var string */ - private $customUrl; - /** @var string */ - private $customMethod; + protected $parameters; /** * @param string $session_id @@ -55,48 +47,4 @@ public function getParameters() { return $this->parameters; } - - /** - * @param string $custom_url - * @param string $custom_method - * @throws WebDriverException - */ - public function setCustomRequestParameters($custom_url, $custom_method) - { - if (!in_array($custom_method, [static::METHOD_GET, static::METHOD_POST])) { - throw new WebDriverException('Invalid custom method'); - } - $this->customMethod = $custom_method; - - if (mb_substr($custom_url, 0, 1) !== '/') { - throw new WebDriverException('Custom url should start with /'); - } - $this->customUrl = $custom_url; - } - - /** - * @throws WebDriverException - * @return string - */ - public function getCustomUrl() - { - if ($this->customUrl === null) { - throw new WebDriverException('Custom url is not set'); - } - - return $this->customUrl; - } - - /** - * @throws WebDriverException - * @return string - */ - public function getCustomMethod() - { - if ($this->customUrl === null) { - throw new WebDriverException('Custom url is not set'); - } - - return $this->customMethod; - } } diff --git a/tests/unit/Remote/CustomWebDriverCommandTest.php b/tests/unit/Remote/CustomWebDriverCommandTest.php new file mode 100644 index 000000000..b56bc4aea --- /dev/null +++ b/tests/unit/Remote/CustomWebDriverCommandTest.php @@ -0,0 +1,38 @@ + 'bar'] + ); + + $this->assertSame('/some-url', $command->getCustomUrl()); + $this->assertSame('POST', $command->getCustomMethod()); + } + + public function testCustomCommandInvalidUrlExceptionInit() + { + $this->expectException(WebDriverException::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', []); + } + + public function testCustomCommandInvalidMethodExceptionInit() + { + $this->expectException(WebDriverException::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/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 43c5859c9..d76780463 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -19,30 +19,17 @@ protected function setUp() /** * @dataProvider provideCommand - * @param int $command - * @param array $params + * @param WebDriverCommand $command * @param bool $shouldResetExpectHeader * @param string $expectedUrl - * @param string $expectedPostData - * @param null|array $customCommandParameters + * @param string|null $expectedPostData */ public function testShouldSendRequestToAssembledUrl( - $command, - array $params, + WebDriverCommand $command, $shouldResetExpectHeader, $expectedUrl, - $expectedPostData, - $customCommandParameters = null + $expectedPostData ) { - $command = new WebDriverCommand('foo-123', $command, $params); - - if ($customCommandParameters !== null) { - $command->setCustomRequestParameters( - $customCommandParameters['url'], - $customCommandParameters['method'] - ); - } - $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); $curlSetoptMock->expects($this->at(0)) ->with($this->anything(), CURLOPT_URL, $expectedUrl); @@ -81,61 +68,60 @@ public function provideCommand() { return [ 'POST command having :id placeholder in url' => [ - DriverCommand::SEND_KEYS_TO_ELEMENT, - ['value' => 'submitted-value', ':id' => '1337'], + new WebDriverCommand( + 'fooSession', + DriverCommand::SEND_KEYS_TO_ELEMENT, + ['value' => 'submitted-value', ':id' => '1337'] + ), true, - 'http://localhost:4444/session/foo-123/element/1337/value', + 'http://localhost:4444/session/fooSession/element/1337/value', '{"value":"submitted-value"}', ], 'POST command without :id placeholder in url' => [ - DriverCommand::TOUCH_UP, - ['x' => 3, 'y' => 6], + new WebDriverCommand('fooSession', DriverCommand::TOUCH_UP, ['x' => 3, 'y' => 6]), true, - 'http://localhost:4444/session/foo-123/touch/up', + 'http://localhost:4444/session/fooSession/touch/up', '{"x":3,"y":6}', ], 'Extra useless placeholder parameter should be removed' => [ - DriverCommand::TOUCH_UP, - ['x' => 3, 'y' => 6, ':useless' => 'foo'], + new WebDriverCommand('fooSession', DriverCommand::TOUCH_UP, ['x' => 3, 'y' => 6, ':useless' => 'foo']), true, - 'http://localhost:4444/session/foo-123/touch/up', + 'http://localhost:4444/session/fooSession/touch/up', '{"x":3,"y":6}', ], 'DELETE command' => [ - DriverCommand::DELETE_COOKIE, - [':name' => 'cookie-name'], + new WebDriverCommand('fooSession', DriverCommand::DELETE_COOKIE, [':name' => 'cookie-name']), false, - 'http://localhost:4444/session/foo-123/cookie/cookie-name', + 'http://localhost:4444/session/fooSession/cookie/cookie-name', null, ], 'GET command without session in URL' => [ - DriverCommand::GET_ALL_SESSIONS, - [], + new WebDriverCommand('fooSession', DriverCommand::GET_ALL_SESSIONS, []), false, 'http://localhost:4444/sessions', null, ], 'Custom GET command' => [ - DriverCommand::CUSTOM_COMMAND, - [':someParameter' => 'someValue'], + new CustomWebDriverCommand( + 'fooSession', + '/session/:sessionId/custom-command/:someParameter', + 'GET', + [':someParameter' => 'someValue'] + ), false, - 'http://localhost:4444/session/foo-123/custom-command/someValue', + 'http://localhost:4444/session/fooSession/custom-command/someValue', null, - [ - 'url' => '/session/:sessionId/custom-command/:someParameter', - 'method' => 'GET', - ], ], 'Custom POST command' => [ - DriverCommand::CUSTOM_COMMAND, - ['someParameter' => 'someValue'], + new CustomWebDriverCommand( + 'fooSession', + '/session/:sessionId/custom-post-command/', + 'POST', + ['someParameter' => 'someValue'] + ), true, - 'http://localhost:4444/session/foo-123/custom-post-command/', + 'http://localhost:4444/session/fooSession/custom-post-command/', '{"someParameter":"someValue"}', - [ - 'url' => '/session/:sessionId/custom-post-command/', - 'method' => 'POST', - ], ], ]; } diff --git a/tests/unit/Remote/WebDriverCommandTest.php b/tests/unit/Remote/WebDriverCommandTest.php index 39177dca1..0bf6efb20 100644 --- a/tests/unit/Remote/WebDriverCommandTest.php +++ b/tests/unit/Remote/WebDriverCommandTest.php @@ -2,7 +2,6 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\WebDriverException; use PHPUnit\Framework\TestCase; class WebDriverCommandTest extends TestCase @@ -15,27 +14,4 @@ public function testShouldSetOptionsUsingConstructor() $this->assertSame('bar-baz-name', $command->getName()); $this->assertSame(['foo' => 'bar'], $command->getParameters()); } - - public function testCustomCommandInit() - { - $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); - $command->setCustomRequestParameters('/some-url', 'POST'); - - $this->assertSame('/some-url', $command->getCustomUrl()); - $this->assertSame('POST', $command->getCustomMethod()); - } - - public function testCustomCommandInvalidUrlExceptionInit() - { - $this->expectException(WebDriverException::class); - $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); - $command->setCustomRequestParameters('url-without-leading-slash', 'POST'); - } - - public function testCustomCommandInvalidMethodExceptionInit() - { - $this->expectException(WebDriverException::class); - $command = new WebDriverCommand('session-id-123', 'customCommand', ['foo' => 'bar']); - $command->setCustomRequestParameters('/some-url', 'invalid-method'); - } } From f5a1b0d998b4188d2bb2d0e362768b21d8c346d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 3 Feb 2020 12:30:41 +0100 Subject: [PATCH 004/265] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9317baab..645ffd936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Experimental W3C WebDriver protocol support. The protocol will be used automatically when remote end (like Geckodriver, newer Chromedriver etc.) supports it. - `getStatus()` method of `RemoteWebDriver` to get information about remote-end readiness to create new sessions. - `takeElementScreenshot()` method of `RemoteWebElement` to do the obvious - take screenshot of the particular element. +- Support for sending custom commands via `executeCustomCommand()`. See [wiki](https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands) for more information. ### Changed - The repository was migrated to [`php-webdriver/php-webdriver`](https://github.com/php-webdriver/php-webdriver/). From bb8d3707b06b3313fac511b53dce2fbaa14382af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 16 Dec 2019 16:29:14 +0100 Subject: [PATCH 005/265] Update example.php to use Wikipedia website --- composer.json | 2 +- example.php | 71 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index aa8ab5a1d..77df01f21 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" ], "analyze": [ - "vendor/bin/parallel-lint -j 10 ./lib ./tests", + "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" ] }, diff --git a/example.php b/example.php index ab7464a3a..be2c5eaa3 100644 --- a/example.php +++ b/example.php @@ -1,7 +1,7 @@ get('https://www.seleniumhq.org/'); +$driver = RemoteWebDriver::create($host, $capabilities); -// adding cookie -$driver->manage()->deleteAllCookies(); +// navigate to Selenium page on Wikipedia +$driver->get('https://en.wikipedia.org/wiki/Selenium_(software)'); -$cookie = new Cookie('cookie_name', 'cookie_value'); -$driver->manage()->addCookie($cookie); +// write 'PHP' in the search box +$driver->findElement(WebDriverBy::id('searchInput')) // find search input element + ->sendKeys('PHP') // fill the search box + ->submit(); // submit the whole form -$cookies = $driver->manage()->getCookies(); -print_r($cookies); +// wait until 'PHP' is shown in the page heading element +$driver->wait()->until( + WebDriverExpectedCondition::elementTextContains(WebDriverBy::id('firstHeading'), 'PHP') +); + +// print title of the current page to output +echo "The title is '" . $driver->getTitle() . "'\n"; + +// print URL of current page to output +echo "The current URL is '" . $driver->getCurrentURL() . "'\n"; -// click the link 'About' -$link = $driver->findElement( - WebDriverBy::id('menu_about') +// find element of 'History' item in menu +$historyButton = $driver->findElement( + WebDriverBy::cssSelector('#ca-history a') ); -$link->click(); -// wait until the page is loaded +// read text of the element and print it to output +echo "About to click to button with text: '" . $historyButton->getText() . "'\n"; + +// click the element to navigate to revision history page +$historyButton->click(); + +// wait until the target page is loaded $driver->wait()->until( - WebDriverExpectedCondition::titleContains('About') + WebDriverExpectedCondition::titleContains('Revision history') ); // print the title of the current page echo "The title is '" . $driver->getTitle() . "'\n"; // print the URI of the current page + echo "The current URI is '" . $driver->getCurrentURL() . "'\n"; -// write 'php' in the search box -$driver->findElement(WebDriverBy::id('q')) - ->sendKeys('php') // fill the search box - ->submit(); // submit the whole form +// delete all cookies +$driver->manage()->deleteAllCookies(); + +// add new cookie +$cookie = new Cookie('cookie_set_by_selenium', 'cookie_value'); +$driver->manage()->addCookie($cookie); + +// dump current cookies to output +$cookies = $driver->manage()->getCookies(); +print_r($cookies); -// wait at most 10 seconds until at least one result is shown -$driver->wait(10)->until( - WebDriverExpectedCondition::presenceOfAllElementsLocatedBy( - WebDriverBy::className('gsc-result') - ) -); // close the browser $driver->quit(); From 8994d572eb7142ab2e2aefcbaa007e2e82e0f515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 16 Dec 2019 16:29:46 +0100 Subject: [PATCH 006/265] Baby yoda does not have a place here --- lib/Remote/RemoteWebElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index cb0caee46..fff018e41 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -392,7 +392,7 @@ public function submit() // cannot call the submit method directly in case an input of this form is named "submit" 'script' => sprintf( 'return Object.getPrototypeOf(%1$s).submit.call(%1$s);', - 'form' === $this->getTagName() ? 'arguments[0]' : 'arguments[0].form' + $this->getTagName() === 'form' ? 'arguments[0]' : 'arguments[0].form' ), 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], ]); From a2e7a9cdd4ff7ddf061ba483bded1d73ee9b59d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 3 Feb 2020 17:37:50 +0100 Subject: [PATCH 007/265] Mention Symfony Panther and Laravel Dusk in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 199d210e5..c9f4550cc 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ You may also want to check out the Selenium [docs](http://docs.seleniumhq.org/do To take advantage of automatized testing you may want to integrate php-webdriver to your testing framework. 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 From c17361f92ccbc389ecafcfe55179696c7a761ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 5 Feb 2020 23:12:38 +0100 Subject: [PATCH 008/265] Exclude fullscreen test from chrome Because of Chrome 80 regression: https://bugs.chromium.org/p/chromium/issues/detail?id=1049336 --- tests/functional/WebDriverWindowTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php index 6b2671b81..ba0b45187 100644 --- a/tests/functional/WebDriverWindowTest.php +++ b/tests/functional/WebDriverWindowTest.php @@ -50,6 +50,8 @@ public function testShouldMaximizeWindow() /** * @group exclude-edge + * @group exclude-chrome + * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1049336 */ public function testShouldFullscreenWindow() { From 29f054fefd7cc4d776b3370cb43c95094689b0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Feb 2020 16:38:35 +0100 Subject: [PATCH 009/265] Extend and improve readme --- README.md | 121 +++++++++++++++++++++++++++++++++++++++++----------- example.php | 1 - 2 files changed, 96 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index c9f4550cc..25d7d29de 100644 --- a/README.md +++ b/README.md @@ -36,66 +36,137 @@ Then install the library: ## Getting started -### Start Server +### 1. Start server (aka. remote end) -The required server is the `selenium-server-standalone-#.jar` file provided here: http://selenium-release.storage.googleapis.com/index.html +To control a browser, you need to start a *remote end* (server), which will listen to the commands sent +from this library and will execute them in the respective browser. -Download and run the server by **replacing #** with the current server version. Keep in mind **you must have Java 8+ installed to run this command**. +This could be Selenium standalone server, but for local development, you can send them directly to so-called "browser driver" like Chromedriver or Geckodriver. - java -jar selenium-server-standalone-#.jar +#### a) Chromedriver -### Create a Browser Session +Install the latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads). +Make sure to have a compatible version of Chromedriver and Chrome! -When creating a browser session, be sure to pass the url of your running server. +Run `chromedriver` binary, you can pass `port` argument, so that it listens on port 4444: -```php -// This would be the url of the host running the server-standalone.jar -$host = 'http://localhost:4444/wd/hub'; // this is the default url and port where Selenium server starts +```sh +chromedriver --port=4444 ``` -##### Launch Chrome +#### b) Geckodriver -Install latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads). +Install the latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases). +Make sure to have a compatible version of Geckodriver and Firefox! -The `chromedriver` binary must be placed in system `PATH` directory, otherwise you must provide the path when starting Selenium server -(eg. `java -Dwebdriver.chrome.driver="/path/to/chromedriver" -jar selenium-server-standalone-#.jar`). +Run `geckodriver` binary (it start to listen on port 4444 by default): -```php -$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); +```sh +geckodriver +``` + +#### c) Selenium standalone server + +[Selenium server](https://selenium.dev/downloads/) is useful especially when you need to execute multiple tests at once +or your tests are run in different browsers - like on your CI server. + +Selenium server receives commands and starts new sessions using browser drivers acting like hub distributing the commands +among multiple nodes. + +To run the standalone server, download [`selenium-server-standalone-#.jar` file](http://selenium-release.storage.googleapis.com/index.html) +(replace # with the current server version). Keep in mind **you must have Java 8+ installed**. + +Run the server: + +```sh +java -jar selenium-server-standalone-#.jar +``` + +You may need to provide path to `chromedriver`/`geckodriver` binary (if they are not placed in system `PATH` directory): + +```sh +# Chromedriver: +java -Dwebdriver.chrome.driver="/opt/chromium-browser/chromedriver" -jar vendor/bin/selenium-server-standalone-#.jar +# Geckodriver: +java -Dwebdriver.gecko.driver="/home/john/bin/geckodriver" -jar vendor/bin/selenium-server-standalone-#.jar + +# (These options could be combined) ``` -##### Launch Firefox +If you want to distribute browser sessions among multiple servers ("grid mode" - one Selenium hub and multiple Selenium nodes) please +[refer to the documentation](https://selenium.dev/documentation/en/grid/). + +#### d) Docker -Install latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases). +Selenium server could also be started inside Docker container - see [docker-selenium project](https://github.com/SeleniumHQ/docker-selenium). -The `geckodriver` binary must be placed in system `PATH` directory, otherwise you must provide the path when starting Selenium server -(eg. `java -Dwebdriver.gecko.driver="/path/to/geckodriver" -jar selenium-server-standalone-#.jar`). +### 2. Create a Browser Session +When creating a browser session, be sure to pass the url of your running server. + +For example: + +```php +// Chromedriver (if started using --port=4444 as above) +$host = 'http://localhost:4444/'; +// Geckodriver +$host = 'http://localhost:4444/'; +// selenium-server-standalone-#.jar (version 2.x or 3.x) +$host = 'http://localhost:4444/wd/hub'; +// selenium-server-standalone-#.jar (version 4.x) +$host = 'http://localhost:4444/'; +``` + +Now you can start browser of your choice: ```php +use Facebook\WebDriver\Remote\RemoteWebDriver; + +// Chrome +$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); +// Firefox $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); +// Microsoft Edge +$driver = RemoteWebDriver::create($host, DesiredCapabilities::microsoftEdge()); ``` -### Customize Desired Capabilities +### 3. Customize Desired Capabilities + +Desired capabilities define properties of the browser you are about to start. + +They can be customized: ```php +use Facebook\WebDriver\Remote\DesiredCapabilities; + $desiredCapabilities = DesiredCapabilities::firefox(); + +// Disable accepting SSL certificates $desiredCapabilities->setCapability('acceptSslCerts', false); + +// Run headless firefox +$desiredCapabilities->setCapability('moz:firefoxOptions', ['args' => ['-headless']]); + $driver = RemoteWebDriver::create($host, $desiredCapabilities); ``` -* See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities for more details. +They can also be used to [configure proxy server](https://github.com/php-webdriver/php-webdriver/wiki/HowTo-Work-with-proxy) which the browser should use. +To configure Chrome, you may use ChromeOptions - see [details in our wiki](https://github.com/php-webdriver/php-webdriver/wiki/ChromeOptions). -**NOTE:** Above snippets are not intended to be a working example by simply copy-pasting. See [example.php](example.php) for working example. +* See [legacy JsonWire protocol](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities) documentation or [W3C WebDriver specification](https://w3c.github.io/webdriver/#capabilities) for more details. + +**NOTE:** Above snippets are not intended to be a working example by simply copy-pasting. See [example.php](example.php) for a working example. ## Changelog For latest changes see [CHANGELOG.md](CHANGELOG.md) file. ## More information -Some how-tos are provided right here in [our GitHub wiki](https://github.com/php-webdriver/php-webdriver/wiki). +Some basic usage example is provided in [example.php](example.php) file. + +How-tos are provided right here in [our GitHub wiki](https://github.com/php-webdriver/php-webdriver/wiki). -You may also want to check out the Selenium [docs](http://docs.seleniumhq.org/docs/) and [wiki](https://github.com/SeleniumHQ/selenium/wiki). +You may also want to check out the Selenium [docs](https://selenium.dev/documentation/en/) and [wiki](https://github.com/SeleniumHQ/selenium/wiki). ## Testing framework integration @@ -118,4 +189,4 @@ We have a great community willing to help you! ## Contributing -We love to have your help to make php-webdriver better. See [CONTRIBUTING.md](CONTRIBUTING.md) for more information about contributing and developing php-webdriver. +We love to have your help to make php-webdriver better. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more information about contributing and developing php-webdriver. diff --git a/example.php b/example.php index be2c5eaa3..b7daac7bc 100644 --- a/example.php +++ b/example.php @@ -70,6 +70,5 @@ $cookies = $driver->manage()->getCookies(); print_r($cookies); - // close the browser $driver->quit(); From 1b13ddf96cc007560f8522309a2b0e17d53cccb5 Mon Sep 17 00:00:00 2001 From: Kaz Date: Wed, 5 Feb 2020 10:11:16 +0100 Subject: [PATCH 010/265] Attempt to upload the file to the remote browser even in W3C mode --- lib/Remote/RemoteWebElement.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index fff018e41..1e6a217f2 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -341,8 +341,17 @@ public function sendKeys($value) } } else { if ($this->isW3cCompliant) { + try { + // Attempt to upload the file to the remote browser. + // 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) { + $fileName = $local_file; + } + $params = [ - 'text' => $local_file, + 'text' => $fileName, ':id' => $this->id, ]; } else { From 748901162c03a4df7610e44da01907262d4fce50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 6 Feb 2020 12:23:24 +0100 Subject: [PATCH 011/265] Add few missing unit tests --- lib/Remote/RemoteTargetLocator.php | 4 ++-- tests/functional/RemoteTargetLocatorTest.php | 6 +++++ tests/unit/Remote/DesiredCapabilitiesTest.php | 15 ++++++++++++ tests/unit/Remote/LocalFileDetectorTest.php | 24 +++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Remote/LocalFileDetectorTest.php diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index a5bd9e03c..fb5b552bc 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -27,8 +27,8 @@ public function __construct($executor, $driver, $isW3cCompliant = false) } /** - * Switch to the main document if the page contains iframes. Otherwise, switch - * to the first frame on the page. + * Set the current browsing context to the current top-level browsing context. + * This is the same as calling `RemoteTargetLocator::frame(null);` * * @return WebDriver The driver focused on the top window or the first frame. */ diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index e47612173..8ee5c96bc 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -77,6 +77,12 @@ public function testShouldSwitchToFrameByItsId() $this->driver->switchTo()->frame(null); $this->assertContains($parentPage, $this->driver->getPageSource()); + + $this->driver->switchTo()->frame(0); + $this->assertContains($firstChildFrame, $this->driver->getPageSource()); + + $this->driver->switchTo()->defaultContent(); + $this->assertContains($parentPage, $this->driver->getPageSource()); } public function testShouldSwitchToParentFrame() diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index 131bec2ca..f74eb754d 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -50,6 +50,21 @@ public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters() $this->assertSame(333, $capabilities->getVersion()); } + public function testShouldAccessCapabilitiesIsser() + { + $capabilities = new DesiredCapabilities(); + + $capabilities->setCapability('custom', 1337); + $capabilities->setCapability('customBooleanTrue', true); + $capabilities->setCapability('customBooleanFalse', false); + $capabilities->setCapability('customNull', null); + + $this->assertTrue($capabilities->is('custom')); + $this->assertTrue($capabilities->is('customBooleanTrue')); + $this->assertFalse($capabilities->is('customBooleanFalse')); + $this->assertFalse($capabilities->is('customNull')); + } + public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser() { $this->expectException(\Exception::class); diff --git a/tests/unit/Remote/LocalFileDetectorTest.php b/tests/unit/Remote/LocalFileDetectorTest.php new file mode 100644 index 000000000..b58413d8b --- /dev/null +++ b/tests/unit/Remote/LocalFileDetectorTest.php @@ -0,0 +1,24 @@ +getLocalFile(__DIR__ . '/./' . basename(__FILE__)); + + $this->assertSame(__FILE__, $file); + } + + public function testShouldReturnNullIfFileNotDetected() + { + $detector = new LocalFileDetector(); + + $this->assertNull($detector->getLocalFile('/this/is/not/a/file')); + } +} From cfc96ebb114d16ab04ee4019b7b8358199f9ccd2 Mon Sep 17 00:00:00 2001 From: Aleksandar Rusakov Date: Wed, 5 Feb 2020 17:21:33 +0200 Subject: [PATCH 012/265] Let ZipArchive handle the creation of the zip files --- lib/Remote/RemoteWebElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 1e6a217f2..8e238698c 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -499,7 +499,7 @@ protected function upload($local_file) } // Create a temporary file in the system temp directory. - $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverZip'); + $temp_zip = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('WebDriverZip', false); $zip = new ZipArchive(); if (($errorCode = $zip->open($temp_zip, ZipArchive::CREATE)) !== true) { throw new WebDriverException(sprintf('Error creating zip archive: %s', $errorCode)); From e0de4572570704d8378710b0b67422ee3c333394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 7 Feb 2020 12:18:09 +0100 Subject: [PATCH 013/265] Refactor zip file upload method --- lib/Remote/RemoteWebElement.php | 38 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 8e238698c..fd4de3511 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -498,26 +498,38 @@ protected function upload($local_file) throw new WebDriverException('You may only upload files: ' . $local_file); } + $temp_zip_path = $this->createTemporaryZipArchive($local_file); + + $remote_path = $this->executor->execute( + DriverCommand::UPLOAD_FILE, + ['file' => base64_encode(file_get_contents($temp_zip_path))] + ); + + unlink($temp_zip_path); + + return $remote_path; + } + + /** + * @param string $fileToZip + * @return string + */ + protected function createTemporaryZipArchive($fileToZip) + { // Create a temporary file in the system temp directory. - $temp_zip = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('WebDriverZip', false); + // Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle. + $tempZipPath = sys_get_temp_dir() . '/' . uniqid('WebDriverZip', false); + $zip = new ZipArchive(); - if (($errorCode = $zip->open($temp_zip, ZipArchive::CREATE)) !== true) { + if (($errorCode = $zip->open($tempZipPath, ZipArchive::CREATE)) !== true) { throw new WebDriverException(sprintf('Error creating zip archive: %s', $errorCode)); } - $info = pathinfo($local_file); + $info = pathinfo($fileToZip); $file_name = $info['basename']; - $zip->addFile($local_file, $file_name); + $zip->addFile($fileToZip, $file_name); $zip->close(); - $params = [ - 'file' => base64_encode(file_get_contents($temp_zip)), - ]; - $remote_path = $this->executor->execute( - DriverCommand::UPLOAD_FILE, - $params - ); - unlink($temp_zip); - return $remote_path; + return $tempZipPath; } } From 4708f6905d3daa92083b025128c770abe9d5907b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Feb 2020 16:03:46 +0100 Subject: [PATCH 014/265] Add information about renaming into README (#756) --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 25d7d29de..6585cd227 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,21 @@ Then install the library: php composer.phar require php-webdriver/webdriver +## Upgrade from version <1.8.0 + +Starting from version 1.8.0, the project has been renamed from `facebook/php-webdriver` to `php-webdriver/webdriver`. + +In order to receive the new version and future updates, **you need to rename it in your composer.json**: + +```diff +"require": { +- "facebook/webdriver": "(version you use)", ++ "php-webdriver/webdriver": "(version you use)", +} +``` + +and run `composer update`. + ## Getting started ### 1. Start server (aka. remote end) From 3e33ee3b8a688d719c55acdd7c6788e3006e1d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Feb 2020 16:04:25 +0100 Subject: [PATCH 015/265] =?UTF-8?q?Release=20version=201.8.0=20?= =?UTF-8?q?=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 645ffd936..7fc31a816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## 1.8.0 - 2020-02-10 ### Added - Experimental W3C WebDriver protocol support. The protocol will be used automatically when remote end (like Geckodriver, newer Chromedriver etc.) supports it. - `getStatus()` method of `RemoteWebDriver` to get information about remote-end readiness to create new sessions. From 7729633d3b7068a62e76adc4b784eb78f8dec9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 13 Feb 2020 12:54:32 +0100 Subject: [PATCH 016/265] Fix sendKeys not accepting array as input (#759) --- CHANGELOG.md | 2 ++ lib/Remote/RemoteWebElement.php | 2 +- lib/WebDriverKeys.php | 20 +++++++++--- tests/functional/RemoteWebElementTest.php | 5 +++ tests/unit/WebDriverKeysTest.php | 39 ++++++++++++++++------- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fc31a816..48ad2ca51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.) ## 1.8.0 - 2020-02-10 ### Added diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index fd4de3511..a02613104 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -330,7 +330,7 @@ public function sendKeys($value) if ($local_file === null) { if ($this->isW3cCompliant) { $params = [ - 'text' => (string) $value, + 'text' => WebDriverKeys::encode($value, true), ':id' => $this->id, ]; } else { diff --git a/lib/WebDriverKeys.php b/lib/WebDriverKeys.php index 77f75b4ea..5bba2eff0 100644 --- a/lib/WebDriverKeys.php +++ b/lib/WebDriverKeys.php @@ -90,11 +90,13 @@ class WebDriverKeys const COMMAND = self::META; /** - * Encode input of `sendKeys()`. + * Encode input of `sendKeys()` to appropriate format according to protocol. + * * @param string|array|int|float $keys - * @return array + * @param bool $isW3cCompliant + * @return array|string */ - public static function encode($keys) + public static function encode($keys, $isW3cCompliant = false) { if (is_numeric($keys)) { $keys = (string) $keys; @@ -105,7 +107,11 @@ public static function encode($keys) } if (!is_array($keys)) { - return []; + if (!$isW3cCompliant) { + return []; + } + + return ''; } $encoded = []; @@ -117,6 +123,10 @@ public static function encode($keys) $encoded[] = (string) $key; } - return $encoded; + if (!$isW3cCompliant) { + return $encoded; + } + + return implode('', $encoded); } } diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 46e3509bb..319a8eabb 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -157,6 +157,11 @@ public function testShouldSendKeysToFormElement() $this->assertSame('foo bar', $textarea->getAttribute('value')); $textarea->sendKeys(' baz'); $this->assertSame('foo bar baz', $textarea->getAttribute('value')); + + // Send keys as array + $textarea->clear(); + $textarea->sendKeys(['bat', 1, '3', ' ', 3, '7']); + $this->assertSame('bat13 37', $textarea->getAttribute('value')); } /** diff --git a/tests/unit/WebDriverKeysTest.php b/tests/unit/WebDriverKeysTest.php index b60bdb2b0..757b7ff99 100644 --- a/tests/unit/WebDriverKeysTest.php +++ b/tests/unit/WebDriverKeysTest.php @@ -12,11 +12,13 @@ class WebDriverKeysTest extends TestCase /** * @dataProvider provideKeys * @param mixed $keys - * @param array $expectedOutput + * @param array $expectedOssOutput + * @param string $expectedW3cOutput */ - public function testShouldEncodeKeysToArrayOfStrings($keys, $expectedOutput) + public function testShouldEncodeKeysToFormatOfEachProtocol($keys, $expectedOssOutput, $expectedW3cOutput) { - $this->assertSame($expectedOutput, WebDriverKeys::encode($keys)); + $this->assertSame($expectedOssOutput, WebDriverKeys::encode($keys)); + $this->assertSame($expectedW3cOutput, WebDriverKeys::encode($keys, true)); } /** @@ -25,19 +27,32 @@ public function testShouldEncodeKeysToArrayOfStrings($keys, $expectedOutput) public function provideKeys() { return [ - 'empty string' => ['', ['']], - 'simple string' => ['foo', ['foo']], - 'string as an array' => [['foo'], ['foo']], - 'string with modifier as an array' => [[WebDriverKeys::SHIFT, 'foo'], [WebDriverKeys::SHIFT, 'foo']], - 'string with concatenated modifier' => [[WebDriverKeys::SHIFT . 'foo'], [WebDriverKeys::SHIFT . 'foo']], - 'simple numeric value' => [3, ['3']], - 'multiple numeric values' => [[1, 3.33], ['1', '3.33']], - 'multiple mixed values ' => [['foo', WebDriverKeys::END, '1.234'], ['foo', WebDriverKeys::END, '1.234']], + 'empty string' => ['', [''], ''], + 'simple string' => ['foo', ['foo'], 'foo'], + 'string as an array' => [['foo'], ['foo'], 'foo'], + 'string with modifier as an array' => [ + [WebDriverKeys::SHIFT, 'foo'], + [WebDriverKeys::SHIFT, 'foo'], + WebDriverKeys::SHIFT . 'foo', + ], + 'string with concatenated modifier' => [ + [WebDriverKeys::SHIFT . 'foo'], + [WebDriverKeys::SHIFT . 'foo'], + WebDriverKeys::SHIFT . 'foo', + ], + 'simple numeric value' => [3, ['3'], '3'], + 'multiple numeric values' => [[1, 3.33], ['1', '3.33'], '13.33'], + 'multiple mixed values ' => [ + ['foo', WebDriverKeys::END, '1.234'], + ['foo', WebDriverKeys::END, '1.234'], + 'foo' . WebDriverKeys::END . '1.234', + ], 'array of strings with modifiers should separate them with NULL character' => [ [[WebDriverKeys::SHIFT, 'foo'], [WebDriverKeys::META, 'bar']], [WebDriverKeys::SHIFT . 'foo' . WebDriverKeys::NULL, WebDriverKeys::META . 'bar' . WebDriverKeys::NULL], + WebDriverKeys::SHIFT . 'foo' . WebDriverKeys::NULL . WebDriverKeys::META . 'bar' . WebDriverKeys::NULL, ], - 'null' => [null, []], + 'null' => [null, [], ''], ]; } } From 6ee357444f5050feac2e11aaf83cd6130931a09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 13 Feb 2020 16:30:33 +0100 Subject: [PATCH 017/265] Edge on Sauce Labs started to misidentify itself back and is failing the build --- tests/functional/RemoteWebDriverCreateTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index bf2d13620..2beaf8357 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -5,6 +5,7 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\HttpCommandExecutor; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\WebDriverBrowserType; /** * @covers \Facebook\WebDriver\Remote\HttpCommandExecutor @@ -36,7 +37,12 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $returnedCapabilities = $this->driver->getCapabilities(); $this->assertInstanceOf(WebDriverCapabilities::class, $returnedCapabilities); - $this->assertSame($this->desiredCapabilities->getBrowserName(), $returnedCapabilities->getBrowserName()); + + // MicrosoftEdge on Sauce Labs started to identify itself back as "msedge" + if ($this->desiredCapabilities->getBrowserName() !== WebDriverBrowserType::MICROSOFT_EDGE) { + $this->assertSame($this->desiredCapabilities->getBrowserName(), $returnedCapabilities->getBrowserName()); + } + $this->assertNotEmpty($returnedCapabilities->getPlatform()); $this->assertNotEmpty($returnedCapabilities); $this->assertNotEmpty($returnedCapabilities->getVersion()); From e8c0f60e0ed9d639d7e40c5bbefe7786c26694bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 12 Feb 2020 01:27:50 +0100 Subject: [PATCH 018/265] Use constants when referencing mouse buttons --- lib/Remote/RemoteMouse.php | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index e95a12fe3..2d9c34a99 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -10,6 +10,13 @@ */ class RemoteMouse implements WebDriverMouse { + /** @internal */ + const BUTTON_LEFT = 0; + /** @internal */ + const BUTTON_MIDDLE = 1; + /** @internal */ + const BUTTON_RIGHT = 2; + /** * @var RemoteExecuteMethod */ @@ -54,7 +61,7 @@ public function click(WebDriverCoordinates $where = null) $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ - 'button' => 0, + 'button' => self::BUTTON_LEFT, ]); return $this; @@ -78,13 +85,11 @@ public function contextClick(WebDriverCoordinates $where = null) 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerDown', - 'duration' => 0, - 'button' => 2, + 'button' => self::BUTTON_RIGHT, ], [ 'type' => 'pointerUp', - 'duration' => 0, - 'button' => 2, + 'button' => self::BUTTON_RIGHT, ], ]), ], @@ -96,7 +101,7 @@ public function contextClick(WebDriverCoordinates $where = null) $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ - 'button' => 2, + 'button' => self::BUTTON_RIGHT, ]); return $this; @@ -150,8 +155,7 @@ public function mouseDown(WebDriverCoordinates $where = null) $this->createMoveAction($where), [ 'type' => 'pointerDown', - 'duration' => 0, - 'button' => 0, + 'button' => self::BUTTON_LEFT, ], ], ], @@ -229,8 +233,7 @@ public function mouseUp(WebDriverCoordinates $where = null) 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerUp', - 'duration' => 0, - 'button' => 0, + 'button' => self::BUTTON_LEFT, ], ]), ], @@ -290,13 +293,11 @@ private function createClickActions() return [ [ 'type' => 'pointerDown', - 'duration' => 0, - 'button' => 0, + 'button' => self::BUTTON_LEFT, ], [ 'type' => 'pointerUp', - 'duration' => 0, - 'button' => 0, + 'button' => self::BUTTON_LEFT, ], ]; } From f6fe710c7160d3058016f3caf66a9a7fef61f36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 13 Feb 2020 14:36:22 +0100 Subject: [PATCH 019/265] Add functional tests for dragAndDrop actions --- tests/functional/WebDriverActionsTest.php | 134 ++++++++++++++++------ tests/functional/web/events.html | 2 + tests/functional/web/index.html | 2 + tests/functional/web/sortable.html | 76 ++++++++++++ 4 files changed, 177 insertions(+), 37 deletions(-) create mode 100644 tests/functional/web/sortable.html diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 054147d52..40c560f96 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -5,28 +5,32 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @coversDefaultClass \Facebook\WebDriver\Interactions\WebDriverActions + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverButtonReleaseAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverClickAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverClickAndHoldAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverContextClickAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverDoubleClickAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverKeyDownAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverKeysRelatedAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverKeyUpAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverMouseAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverMouseMoveAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverMoveToOffsetAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverSendKeysAction + * @covers \Facebook\WebDriver\Interactions\Internal\WebDriverSingleKeyAction + * @covers \Facebook\WebDriver\Interactions\WebDriverActions */ class WebDriverActionsTest extends WebDriverTestCase { - protected function setUp() - { - parent::setUp(); - - $this->driver->get($this->getTestPageUrl('events.html')); - } - - /** - * @covers ::__construct - * @covers ::click - * @covers ::perform - */ public function testShouldClickOnElement() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Not supported by HtmlUnit browser'); } + $this->driver->get($this->getTestPageUrl('events.html')); + $element = $this->driver->findElement(WebDriverBy::id('item-1')); $this->driver->action() @@ -45,18 +49,14 @@ public function testShouldClickOnElement() $this->assertSame($logs, $loggedEvents); } - /** - * @covers ::__construct - * @covers ::clickAndHold - * @covers ::perform - * @covers ::release - */ public function testShouldClickAndHoldOnElementAndRelease() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Not supported by HtmlUnit browser'); } + $this->driver->get($this->getTestPageUrl('events.html')); + $element = $this->driver->findElement(WebDriverBy::id('item-1')); $this->driver->action() @@ -76,9 +76,6 @@ public function testShouldClickAndHoldOnElementAndRelease() /** * @group exclude-saucelabs - * @covers ::__construct - * @covers ::contextClick - * @covers ::perform */ public function testShouldContextClickOnElement() { @@ -90,6 +87,8 @@ public function testShouldContextClickOnElement() $this->markTestSkipped('Getting stuck in EdgeDriver'); } + $this->driver->get($this->getTestPageUrl('events.html')); + $element = $this->driver->findElement(WebDriverBy::id('item-2')); $this->driver->action() @@ -103,17 +102,14 @@ public function testShouldContextClickOnElement() $this->assertContains('contextmenu item-2', $loggedEvents); } - /** - * @covers ::__construct - * @covers ::doubleClick - * @covers ::perform - */ public function testShouldDoubleClickOnElement() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Not supported by HtmlUnit browser'); } + $this->driver->get($this->getTestPageUrl('events.html')); + $element = $this->driver->findElement(WebDriverBy::id('item-3')); $this->driver->action() @@ -124,9 +120,6 @@ public function testShouldDoubleClickOnElement() } /** - * @covers ::__construct - * @covers ::dragAndDrop - * @covers ::perform * @group exclude-saucelabs */ public function testShouldDragAndDrop() @@ -135,17 +128,70 @@ public function testShouldDragAndDrop() $this->markTestSkipped('Not supported by HtmlUnit browser'); } - $element = $this->driver->findElement(WebDriverBy::id('item-3')); - $target = $this->driver->findElement(WebDriverBy::id('item-1')); + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::FIREFOX) { + self::skipForJsonWireProtocol('Broken in legacy Firefox'); + } + + $this->driver->get($this->getTestPageUrl('sortable.html')); + + $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); + $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); + + $this->driver->action() + ->dragAndDrop($item13, $item24) + ->perform(); + + $this->assertSame( + [['1-1', '1-2', '1-4', '1-5'], ['2-1', '2-2', '2-3', '2-4', '1-3', '2-5']], + $this->retrieveListContent() + ); + + $item21 = $this->driver->findElement(WebDriverBy::id('item-2-1')); $this->driver->action() - ->dragAndDrop($element, $target) + ->dragAndDrop($item24, $item21) ->perform(); - $this->assertContains('mouseover item-3', $this->retrieveLoggedEvents()); - $this->assertContains('mousedown item-3', $this->retrieveLoggedEvents()); - $this->assertContains('mouseover item-1', $this->retrieveLoggedEvents()); - $this->assertContains('mouseup item-1', $this->retrieveLoggedEvents()); + $this->assertSame( + [['1-1', '1-2', '1-4', '1-5'], ['2-4', '2-1', '2-2', '2-3', '1-3', '2-5']], + $this->retrieveListContent() + ); + } + + /** + * @group exclude-saucelabs + */ + public function testShouldDragAndDropBy() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $this->driver->get($this->getTestPageUrl('sortable.html')); + + $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); + + $this->driver->action() + ->dragAndDropBy($item13, 100, 55) + ->perform(); + + $this->assertSame( + [['1-1', '1-2', '1-4', '1-5'], ['2-1', '2-2', '2-3', '2-4', '1-3', '2-5']], + $this->retrieveListContent() + ); + + $item25 = $this->driver->findElement(WebDriverBy::id('item-2-5')); + $item22 = $this->driver->findElement(WebDriverBy::id('item-2-2')); + + $this->driver->action() + ->dragAndDropBy($item25, 0, -130) + ->dragAndDropBy($item22, -100, -35) + ->perform(); + + $this->assertSame( + [['1-1', '2-2', '1-2', '1-4', '1-5'], ['2-1', '2-5', '2-3', '2-4', '1-3']], + $this->retrieveListContent() + ); } /** @@ -157,4 +203,18 @@ private function retrieveLoggedEvents() return explode("\n", $logElement->getText()); } + + /** + * @return array + */ + private function retrieveListContent() + { + $list1 = $this->driver->findElement(WebDriverBy::cssSelector('#sortable1')); + $list2 = $this->driver->findElement(WebDriverBy::cssSelector('#sortable2')); + + return [ + explode("\n", $list1->getText()), + explode("\n", $list2->getText()), + ]; + } } diff --git a/tests/functional/web/events.html b/tests/functional/web/events.html index 45e8f3971..5511d321a 100644 --- a/tests/functional/web/events.html +++ b/tests/functional/web/events.html @@ -28,8 +28,10 @@
  • Third item
  • +

    Mouse events:

    
     
    +

    Keyboard events:

    
     
     
    +    
    +    
    +
    +
    +
    +

    Sortable using jQuery

    + + +
    +
    +
      +
    • 1-1
    • +
    • 1-2
    • +
    • 1-3
    • +
    • 1-4
    • +
    • 1-5
    • +
    +
    +
    +
      +
    • 2-1
    • +
    • 2-2
    • +
    • 2-3
    • +
    • 2-4
    • +
    • 2-5
    • +
    +
    +
    + + + From 7f8382892cb34118700c8544e4a03366efbcc50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 12 Feb 2020 01:41:52 +0100 Subject: [PATCH 020/265] Fix mouse move action in W3C mode to use relative offset (fixes #757) https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action --- CHANGELOG.md | 1 + lib/Remote/RemoteMouse.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ad2ca51..c62e52d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Fixed - Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.) +- Use relative offset when moving mouse pointer in W3C WebDriver mode. ## 1.8.0 - 2020-02-10 ### Added diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index 2d9c34a99..3495ea745 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -273,13 +273,15 @@ private function createMoveAction( ) { $move_action = [ 'type' => 'pointerMove', - 'duration' => 0, + 'duration' => 100, // to simulate human delay 'x' => $x_offset === null ? 0 : $x_offset, 'y' => $y_offset === null ? 0 : $y_offset, ]; if ($where !== null) { $move_action['origin'] = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $where->getAuxiliary()]; + } else { + $move_action['origin'] = 'pointer'; } return $move_action; From 739d2fc13787f810f7bcf9d54428a44b91eee9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 13 Feb 2020 15:25:08 +0100 Subject: [PATCH 021/265] Add functional tests for keys actions --- lib/Interactions/WebDriverActions.php | 2 +- tests/functional/RemoteKeyboardTest.php | 16 +--- tests/functional/RetrieveEventsTrait.php | 31 +++++++ tests/functional/WebDriverActionsTest.php | 106 +++++++++++++++++----- 4 files changed, 121 insertions(+), 34 deletions(-) create mode 100644 tests/functional/RetrieveEventsTrait.php diff --git a/lib/Interactions/WebDriverActions.php b/lib/Interactions/WebDriverActions.php index aefd07faf..b5172e2e7 100644 --- a/lib/Interactions/WebDriverActions.php +++ b/lib/Interactions/WebDriverActions.php @@ -245,7 +245,7 @@ public function keyUp(WebDriverElement $element = null, $key = null) /** * Send keys by keyboard. - * If $element is provided, focus on that element first. + * If $element is provided, focus on that element first (using single mouse click). * * @see WebDriverKeys for special keys like CONTROL, ALT, etc. * @param WebDriverElement $element diff --git a/tests/functional/RemoteKeyboardTest.php b/tests/functional/RemoteKeyboardTest.php index 3da1341c0..45c9be151 100644 --- a/tests/functional/RemoteKeyboardTest.php +++ b/tests/functional/RemoteKeyboardTest.php @@ -9,6 +9,8 @@ */ class RemoteKeyboardTest extends WebDriverTestCase { + use RetrieveEventsTrait; + /** * @group exclude-firefox * Firefox does not properly support keyboard actions: @@ -55,7 +57,7 @@ public function testShouldPressSendAndReleaseKeys() 'keyup "Shift"', 'keyup "f"', ], - $this->retrieveLoggedEvents() + $this->retrieveLoggedKeyboardEvents() ); } else { $this->assertEquals( @@ -79,18 +81,8 @@ public function testShouldPressSendAndReleaseKeys() 'keydown "f"', 'keyup "f"', ], - $this->retrieveLoggedEvents() + $this->retrieveLoggedKeyboardEvents() ); } } - - /** - * @return array - */ - private function retrieveLoggedEvents() - { - $logElement = $this->driver->findElement(WebDriverBy::id('keyboardEventsLog')); - - return explode("\n", $logElement->getText()); - } } diff --git a/tests/functional/RetrieveEventsTrait.php b/tests/functional/RetrieveEventsTrait.php new file mode 100644 index 000000000..3a4a3d512 --- /dev/null +++ b/tests/functional/RetrieveEventsTrait.php @@ -0,0 +1,31 @@ +driver->findElement(WebDriverBy::id('keyboardEventsLog')); + + return explode("\n", $logElement->getText()); + } + + /** + * @return array + */ + private function retrieveLoggedMouseEvents() + { + $logElement = $this->driver->findElement(WebDriverBy::id('mouseEventsLog')); + + return explode("\n", $logElement->getText()); + } +} diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 40c560f96..476b359fd 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -23,6 +23,8 @@ */ class WebDriverActionsTest extends WebDriverTestCase { + use RetrieveEventsTrait; + public function testShouldClickOnElement() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { @@ -38,7 +40,7 @@ public function testShouldClickOnElement() ->perform(); $logs = ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1']; - $loggedEvents = $this->retrieveLoggedEvents(); + $loggedEvents = $this->retrieveLoggedMouseEvents(); if (getenv('GECKODRIVER') === '1') { $loggedEvents = array_slice($loggedEvents, 0, count($logs)); @@ -65,11 +67,11 @@ public function testShouldClickAndHoldOnElementAndRelease() ->perform(); if (self::isW3cProtocolBuild()) { - $this->assertArraySubset(['mouseover item-1', 'mousedown item-1'], $this->retrieveLoggedEvents()); + $this->assertArraySubset(['mouseover item-1', 'mousedown item-1'], $this->retrieveLoggedMouseEvents()); } else { $this->assertSame( ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], - $this->retrieveLoggedEvents() + $this->retrieveLoggedMouseEvents() ); } } @@ -95,7 +97,7 @@ public function testShouldContextClickOnElement() ->contextClick($element) ->perform(); - $loggedEvents = $this->retrieveLoggedEvents(); + $loggedEvents = $this->retrieveLoggedMouseEvents(); $this->assertContains('mousedown item-2', $loggedEvents); $this->assertContains('mouseup item-2', $loggedEvents); @@ -116,12 +118,87 @@ public function testShouldDoubleClickOnElement() ->doubleClick($element) ->perform(); - $this->assertContains('dblclick item-3', $this->retrieveLoggedEvents()); + $this->assertContains('dblclick item-3', $this->retrieveLoggedMouseEvents()); + } + + public function testShouldSendKeysUpAndDown() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::FIREFOX) { + self::skipForJsonWireProtocol('Broken in legacy Firefox'); + } + + $this->driver->get($this->getTestPageUrl('events.html')); + + $this->driver->action() + ->keyDown(null, WebDriverKeys::CONTROL) + ->keyUp(null, WebDriverKeys::CONTROL) + ->sendKeys(null, 'ab') + ->perform(); + + $events = $this->retrieveLoggedKeyboardEvents(); + + $this->assertEquals( + [ + 'keydown "Control"', + 'keyup "Control"', + 'keydown "a"', + 'keyup "a"', + 'keydown "b"', + 'keyup "b"', + ], + $events + ); + } + + public function testShouldMoveToElement() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $this->driver->get($this->getTestPageUrl('sortable.html')); + + $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); + $item24 = $this->driver->findElement(WebDriverBy::id('item-2-4')); + + $this->driver->action() + ->clickAndHold($item13) + ->moveToElement($item24) + ->release() + ->perform(); + + $this->assertSame( + [['1-1', '1-2', '1-4', '1-5'], ['2-1', '2-2', '2-3', '2-4', '1-3', '2-5']], + $this->retrieveListContent() + ); + } + + public function testShouldMoveByOffset() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $this->driver->get($this->getTestPageUrl('sortable.html')); + + $item13 = $this->driver->findElement(WebDriverBy::id('item-1-3')); + + $this->driver->action() + ->clickAndHold($item13) + ->moveByOffset(100, 55) + ->release() + ->perform(); + + $this->assertSame( + [['1-1', '1-2', '1-4', '1-5'], ['2-1', '2-2', '2-3', '2-4', '1-3', '2-5']], + $this->retrieveListContent() + ); } - /** - * @group exclude-saucelabs - */ public function testShouldDragAndDrop() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { @@ -158,9 +235,6 @@ public function testShouldDragAndDrop() ); } - /** - * @group exclude-saucelabs - */ public function testShouldDragAndDropBy() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { @@ -194,16 +268,6 @@ public function testShouldDragAndDropBy() ); } - /** - * @return array - */ - private function retrieveLoggedEvents() - { - $logElement = $this->driver->findElement(WebDriverBy::id('mouseEventsLog')); - - return explode("\n", $logElement->getText()); - } - /** * @return array */ From 639cb48fbc575f73d73c3b02eb3c2512052caebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 16 Feb 2020 20:10:27 +0100 Subject: [PATCH 022/265] Exclude skipped tests on SauceLabs --- tests/functional/WebDriverActionsTest.php | 6 ++++++ tests/functional/WebDriverWindowTest.php | 1 + 2 files changed, 7 insertions(+) diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 476b359fd..c3909da9b 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -121,6 +121,9 @@ public function testShouldDoubleClickOnElement() $this->assertContains('dblclick item-3', $this->retrieveLoggedMouseEvents()); } + /** + * @group exclude-saucelabs + */ public function testShouldSendKeysUpAndDown() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { @@ -199,6 +202,9 @@ public function testShouldMoveByOffset() ); } + /** + * @group exclude-saucelabs + */ public function testShouldDragAndDrop() { if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php index ba0b45187..62756f847 100644 --- a/tests/functional/WebDriverWindowTest.php +++ b/tests/functional/WebDriverWindowTest.php @@ -49,6 +49,7 @@ public function testShouldMaximizeWindow() } /** + * @group exclude-saucelabs * @group exclude-edge * @group exclude-chrome * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1049336 From 4c4bae3eaa1a474db8bc2d36b673d1e1681cb9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 14 Feb 2020 22:31:22 +0100 Subject: [PATCH 023/265] Reference phpunit.xsd from vendor to make it always up-to-date --- phpunit.xml.dist | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 430a8fb5c..2e81aef6d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,15 +3,9 @@ From 073a170b8b5f337c2b4eebf00057d346887e5697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 14 Feb 2020 22:31:33 +0100 Subject: [PATCH 024/265] Normalize composer.json --- .github/workflows/php.yaml | 8 +++++- composer.json | 53 +++++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index 04fd5b9da..c96bec74e 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -22,7 +22,13 @@ jobs: - name: Install dependencies run: | composer update --no-interaction - composer require phpstan/phpstan # Not part of require-dev, because it won't install on PHP 5.6 + composer require --dev phpstan/phpstan # Not part of require-dev, because it won't install on PHP 5.6 + composer require --dev ergebnis/composer-normalize + + - name: Check composer.json + run: | + composer validate --strict + composer normalize --dry-run - name: Run checks run: | diff --git a/composer.json b/composer.json index 77df01f21..619ebc205 100644 --- a/composer.json +++ b/composer.json @@ -1,11 +1,16 @@ { "name": "php-webdriver/webdriver", + "type": "library", "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", - "keywords": ["webdriver", "selenium", "php", "geckodriver", "chromedriver"], + "keywords": [ + "webdriver", + "selenium", + "php", + "geckodriver", + "chromedriver" + ], "homepage": "https://github.com/php-webdriver/php-webdriver", - "type": "library", "license": "MIT", - "minimum-stability": "beta", "require": { "php": "^5.6 || ~7.0", "ext-curl": "*", @@ -28,21 +33,39 @@ "suggest": { "ext-SimpleXML": "For Firefox profile creation" }, + "config": { + "sort-packages": true + }, + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, "autoload": { - "files": [ - "lib/Exception/TimeoutException.php" - ], "psr-4": { "Facebook\\WebDriver\\": "lib/" - } + }, + "files": [ + "lib/Exception/TimeoutException.php" + ] }, "autoload-dev": { "psr-4": { - "Facebook\\WebDriver\\": ["tests/unit", "tests/functional"] + "Facebook\\WebDriver\\": [ + "tests/unit", + "tests/functional" + ] }, - "classmap": ["tests/functional/"] + "classmap": [ + "tests/functional/" + ] }, + "minimum-stability": "beta", "scripts": { + "analyze": [ + "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", + "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" + ], "codestyle:check": [ "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff --dry-run -vvv --ansi", "vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/" @@ -50,18 +73,6 @@ "codestyle:fix": [ "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff -vvv || exit 0", "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" - ], - "analyze": [ - "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", - "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" ] - }, - "config": { - "sort-packages": true - }, - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } } } From 262ea0d209c292e0330be1041424887bbbffef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 17 Feb 2020 09:14:38 +0100 Subject: [PATCH 025/265] Release version 1.8.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c62e52d87..9c5d381ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.8.1 - 2020-02-17 ### Fixed - Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.) - Use relative offset when moving mouse pointer in W3C WebDriver mode. From cfb919b93e665b8e07a60ddec89a5a942819a84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 19 Feb 2020 12:03:11 +0100 Subject: [PATCH 026/265] Reimplement element equals method for W3C mode --- CHANGELOG.md | 2 ++ lib/Remote/RemoteWebElement.php | 3 +-- tests/functional/RemoteWebElementTest.php | 3 --- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5d381ad..9b0cbc84e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Reimplement element `equals()` method to be working in W3C mode. ## 1.8.1 - 2020-02-17 ### Fixed diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index a02613104..023a7390c 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -2,7 +2,6 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; @@ -463,7 +462,7 @@ public function takeElementScreenshot($save_as = null) public function equals(WebDriverElement $other) { if ($this->isW3cCompliant) { - throw new UnsupportedOperationException('"elementEquals" is not supported by the W3C specification'); + return $this->getID() === $other->getID(); } return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [ diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 319a8eabb..cacf16390 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -262,12 +262,9 @@ public function testShouldSubmitFormByClickOnSubmitInput() /** * @covers ::equals - * @group exclude-saucelabs */ public function testShouldCompareEqualsElement() { - self::skipForW3cProtocol('"equals" is not supported by the W3C specification'); - $this->driver->get($this->getTestPageUrl('index.html')); $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); From fdf430d33376acf626b6335ecf576f7c3d7a5e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 24 Feb 2020 17:54:33 +0100 Subject: [PATCH 027/265] When creating from session ID properly read W3C compliance param --- CHANGELOG.md | 3 +++ lib/Remote/RemoteWebDriver.php | 10 ++++++++-- phpstan.neon | 4 ++++ tests/functional/RemoteWebDriverCreateTest.php | 6 +++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0cbc84e..95cf82f88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Reimplement element `equals()` method to be working in W3C mode. +### Fixed +- Properly read fifth parameter whether W3C compliant instance should be created when using `createBySessionID()`. + ## 1.8.1 - 2020-02-17 ### Fixed - Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index f157fdf06..0f81a2f90 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -161,6 +161,7 @@ public static function create( * @param string $session_id The existing session id * @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 false to use the legacy JsonWire protocol, true for the W3C WebDriver spec * @return static */ public static function createBySessionID( @@ -170,7 +171,8 @@ public static function createBySessionID( $request_timeout_in_ms = null ) { // BC layer to not break the method signature - $w3c_compliant = func_num_args() > 3 ? func_get_arg(3) : false; + $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : false; + $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); @@ -179,7 +181,11 @@ public static function createBySessionID( $executor->setRequestTimeout($request_timeout_in_ms); } - return new static($executor, $session_id, null, $w3c_compliant); + if (!$isW3cCompliant) { + $executor->disableW3cCompliance(); + } + + return new static($executor, $session_id, null, $isW3cCompliant); } /** diff --git a/phpstan.neon b/phpstan.neon index de6574f0b..cf3300bfe 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,4 +10,8 @@ parameters: - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' - '#Unsafe usage of new static\(\)#' + # Parameter is intentionally not part of signature to not break BC + - message: '#PHPDoc tag \@param references unknown parameter: \$isW3cCompliant#' + path: 'lib/Remote/RemoteWebDriver.php' + inferPrivatePropertyTypeFromConstructor: true diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 2beaf8357..4eb7ee507 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -92,11 +92,15 @@ public function testShouldCreateInstanceFromExistingSessionId() // Store session ID $sessionId = $originalDriver->getSessionID(); + $isW3cCompliant = $originalDriver->isW3cCompliant(); // Create new RemoteWebDriver instance based on the session ID - $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl); + $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl, null, null, $isW3cCompliant); // Check we reused the previous instance (window) and it has the same URL $this->assertContains('/index.html', $this->driver->getCurrentURL()); + + // Do some interaction with the new driver + $this->assertNotEmpty($this->driver->findElement(WebDriverBy::id('id_test'))->getText()); } } From c1539255983f567d7711641ce603e9a3e4d13563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 24 Feb 2020 17:56:58 +0100 Subject: [PATCH 028/265] Create W3C compatible instance by default (fixes #771) --- CHANGELOG.md | 1 + lib/Remote/RemoteWebDriver.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95cf82f88..128ee4b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Changed - Reimplement element `equals()` method to be working in W3C mode. +- New instance of `RemoteWebDriver` created via `createBySessionID()` by default expects W3C mode. This could be disabled using fifth parameter of `createBySessionID()`. ### Fixed - Properly read fifth parameter whether W3C compliant instance should be created when using `createBySessionID()`. diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 0f81a2f90..b5f53246f 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -161,7 +161,7 @@ public static function create( * @param string $session_id The existing session id * @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 false to use the legacy JsonWire protocol, true for the W3C WebDriver spec + * @param bool $isW3cCompliant True to use W3C WebDriver (default), false to use the legacy JsonWire protocol * @return static */ public static function createBySessionID( @@ -171,7 +171,7 @@ public static function createBySessionID( $request_timeout_in_ms = null ) { // BC layer to not break the method signature - $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : false; + $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : true; $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { From b614a01496ad7efe21bc24dc63c6419ebbf76eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 22 Feb 2020 12:16:36 +0100 Subject: [PATCH 029/265] Fix creating firefox profile zip when using libzip 1.6+ --- lib/Firefox/FirefoxProfile.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index 04e31dbc0..cce9baaf4 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -148,8 +148,10 @@ public function encode() } file_put_contents($temp_dir . '/user.js', $content); + // Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle. + $temp_zip = sys_get_temp_dir() . '/' . uniqid('WebDriverFirefoxProfileZip', false); + $zip = new ZipArchive(); - $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverFirefoxProfileZip'); $zip->open($temp_zip, ZipArchive::CREATE); $dir = new RecursiveDirectoryIterator($temp_dir); From 792e82cfb90375efb9191e487ab260fa193cf39c Mon Sep 17 00:00:00 2001 From: Chin Leung Date: Mon, 17 Feb 2020 10:17:23 -0500 Subject: [PATCH 030/265] Disable the JSON viewer in Firefox --- lib/Firefox/FirefoxPreferences.php | 2 ++ lib/Remote/DesiredCapabilities.php | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Firefox/FirefoxPreferences.php b/lib/Firefox/FirefoxPreferences.php index 96248aa98..159a9c806 100644 --- a/lib/Firefox/FirefoxPreferences.php +++ b/lib/Firefox/FirefoxPreferences.php @@ -16,6 +16,8 @@ class FirefoxPreferences const READER_PARSE_ON_LOAD_ENABLED = 'reader.parse-on-load.enabled'; /** @var string Browser homepage */ const BROWSER_STARTUP_HOMEPAGE = 'browser.startup.homepage'; + /** @var string Should the Devtools JSON view be enabled? */ + const DEVTOOLS_JSONVIEW = 'devtools.jsonview.enabled'; private function __construct() { diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 7eef1de29..e022ccecd 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -288,9 +288,12 @@ public static function firefox() WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); - // disable the "Reader View" help tooltip, which can hide elements in the window.document $profile = new FirefoxProfile(); + // disable the "Reader View" help tooltip, which can hide elements in the window.document $profile->setPreference(FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED, false); + // disable JSON viewer and let JSON be rendered as raw data + $profile->setPreference(FirefoxPreferences::DEVTOOLS_JSONVIEW, false); + $caps->setCapability(FirefoxDriver::PROFILE, $profile); return $caps; From 28fdc8db48b186817d4b9fbf86d7e6853e6b0609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 4 Mar 2020 13:17:19 +0100 Subject: [PATCH 031/265] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 128ee4b78..71d904027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Reimplement element `equals()` method to be working in W3C mode. - New instance of `RemoteWebDriver` created via `createBySessionID()` by default expects W3C mode. This could be disabled using fifth parameter of `createBySessionID()`. +- Disable JSON viewer in Firefox to let JSON response be rendered as-is. ### Fixed - Properly read fifth parameter whether W3C compliant instance should be created when using `createBySessionID()`. +- Creating of Firefox profile with libzip 1.6+ (eg. on Mac OS Catalina). ## 1.8.1 - 2020-02-17 ### Fixed From 3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 4 Mar 2020 15:40:12 +0100 Subject: [PATCH 032/265] Release version 1.8.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d904027..b2ff306a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.8.2 - 2020-03-04 ### Changed - Reimplement element `equals()` method to be working in W3C mode. - New instance of `RemoteWebDriver` created via `createBySessionID()` by default expects W3C mode. This could be disabled using fifth parameter of `createBySessionID()`. From 306402134d01590dad2070b779288e30b7fce5ea Mon Sep 17 00:00:00 2001 From: William Desportes Date: Thu, 19 Mar 2020 21:47:38 +0100 Subject: [PATCH 033/265] Clean up some files from dist version --- .gitattributes | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitattributes b/.gitattributes index c3b6383af..3123b82f2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,7 @@ /.travis.yml export-ignore /example.php export-ignore /phpunit.xml.dist export-ignore +/.github export-ignore +/.php_cs.dist export-ignore +/phpstan.neon export-ignore +/.coveralls.yml export-ignore From 512899069b78274b4f09d96f209b788603faaf5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 24 Apr 2020 16:34:08 +0200 Subject: [PATCH 034/265] Update dependencies and fix phpstan build --- composer.json | 2 +- phpstan.neon | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 619ebc205..1d6e43ec0 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", - "jakub-onderka/php-parallel-lint": "^1.0", "php-coveralls/php-coveralls": "^2.0", "php-mock/php-mock-phpunit": "^1.1", + "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^5.7", "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", "sminnee/phpunit-mock-objects": "^3.4", diff --git a/phpstan.neon b/phpstan.neon index cf3300bfe..939659c07 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,5 @@ parameters: ignoreErrors: - - '#Class Symfony\\Component\\Process\\ProcessBuilder not found.#' - - '#Instantiated class Symfony\\Component\\Process\\ProcessBuilder not found.#' - - '#Call to method setPrefix\(\) on an unknown class Symfony\\Component\\Process\\ProcessBuilder#' # To be fixed: - '#Call to an undefined method RecursiveIteratorIterator::getSubPathName\(\)#' - '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' From 86179ee11f5d8a2ad07ac837b06122aad9449cf7 Mon Sep 17 00:00:00 2001 From: Jean Ragouin Date: Sun, 12 Apr 2020 21:45:07 +0800 Subject: [PATCH 035/265] Update method description --- lib/Remote/RemoteWebElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 023a7390c..f8ee1d1fd 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -124,7 +124,7 @@ public function findElements(WebDriverBy $by) } /** - * Get the value of a the given attribute of the element. + * Get the value of the given attribute of the element. * * @param string $attribute_name The name of the attribute. * @return string|null The value of the attribute. From a3b6811dcf3d638ddcd33cf82cc81fd2d334d7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 24 Apr 2020 18:31:12 +0200 Subject: [PATCH 036/265] Mark package replacing facebook/webdriver (fixes #780) --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 1d6e43ec0..32014e38c 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ "symfony/polyfill-mbstring": "^1.12", "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0" }, + "replace": { + "facebook/webdriver": "*" + }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", "php-coveralls/php-coveralls": "^2.0", From f5caf908489c4b2ba0b8b378e1d2b7487e33b116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 7 May 2020 00:35:03 +0200 Subject: [PATCH 037/265] Fix phpstan build --- phpstan.neon | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 939659c07..b0777e2c2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,7 +1,6 @@ parameters: ignoreErrors: # To be fixed: - - '#Call to an undefined method RecursiveIteratorIterator::getSubPathName\(\)#' - '#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\(\)#' From a5974811f645adf625c27af8abd7e8a73e89a362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 May 2020 12:33:26 +0200 Subject: [PATCH 038/265] Fix alertIsPresent expected condition in W3C mode (fixes #790) --- lib/WebDriverExpectedCondition.php | 4 ++-- tests/functional/WebDriverAlertTest.php | 4 ++-- tests/functional/web/alert.html | 11 ++++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 23fc92fba..02d2e71e0 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -2,7 +2,7 @@ namespace Facebook\WebDriver; -use Facebook\WebDriver\Exception\NoAlertOpenException; +use Facebook\WebDriver\Exception\NoSuchAlertException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\NoSuchFrameException; use Facebook\WebDriver\Exception\StaleElementReferenceException; @@ -545,7 +545,7 @@ function (WebDriver $driver) { $alert->getText(); return $alert; - } catch (NoAlertOpenException $e) { + } catch (NoSuchAlertException $e) { return null; } } diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 83730f6af..f8373d635 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -21,8 +21,8 @@ protected function setUp() public function testShouldAcceptAlert() { - // Open alert - $this->driver->findElement(WebDriverBy::id('open-alert'))->click(); + // 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(); // Wait until present $this->driver->wait()->until(WebDriverExpectedCondition::alertIsPresent()); diff --git a/tests/functional/web/alert.html b/tests/functional/web/alert.html index 079d75130..4bf78680a 100644 --- a/tests/functional/web/alert.html +++ b/tests/functional/web/alert.html @@ -7,13 +7,18 @@

    - Open alert + Open alert
    - Open confirm + + Open alert after 1 second +
    - Open prompt + Open confirm +
    + + Open prompt

    From 0bb2c95d673d0a6ee10e92381ac9a0bfd304c6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 May 2020 12:10:48 +0200 Subject: [PATCH 039/265] Fix RemoteWebDriver cannot be instantiated without second parameter (fixes #789) --- lib/Chrome/ChromeDriver.php | 2 +- lib/Remote/RemoteWebDriver.php | 9 ++++----- tests/functional/RemoteWebDriverCreateTest.php | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/Chrome/ChromeDriver.php b/lib/Chrome/ChromeDriver.php index 1c9828c8e..a9ead161b 100644 --- a/lib/Chrome/ChromeDriver.php +++ b/lib/Chrome/ChromeDriver.php @@ -35,7 +35,7 @@ public function startSession(DesiredCapabilities $desired_capabilities) null, DriverCommand::NEW_SESSION, [ - 'desiredCapabilities' => $desired_capabilities->toArray(), + 'desiredCapabilities' => (object) $desired_capabilities->toArray(), ] ); $response = $this->executor->execute($command); diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index b5f53246f..f1a47fe44 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -93,7 +93,6 @@ public static function create( $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { - // BC layer to not break the method signature $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url); $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); @@ -109,12 +108,12 @@ public static function create( // W3C $parameters = [ 'capabilities' => [ - 'firstMatch' => [$desired_capabilities->toW3cCompatibleArray()], + 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], ], ]; if ($required_capabilities !== null && !empty($required_capabilities->toArray())) { - $parameters['capabilities']['alwaysMatch'] = $required_capabilities->toW3cCompatibleArray(); + $parameters['capabilities']['alwaysMatch'] = (object) $required_capabilities->toW3cCompatibleArray(); } // Legacy protocol @@ -122,10 +121,10 @@ public static function create( // TODO: Selenium (as of v3.0.1) does accept requiredCapabilities only as a property of desiredCapabilities. // This has changed with the W3C WebDriver spec, but is the only way how to pass these // values with the legacy protocol. - $desired_capabilities->setCapability('requiredCapabilities', $required_capabilities->toArray()); + $desired_capabilities->setCapability('requiredCapabilities', (object) $required_capabilities->toArray()); } - $parameters['desiredCapabilities'] = $desired_capabilities->toArray(); + $parameters['desiredCapabilities'] = (object) $desired_capabilities->toArray(); $command = new WebDriverCommand( null, diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 4eb7ee507..db379964d 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -78,6 +78,22 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); } + /** + * Capabilities (browser name) must be defined when executing via Selenium proxy (standalone server, Saucelabs etc.) + * @group exclude-saucelabs + */ + public function testShouldCreateWebDriverWithoutCapabilities() + { + if (getenv('GECKODRIVER') !== '1' && getenv('CHROMEDRIVER') !== '1') { + $this->markTestSkipped('This test makes sense only when run directly via specific browser driver'); + } + + $this->driver = RemoteWebDriver::create($this->serverUrl); + + $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); + $this->assertNotEmpty($this->driver->getSessionID()); + } + public function testShouldCreateInstanceFromExistingSessionId() { // Create driver instance and load page "index.html" From 53b023cd48af86c30e8cf182146a402b4f09c260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 12 May 2020 23:59:42 +0200 Subject: [PATCH 040/265] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ff306a8..33c219c31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- Make `alertIsPresent()` condition working in W3C mode. +- `RemoteWebDriver::create()` cannot be used without providing the second parameter (which is in fact optional). ## 1.8.2 - 2020-03-04 ### Changed From 501987c3b8c012a08384c0c65dcffd319d0cebae Mon Sep 17 00:00:00 2001 From: Arnout Boks Date: Tue, 2 Jun 2020 21:33:06 +0200 Subject: [PATCH 041/265] Fix ChromeDriver::start() causes 'mixed' W3c mode The `new static` within ChromeDriver::start() creates a RemoteWebDriver that is not W3C compatible by default (due to the default for RemoteWebDriver's 3rd constructor arg). The command executor it uses assumes W3C compatibility by default though. This causes strange bugs, where commands are created assuming non-compliance, but translated by the command executor as if they were W3C compliant. By using the logic already present in RemoteWebDriver::create(), ChromeDriver now detects whether the sesson it started is in W3C compatible mode and updates the flags both on itself and on its executor. --- lib/Chrome/ChromeDriver.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Chrome/ChromeDriver.php b/lib/Chrome/ChromeDriver.php index a9ead161b..1b77169fb 100644 --- a/lib/Chrome/ChromeDriver.php +++ b/lib/Chrome/ChromeDriver.php @@ -39,6 +39,12 @@ public function startSession(DesiredCapabilities $desired_capabilities) ] ); $response = $this->executor->execute($command); + $value = $response->getValue(); + + if (!$this->isW3cCompliant = isset($value['capabilities'])) { + $this->executor->disableW3cCompliance(); + } + $this->sessionID = $response->getSessionID(); } From e4347e2164d56c9ad20911f37d41919a0eaadf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 18 Jun 2020 19:43:24 +0200 Subject: [PATCH 042/265] Rename default branch to main --- .github/CONTRIBUTING.md | 2 +- README.md | 2 +- composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2fdbb677f..ed13ceed8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,7 +14,7 @@ The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) 2. Implement your code changes into separate branch 3. Make sure all PHPUnit tests passes and code-style matches PSR-2 (see below). We also have Travis CI build which will automatically run tests on your pull request. 4. When implementing notable change, fix or a new feature, add record to Unreleased section of [CHANGELOG.md](CHANGELOG.md) -5. Submit your [pull request](https://github.com/php-webdriver/php-webdriver/pulls) against `master` branch +5. Submit your [pull request](https://github.com/php-webdriver/php-webdriver/pulls) against `main` branch When you are going to contribute, please keep in mind that this webdriver client aims to be as close as possible to other languages Java/Ruby/Python/C#. FYI, here is the overview of [the official Java API](http://seleniumhq.github.io/selenium/docs/api/java/) diff --git a/README.md b/README.md index 6585cd227..c69dea366 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # php-webdriver – Selenium WebDriver bindings for PHP [![Latest Stable Version](https://img.shields.io/packagist/v/php-webdriver/webdriver.svg?style=flat-square)](https://packagist.org/packages/php-webdriver/webdriver) -[![Travis Build](https://img.shields.io/travis/php-webdriver/php-webdriver/master.svg?style=flat-square)](https://travis-ci.com/php-webdriver/php-webdriver) +[![Travis Build](https://img.shields.io/travis/php-webdriver/php-webdriver/main.svg?style=flat-square)](https://travis-ci.com/php-webdriver/php-webdriver) [![Sauce Test Status](https://saucelabs.com/buildstatus/php-webdriver)](https://saucelabs.com/u/php-webdriver) [![Total Downloads](https://img.shields.io/packagist/dd/php-webdriver/webdriver.svg?style=flat-square)](https://packagist.org/packages/php-webdriver/webdriver) diff --git a/composer.json b/composer.json index 32014e38c..7c0f1fdc3 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-main": "1.8.x-dev" } }, "autoload": { From 6916b0bf61ab19b9d06f50f3c79b209e17f4c9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 4 Aug 2020 13:53:57 +0200 Subject: [PATCH 043/265] Minor improvements and typos fixes --- lib/Remote/RemoteWebElement.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index f8ee1d1fd..0ca59aba7 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -81,8 +81,7 @@ public function click() * Find the first WebDriverElement within this element using the given mechanism. * * @param WebDriverBy $by - * @return RemoteWebElement NoSuchElementException is thrown in - * HttpCommandExecutor if no element is found. + * @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found. * @see WebDriverBy */ public function findElement(WebDriverBy $by) @@ -213,10 +212,10 @@ public function getCoordinates() $element = $this; $on_screen = null; // planned but not yet implemented - $in_view_port = function () use ($element) { + $in_view_port = static function () use ($element) { return $element->getLocationOnScreenOnceScrolledIntoView(); }; - $on_page = function () use ($element) { + $on_page = static function () use ($element) { return $element->getLocation(); }; $auxiliary = $this->getID(); @@ -304,7 +303,7 @@ public function isEnabled() } /** - * Determine whether or not this element is selected or not. + * Determine whether this element is selected or not. * * @return bool */ @@ -367,8 +366,7 @@ public function sendKeys($value) } /** - * Set the fileDetector in order to let the RemoteWebElement to know that - * you are going to upload a file. + * Set the fileDetector in order to let the RemoteWebElement to know that you are going to upload a file. * * Basically, if you want WebDriver trying to send a file, set the fileDetector * to be LocalFileDetector. Otherwise, keep it UselessFileDetector. @@ -427,7 +425,7 @@ public function getID() } /** - * Take screenshot of a specific element. + * Take a screenshot of a specific element. * * @param string $save_as The path of the screenshot to be saved. * @return string The screenshot in PNG format. @@ -454,7 +452,7 @@ public function takeElementScreenshot($save_as = null) } /** - * Test if two element IDs refer to the same DOM element. + * Test if two elements IDs refer to the same DOM element. * * @param WebDriverElement $other * @return bool From e2ae45a78370e2000711ba60de16d7f13317a53a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 26 Jun 2020 11:02:41 +0800 Subject: [PATCH 044/265] Work around NULL key issue in sendKeys A long-standing geckodriver bug (https://bugzilla.mozilla.org/show_bug.cgi?id=1494661) means that it is not possible to send a key combination such as SHIFT + 'h' + NULL + 'ello'. In this case the NULL key does not clear the SHIFT modifier as per the specification, and it additionally prints a character for the NULL key. It is possible to work around this bug by splitting on the encoded key and sending the input in multiple commands instead. Geckodriver already sends an implicity NULL at the end of the sendKeys command which does work, does release the modifier and does not print any character. --- lib/Remote/RemoteWebElement.php | 24 +++++++++++++++-------- tests/functional/RemoteWebElementTest.php | 8 ++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 0ca59aba7..b4b640374 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -325,14 +325,20 @@ public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); + $params = []; if ($local_file === null) { if ($this->isW3cCompliant) { - $params = [ - 'text' => WebDriverKeys::encode($value, true), - ':id' => $this->id, - ]; + // Work around the Geckodriver NULL issue by splitting on NULL and calling sendKeys multiple times. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1494661. + $encodedValues = explode(WebDriverKeys::NULL, WebDriverKeys::encode($value, true)); + foreach ($encodedValues as $encodedValue) { + $params[] = [ + 'text' => $encodedValue, + ':id' => $this->id, + ]; + } } else { - $params = [ + $params[] = [ 'value' => WebDriverKeys::encode($value), ':id' => $this->id, ]; @@ -348,19 +354,21 @@ public function sendKeys($value) $fileName = $local_file; } - $params = [ + $params[] = [ 'text' => $fileName, ':id' => $this->id, ]; } else { - $params = [ + $params[] = [ 'value' => WebDriverKeys::encode($this->upload($local_file)), ':id' => $this->id, ]; } } - $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); + foreach ($params as $param) { + $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $param); + } return $this; } diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index cacf16390..021e6ed1a 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -152,12 +152,20 @@ public function testShouldSendKeysToFormElement() $input->sendKeys(' baz'); $this->assertSame('foo bar baz', $input->getAttribute('value')); + $input->clear(); + $input->sendKeys([WebDriverKeys::SHIFT, 'H', WebDriverKeys::NULL, 'ello']); + $this->assertSame('Hello', $input->getAttribute('value')); + $textarea->clear(); $textarea->sendKeys('foo bar'); $this->assertSame('foo bar', $textarea->getAttribute('value')); $textarea->sendKeys(' baz'); $this->assertSame('foo bar baz', $textarea->getAttribute('value')); + $textarea->clear(); + $textarea->sendKeys([WebDriverKeys::SHIFT, 'H', WebDriverKeys::NULL, 'ello']); + $this->assertSame('Hello', $textarea->getAttribute('value')); + // Send keys as array $textarea->clear(); $textarea->sendKeys(['bat', 1, '3', ' ', 3, '7']); From 99ec34e9ee006a80042dd860f17010f13ddabed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 9 Sep 2020 17:44:26 +0200 Subject: [PATCH 045/265] Update travis build to latest geckodriver --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a773b2c6f..134f39466 100644 --- a/.travis.yml +++ b/.travis.yml @@ -132,7 +132,7 @@ before_script: unzip chromedriver_linux64.zip -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - - if [ "$GECKODRIVER" = "1" ]; then mkdir -p geckodriver; wget -q -t 3 https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz; tar xzf geckodriver-v0.26.0-linux64.tar.gz -C geckodriver; fi + - if [ "$GECKODRIVER" = "1" ]; then mkdir -p geckodriver; wget -q -t 3 https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz; tar xzf geckodriver-v0.27.0-linux64.tar.gz -C geckodriver; fi - if [ ! -f jar/selenium-server-standalone.jar ] && [ -n "$SELENIUM_SERVER" ]; then mkdir -p jar; if [ "$SELENIUM_SERVER" = "legacy" ]; then From d01e823a2605118f41638e7d8ebcce870ff70e1d Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Mon, 22 Jun 2020 23:55:53 +0300 Subject: [PATCH 046/265] Make sure that chromeOptions is an object when empty --- lib/Chrome/ChromeOptions.php | 14 ++++++---- lib/Remote/DesiredCapabilities.php | 8 ++++-- tests/unit/Remote/DesiredCapabilitiesTest.php | 28 +++++++++++++------ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index af99d3cfe..1b3c4cbdc 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -117,17 +117,19 @@ public function toCapabilities() } /** - * @return array + * @return \ArrayObject|array */ public function toArray() { - $options = $this->experimentalOptions; - // The selenium server expects a 'dictionary' instead of a 'list' when // reading the chrome option. However, an empty array in PHP will be - // converted to a 'list' instead of a 'dictionary'. To fix it, we always - // set the 'binary' to avoid returning an empty array. - $options['binary'] = $this->binary; + // converted to a 'list' instead of a 'dictionary'. To fix it, we work + // with `ArrayObject` + $options = new \ArrayObject($this->experimentalOptions); + + if (!empty($this->binary)) { + $options['binary'] = $this->binary; + } if (!empty($this->arguments)) { $options['args'] = $this->arguments; diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index e022ccecd..5fcf651d9 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -235,9 +235,11 @@ public function toW3cCompatibleArray() // Convert ChromeOptions if (array_key_exists(ChromeOptions::CAPABILITY, $ossCapabilities)) { if (array_key_exists(ChromeOptions::CAPABILITY_W3C, $ossCapabilities)) { - $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = array_merge_recursive( - $ossCapabilities[ChromeOptions::CAPABILITY], - $ossCapabilities[ChromeOptions::CAPABILITY_W3C] + $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = new \ArrayObject( + array_merge_recursive( + (array) $ossCapabilities[ChromeOptions::CAPABILITY], + (array) $ossCapabilities[ChromeOptions::CAPABILITY_W3C] + ) ); } else { $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = $ossCapabilities[ChromeOptions::CAPABILITY]; diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index f74eb754d..f24d8eb40 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -213,15 +213,24 @@ public function provideW3cCapabilities() 'vendor:prefix' => 'vendor extension should be kept', ], ], + 'chromeOptions should be an object if empty' => [ + new DesiredCapabilities([ + ChromeOptions::CAPABILITY => new ChromeOptions(), + ]), + [ + 'goog:chromeOptions' => new \ArrayObject(), + ], + ], 'chromeOptions should be converted' => [ new DesiredCapabilities([ ChromeOptions::CAPABILITY => $chromeOptions, ]), [ - 'goog:chromeOptions' => [ - 'args' => ['--headless'], - 'binary' => '', - ], + 'goog:chromeOptions' => new \ArrayObject( + [ + 'args' => ['--headless'], + ] + ), ], ], 'chromeOptions should be merged if already defined' => [ @@ -233,11 +242,12 @@ public function provideW3cCapabilities() ], ]), [ - 'goog:chromeOptions' => [ - 'args' => ['--headless', 'window-size=1024,768'], - 'binary' => '', - 'debuggerAddress' => '127.0.0.1:38947', - ], + 'goog:chromeOptions' => new \ArrayObject( + [ + 'args' => ['--headless', 'window-size=1024,768'], + 'debuggerAddress' => '127.0.0.1:38947', + ] + ), ], ], 'firefox_profile should be converted' => [ From 33284ca385542df62a15ee53cc82eef33ca0c407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 10 Sep 2020 13:45:09 +0200 Subject: [PATCH 047/265] Run SauceLabs tests via GitHub Actions --- .github/workflows/sauce-labs.yaml | 66 ++++++++++++++++++++++++++ .travis.yml | 46 +----------------- composer.json | 1 + tests/functional/WebDriverTestCase.php | 26 +++++++--- 4 files changed, 88 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/sauce-labs.yaml diff --git a/.github/workflows/sauce-labs.yaml b/.github/workflows/sauce-labs.yaml new file mode 100644 index 000000000..9398909ae --- /dev/null +++ b/.github/workflows/sauce-labs.yaml @@ -0,0 +1,66 @@ +name: Sauce Labs + +on: + push: + schedule: + - cron: '0 3 * * *' + +jobs: + tests: + runs-on: ubuntu-latest + env: + SAUCELABS: 1 + SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} + SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} + strategy: + fail-fast: false + matrix: + include: + - { name: "Firefox 47, OSS protocol", BROWSER_NAME: "firefox", VERSION: "47.0", PLATFORM: "Windows 10", DISABLE_W3C_PROTOCOL: "1", tunnel-id: "gh-1" } + - { name: "Chrome 74, OSS protocol", BROWSER_NAME: "chrome", VERSION: "74.0", PLATFORM: "Windows 10", DISABLE_W3C_PROTOCOL: "1", tunnel-id: "gh-2" } # 74 is the last version which don't use W3C WebDriver by default + - { name: "Chrome latest, W3C protocol", BROWSER_NAME: "chrome", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-3" } + - { name: "Edge latest, W3C protocol", BROWSER_NAME: "MicrosoftEdge", VERSION: "latest", PLATFORM: "Windows 10", tunnel-id: "gh-4" } + + name: ${{ matrix.name }} (${{ matrix.tunnel-id }}) + steps: + - uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: mbstring, intl, zip + + - name: Install dependencies + run: composer update --no-interaction + + - name: Start local PHP server + run: | + php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & + + - name: Start Sauce Connect + uses: saucelabs/sauce-connect-action@master + with: + username: ${{ secrets.SAUCE_USERNAME }} + accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} + tunnelIdentifier: ${{ matrix.tunnel-id }} + + - name: Run tests + env: + BROWSER_NAME: ${{ matrix.BROWSER_NAME }} + VERSION: ${{ matrix.VERSION }} + PLATFORM: ${{ matrix.PLATFORM }} + DISABLE_W3C_PROTOCOL: ${{ matrix.DISABLE_W3C_PROTOCOL }} + SAUCE_TUNNEL_IDENTIFIER: ${{ matrix.tunnel-id }} + run: | + if [ -n "$SAUCELABS" ]; then EXCLUDE_GROUP+="exclude-saucelabs,"; fi + if [ "$BROWSER_NAME" = "MicrosoftEdge" ]; then EXCLUDE_GROUP+="exclude-edge,"; fi + if [ "$BROWSER_NAME" = "firefox" ]; then EXCLUDE_GROUP+="exclude-firefox,"; fi + if [ "$BROWSER_NAME" = "chrome" ]; then EXCLUDE_GROUP+="exclude-chrome,"; fi + if [ -n "$EXCLUDE_GROUP" ]; then EXTRA_PARAMS+=" --exclude-group $EXCLUDE_GROUP"; fi + ./vendor/bin/phpunit --coverage-clover ./logs/coverage-clover.xml $EXTRA_PARAMS + + - name: Print logs + if: ${{ always() }} + run: | + if [ -f ./logs/php-server.log ]; then cat ./logs/php-server.log; fi diff --git a/.travis.yml b/.travis.yml index 134f39466..a627edc09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,51 +70,6 @@ matrix: addons: chrome: stable - # Saucelabs builds - - name: 'Sauce Labs, Firefox 47, OSS protocol' - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" DISABLE_W3C_PROTOCOL="1" - before_script: - - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - - until $(echo | nc localhost 8000); do sleep 1; echo waiting for PHP server on port 8000...; done; echo "PHP server started" - addons: - sauce_connect: true - jwt: - secure: 5s8iQfH1dHgEm0DeP9VZ/MCzCeiE/HnMWqPFzRmg6VD2qJ53oYdseo8j+QCbE25MIwoSnIbKzlnbCN6fVzZc/0S7Mo45xJiq8xVLPSdMjDoOeqYE4of+t5Srq4iSzGLPCLiMTtB4xDEl6blUVGhYxN5rA/tVN+cVtLNQvo3ovRon3Mw3MqR4pgCE6PofcLXtyJc3KuOBlUJLWdPGRdlZrpKWE5ogyj4a1h4bVwidckZqkOF+gm58Gf0zVfFazDQFIw2Xuq7SZmiNgdOD5yUEePkrMhy2tbOlPNAIgHCpzHldv5Y+GYyxIYHZ0mGlGxHrfjrcAoSA6r2iXB9q2ijLVwqOARpcvGcBzZBil9aMAHRIXHAOV9Ihv4velrzmiLKADtD60Bfj2zzntGYZA3EGucitMMkkP7vfAa769i5QWK1Lniq3+VUuGNVjRzl4GuQPpc0wMWeJvQGc5Uf9Kk/sOCkPp0SPWcZ6nNAUebRy3V5OoADA9IntyXxfTlZdOHSbJTsG+eOGve0uLGRAOS+oeCstO7Gk4e/Ylozju+ixkINEY7HHDGt6AyHGtjPdy08Y0XrIqs0JMxsHKrtTVNxDjIFKbMees+vtxU3DEr/tNo1sTo34ieGKZP2Cp5mG/IrcjD1saebUaCngQO3QfeuKcU8pBTR7l7PtFNHm3HrmdkY= - - - name: 'Sauce Labs, Chrome 74, OSS protocol' - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" PLATFORM="Windows 10" DISABLE_W3C_PROTOCOL="1" # 74 is the last version which don't use W3C WebDriver by default - before_script: - - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - - until $(echo | nc localhost 8000); do sleep 1; echo waiting for PHP server on port 8000...; done; echo "PHP server started" - addons: - sauce_connect: true - jwt: - secure: 5s8iQfH1dHgEm0DeP9VZ/MCzCeiE/HnMWqPFzRmg6VD2qJ53oYdseo8j+QCbE25MIwoSnIbKzlnbCN6fVzZc/0S7Mo45xJiq8xVLPSdMjDoOeqYE4of+t5Srq4iSzGLPCLiMTtB4xDEl6blUVGhYxN5rA/tVN+cVtLNQvo3ovRon3Mw3MqR4pgCE6PofcLXtyJc3KuOBlUJLWdPGRdlZrpKWE5ogyj4a1h4bVwidckZqkOF+gm58Gf0zVfFazDQFIw2Xuq7SZmiNgdOD5yUEePkrMhy2tbOlPNAIgHCpzHldv5Y+GYyxIYHZ0mGlGxHrfjrcAoSA6r2iXB9q2ijLVwqOARpcvGcBzZBil9aMAHRIXHAOV9Ihv4velrzmiLKADtD60Bfj2zzntGYZA3EGucitMMkkP7vfAa769i5QWK1Lniq3+VUuGNVjRzl4GuQPpc0wMWeJvQGc5Uf9Kk/sOCkPp0SPWcZ6nNAUebRy3V5OoADA9IntyXxfTlZdOHSbJTsG+eOGve0uLGRAOS+oeCstO7Gk4e/Ylozju+ixkINEY7HHDGt6AyHGtjPdy08Y0XrIqs0JMxsHKrtTVNxDjIFKbMees+vtxU3DEr/tNo1sTo34ieGKZP2Cp5mG/IrcjD1saebUaCngQO3QfeuKcU8pBTR7l7PtFNHm3HrmdkY= - - - name: 'Sauce Labs, Chrome latest, W3C protocol' - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" - before_script: - - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - - until $(echo | nc localhost 8000); do sleep 1; echo waiting for PHP server on port 8000...; done; echo "PHP server started" - addons: - sauce_connect: true - jwt: - secure: 5s8iQfH1dHgEm0DeP9VZ/MCzCeiE/HnMWqPFzRmg6VD2qJ53oYdseo8j+QCbE25MIwoSnIbKzlnbCN6fVzZc/0S7Mo45xJiq8xVLPSdMjDoOeqYE4of+t5Srq4iSzGLPCLiMTtB4xDEl6blUVGhYxN5rA/tVN+cVtLNQvo3ovRon3Mw3MqR4pgCE6PofcLXtyJc3KuOBlUJLWdPGRdlZrpKWE5ogyj4a1h4bVwidckZqkOF+gm58Gf0zVfFazDQFIw2Xuq7SZmiNgdOD5yUEePkrMhy2tbOlPNAIgHCpzHldv5Y+GYyxIYHZ0mGlGxHrfjrcAoSA6r2iXB9q2ijLVwqOARpcvGcBzZBil9aMAHRIXHAOV9Ihv4velrzmiLKADtD60Bfj2zzntGYZA3EGucitMMkkP7vfAa769i5QWK1Lniq3+VUuGNVjRzl4GuQPpc0wMWeJvQGc5Uf9Kk/sOCkPp0SPWcZ6nNAUebRy3V5OoADA9IntyXxfTlZdOHSbJTsG+eOGve0uLGRAOS+oeCstO7Gk4e/Ylozju+ixkINEY7HHDGt6AyHGtjPdy08Y0XrIqs0JMxsHKrtTVNxDjIFKbMees+vtxU3DEr/tNo1sTo34ieGKZP2Cp5mG/IrcjD1saebUaCngQO3QfeuKcU8pBTR7l7PtFNHm3HrmdkY= - - - name: 'Sauce Labs, Edge latest, W3C protocol' - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="latest" PLATFORM="Windows 10" - before_script: - - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & - - until $(echo | nc localhost 8000); do sleep 1; echo waiting for PHP server on port 8000...; done; echo "PHP server started" - addons: - sauce_connect: true - jwt: - secure: 5s8iQfH1dHgEm0DeP9VZ/MCzCeiE/HnMWqPFzRmg6VD2qJ53oYdseo8j+QCbE25MIwoSnIbKzlnbCN6fVzZc/0S7Mo45xJiq8xVLPSdMjDoOeqYE4of+t5Srq4iSzGLPCLiMTtB4xDEl6blUVGhYxN5rA/tVN+cVtLNQvo3ovRon3Mw3MqR4pgCE6PofcLXtyJc3KuOBlUJLWdPGRdlZrpKWE5ogyj4a1h4bVwidckZqkOF+gm58Gf0zVfFazDQFIw2Xuq7SZmiNgdOD5yUEePkrMhy2tbOlPNAIgHCpzHldv5Y+GYyxIYHZ0mGlGxHrfjrcAoSA6r2iXB9q2ijLVwqOARpcvGcBzZBil9aMAHRIXHAOV9Ihv4velrzmiLKADtD60Bfj2zzntGYZA3EGucitMMkkP7vfAa769i5QWK1Lniq3+VUuGNVjRzl4GuQPpc0wMWeJvQGc5Uf9Kk/sOCkPp0SPWcZ6nNAUebRy3V5OoADA9IntyXxfTlZdOHSbJTsG+eOGve0uLGRAOS+oeCstO7Gk4e/Ylozju+ixkINEY7HHDGt6AyHGtjPdy08Y0XrIqs0JMxsHKrtTVNxDjIFKbMees+vtxU3DEr/tNo1sTo34ieGKZP2Cp5mG/IrcjD1saebUaCngQO3QfeuKcU8pBTR7l7PtFNHm3HrmdkY= - cache: directories: - $HOME/.composer/cache @@ -156,6 +111,7 @@ before_script: script: - if [ -n "$SAUCELABS" ]; then EXCLUDE_GROUP+="exclude-saucelabs,"; fi + - if [ -n "$SAUCELABS" ]; then export SAUCE_TUNNEL_IDENTIFIER="$TRAVIS_JOB_NUMBER"; fi - if [ "$BROWSER_NAME" = "MicrosoftEdge" ]; then EXCLUDE_GROUP+="exclude-edge,"; fi - if [ "$BROWSER_NAME" = "firefox" ]; then EXCLUDE_GROUP+="exclude-firefox,"; fi - if [ "$BROWSER_NAME" = "chrome" ]; then EXCLUDE_GROUP+="exclude-chrome,"; fi diff --git a/composer.json b/composer.json index 7c0f1fdc3..b94e7ac94 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", + "ondram/ci-detector": "^2.1 || ^3.5", "php-coveralls/php-coveralls": "^2.0", "php-mock/php-mock-phpunit": "^1.1", "php-parallel-lint/php-parallel-lint": "^1.2", diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 9718cd671..76c79bb7d 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -8,6 +8,7 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\WebDriverBrowserType; +use OndraM\CiDetector\CiDetector; use PHPUnit\Framework\TestCase; /** @@ -174,9 +175,18 @@ protected function setUpSauceLabs() $name = get_class($this) . '::' . $this->getName(); $tags = [get_class($this)]; - if (getenv('TRAVIS_JOB_NUMBER')) { - $tunnelIdentifier = getenv('TRAVIS_JOB_NUMBER'); - $build = getenv('TRAVIS_JOB_NUMBER'); + $ciDetector = new CiDetector(); + if ($ciDetector->isCiDetected()) { + $ci = $ciDetector->detect(); + if (!empty($ci->getBuildNumber())) { + // SAUCE_TUNNEL_IDENTIFIER appended as a workaround for GH actions not having environment value + // to distinguish runs of the matrix + $build = $ci->getBuildNumber() . '.' . getenv('SAUCE_TUNNEL_IDENTIFIER'); + } + } + + if (getenv('SAUCE_TUNNEL_IDENTIFIER')) { + $tunnelIdentifier = getenv('SAUCE_TUNNEL_IDENTIFIER'); } if (!getenv('DISABLE_W3C_PROTOCOL')) { @@ -184,17 +194,21 @@ protected function setUpSauceLabs() 'name' => $name, 'tags' => $tags, ]; - if (isset($tunnelIdentifier, $build)) { - $sauceOptions['tunnelIdentifier'] = $tunnelIdentifier; + if (isset($build)) { $sauceOptions['build'] = $build; } + if (isset($tunnelIdentifier)) { + $sauceOptions['tunnelIdentifier'] = $tunnelIdentifier; + } $this->desiredCapabilities->setCapability('sauce:options', (object) $sauceOptions); } else { $this->desiredCapabilities->setCapability('name', $name); $this->desiredCapabilities->setCapability('tags', $tags); - if (isset($tunnelIdentifier, $build)) { + if (isset($tunnelIdentifier)) { $this->desiredCapabilities->setCapability('tunnel-identifier', $tunnelIdentifier); + } + if (isset($build)) { $this->desiredCapabilities->setCapability('build', $build); } } From 0ac00d8deca08b2c5a94dd9f25533588da94c8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Oct 2020 20:22:34 +0200 Subject: [PATCH 048/265] Disable composer memory limit in travis builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a627edc09..31c92bd5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,7 @@ env: - DISPLAY=:99.0 - BROWSER_NAME="htmlunit" - SELENIUM_SERVER="https://selenium-release.storage.googleapis.com/3.14/selenium-server-standalone-3.14.0.jar" # Latest version including HtmlUnit + - COMPOSER_MEMORY_LIMIT=-1 services: - xvfb From d5790a8daea2172dc3923b1565aed8df12922177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Oct 2020 21:09:25 +0200 Subject: [PATCH 049/265] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33c219c31..b4ceccbda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Make `alertIsPresent()` condition working in W3C mode. - `RemoteWebDriver::create()` cannot be used without providing the second parameter (which is in fact optional). +- `ChromeDriver::start()` starts in inconsistent state mixing W3C/OSS mode. +- Modifier keys are not released when sending NULL key in GeckoDriver (workaround for GeckoDriver bug [1494661](https://bugzilla.mozilla.org/show_bug.cgi?id=1494661)). +- Do not set unnecessary `binary` value of `goog:chromeOptions` while keep the object in proper data type required by ChromeDriver. ## 1.8.2 - 2020-03-04 ### Changed From fb0fc4cb01c70a7790a5fcc91d461b88c83174a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Oct 2020 21:10:04 +0200 Subject: [PATCH 050/265] Release version 1.8.3 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ceccbda..c4f091d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.8.3 - 2020-10-06 ### Fixed - Make `alertIsPresent()` condition working in W3C mode. - `RemoteWebDriver::create()` cannot be used without providing the second parameter (which is in fact optional). From 841b5b7e099aed453b67b617014f79b822688f90 Mon Sep 17 00:00:00 2001 From: Michael Steininger <11192306+codegain@users.noreply.github.com> Date: Fri, 18 Sep 2020 22:38:24 +0200 Subject: [PATCH 051/265] Add same-site support for cookies (fixes #705) --- lib/Cookie.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index 0e1436089..1005bd0dc 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -58,6 +58,9 @@ public static function createFromArray(array $cookieArray) if (isset($cookieArray['httpOnly'])) { $cookie->setHttpOnly($cookieArray['httpOnly']); } + if (isset($cookieArray['sameSite'])) { + $cookie->setSameSite($cookieArray['sameSite']); + } return $cookie; } @@ -172,6 +175,24 @@ public function isHttpOnly() return $this->offsetGet('httpOnly'); } + /** + * The cookie's same-site value. + * + * @param string $sameSite + */ + public function setSameSite($sameSite) + { + $this->offsetSet('sameSite', $sameSite); + } + + /** + * @return string|null + */ + public function getSameSite() + { + return $this->offsetGet('sameSite'); + } + /** * @return array */ From 7efbcb88a34b1df5aeaa1c40bf20d96388633d09 Mon Sep 17 00:00:00 2001 From: Michael Steininger <11192306+codegain@users.noreply.github.com> Date: Tue, 6 Oct 2020 22:07:06 +0200 Subject: [PATCH 052/265] added unit tests for same-site cookie attribute --- tests/unit/CookieTest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 621fb65c0..cced57524 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -17,6 +17,7 @@ public function testShouldSetAllProperties() $cookie->setExpiry(1485388387); $cookie->setSecure(true); $cookie->setHttpOnly(true); + $cookie->setSameSite('Lax'); $this->assertSame('cookieName', $cookie->getName()); $this->assertSame('someValue', $cookie->getValue()); @@ -25,6 +26,7 @@ public function testShouldSetAllProperties() $this->assertSame(1485388387, $cookie->getExpiry()); $this->assertTrue($cookie->isSecure()); $this->assertTrue($cookie->isHttpOnly()); + $this->assertSame('Lax', $cookie->getSameSite()); return $cookie; } @@ -44,6 +46,7 @@ public function testShouldBeConvertibleToArray(Cookie $cookie) 'expiry' => 1485388387, 'secure' => true, 'httpOnly' => true, + 'sameSite' => 'Lax', ], $cookie->toArray() ); @@ -84,6 +87,7 @@ public function testShouldProvideArrayAccessToProperties(Cookie $cookie) $this->assertSame(1485388387, $cookie['expiry']); $this->assertTrue($cookie['secure']); $this->assertTrue($cookie['httpOnly']); + $this->assertSame('Lax', $cookie['sameSite']); $cookie->offsetSet('domain', 'bar.com'); $this->assertSame('bar.com', $cookie['domain']); @@ -122,6 +126,10 @@ public function testShouldBeCreatableFromAnArrayWithBasicValues() $this->assertArrayNotHasKey('httpOnly', $cookie); $this->assertNull($cookie['httpOnly']); $this->assertNull($cookie->isHttpOnly()); + + $this->assertArrayNotHasKey('sameSite', $cookie); + $this->assertNull($cookie['sameSite']); + $this->assertNull($cookie->getSameSite()); } public function testShouldBeCreatableFromAnArrayWithAllValues() @@ -134,6 +142,7 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() 'expiry' => 1485388333, 'secure' => false, 'httpOnly' => false, + 'sameSite' => 'Lax', ]; $cookie = Cookie::createFromArray($sourceArray); @@ -145,6 +154,7 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() $this->assertSame(1485388333, $cookie['expiry']); $this->assertFalse($cookie['secure']); $this->assertFalse($cookie['httpOnly']); + $this->assertSame('Lax', $cookie['sameSite']); } /** From 4fbb161f0a480b48cce0116af581c61c3fd8c347 Mon Sep 17 00:00:00 2001 From: Michael Steininger <11192306+codegain@users.noreply.github.com> Date: Tue, 6 Oct 2020 22:38:12 +0200 Subject: [PATCH 053/265] Updated testShouldNotContainNullValues --- tests/unit/CookieTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index cced57524..d58419a9a 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -67,6 +67,7 @@ public function testShouldNotContainNullValues() $cookie->setHttpOnly(null); $cookie->setPath(null); + $cookie->setSameSite(null); $cookieArray = $cookie->toArray(); foreach ($cookieArray as $key => $value) { From c106a41a1a75e3e3e163f6a3f58aeec80e434b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 6 Oct 2020 23:12:46 +0200 Subject: [PATCH 054/265] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4f091d8a..5009ff1dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- Support of SameSite cookie property. ## 1.8.3 - 2020-10-06 ### Fixed From 9e8aeac43ec9e3e2f530c7662ecb67cbb4150495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 16 Nov 2020 15:26:32 +0100 Subject: [PATCH 055/265] Move phpstan path and level config to file --- phpstan.neon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index b0777e2c2..846484976 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,9 @@ parameters: + level: 2 + paths: + - lib/ + - tests/ + ignoreErrors: # To be fixed: - '#Call to an undefined method Facebook\\WebDriver\\WebDriver::getTouch\(\)#' From 5898a74ce2bb87b2b755d322069200c7460fe907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 16 Nov 2020 15:30:31 +0100 Subject: [PATCH 056/265] Workaround for broken PHPStan parallel processing --- composer.json | 3 ++- phpstan-webdriverkeys.neon | 6 ++++++ phpstan.neon | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 phpstan-webdriverkeys.neon diff --git a/composer.json b/composer.json index b94e7ac94..0a3e81ae3 100644 --- a/composer.json +++ b/composer.json @@ -68,7 +68,8 @@ "scripts": { "analyze": [ "vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", - "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" + "vendor/bin/phpstan.phar analyze -c phpstan.neon --ansi", + "vendor/bin/phpstan.phar analyze -c phpstan-webdriverkeys.neon --ansi" ], "codestyle:check": [ "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff --dry-run -vvv --ansi", diff --git a/phpstan-webdriverkeys.neon b/phpstan-webdriverkeys.neon new file mode 100644 index 000000000..799558876 --- /dev/null +++ b/phpstan-webdriverkeys.neon @@ -0,0 +1,6 @@ +# WebDriverKeys.php breaks PHPStan parallel processing, so we must analyze the file separately. +# See https://github.com/phpstan/phpstan/issues/3956 and https://github.com/clue/reactphp-ndjson/issues/21 +parameters: + level: 2 + paths: + - lib/WebDriverKeys.php diff --git a/phpstan.neon b/phpstan.neon index 846484976..4b12710a5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -16,3 +16,6 @@ parameters: path: 'lib/Remote/RemoteWebDriver.php' inferPrivatePropertyTypeFromConstructor: true + + excludes_analyse: + - lib/WebDriverKeys.php From 4d2eb507e6308b8794ddf0b2e75be245bbff967c Mon Sep 17 00:00:00 2001 From: William Desportes Date: Mon, 16 Nov 2020 16:22:05 +0100 Subject: [PATCH 057/265] Scripts and configuration changes for PHP 8 Co-Authored-By: Dries Vints --- .gitattributes | 1 + .github/workflows/php.yaml | 2 +- .gitignore | 1 + .php_cs.dist | 2 +- .travis.yml | 22 ++++++++++++++++++---- composer.json | 10 ++++------ scripts/apply-phpunit-patches.sh | 20 ++++++++++++++++++++ 7 files changed, 46 insertions(+), 12 deletions(-) create mode 100755 scripts/apply-phpunit-patches.sh diff --git a/.gitattributes b/.gitattributes index 3123b82f2..8a5e272b2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ * text=auto +/scripts export-ignore /tests export-ignore /.gitattributes export-ignore /.gitignore export-ignore diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index c96bec74e..3065ff732 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v2 - name: Setup PHP - uses: shivammathur/setup-php@v1 + uses: shivammathur/setup-php@v2 with: php-version: '7.4' extensions: mbstring, intl, zip diff --git a/.gitignore b/.gitignore index deb25f3ae..d5605367c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ composer.phar composer.lock vendor .php_cs.cache +.phpunit.result.cache phpunit.xml logs/ diff --git a/.php_cs.dist b/.php_cs.dist index b771f8b04..a438f2095 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -52,7 +52,7 @@ return PhpCsFixer\Config::create() 'ordered_class_elements' => true, 'ordered_imports' => true, 'php_unit_construct' => true, - 'php_unit_dedicate_assert' => true, + 'php_unit_dedicate_assert' => false, 'php_unit_expectation' => ['target' => '5.6'], 'php_unit_method_casing' => ['case' => 'camel_case'], 'php_unit_mock' => true, diff --git a/.travis.yml b/.travis.yml index 31c92bd5d..b8f993e44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: php dist: xenial php: - - '5.6' - - '7.0' - '7.1' - '7.2' - '7.3' @@ -21,10 +19,25 @@ services: matrix: include: + - name: 'PHP nightly build' + php: 'nightly' + env: DEPENDENCIES="--ignore-platform-req=php" + + - name: 'PHP 7.0' + php: '7.0' + env: RUN_PHPUNIT_PATCHES="true" + + - name: 'PHP 5.6' + php: '5.6' + env: RUN_PHPUNIT_PATCHES="true" + # Build with lowest possible dependencies on lowest possible PHP - name: 'Lowest dependencies build' php: '5.6' - env: DEPENDENCIES="--prefer-lowest" + env: + - DEPENDENCIES="--prefer-lowest" + - RUN_PHPUNIT_PATCHES="true" + # Firefox inside Travis environment - name: 'Firefox 45 on Travis (OSS protocol); via legacy Selenium server' @@ -80,6 +93,7 @@ install: - travis_retry composer update --no-interaction $DEPENDENCIES before_script: + - if [ "$RUN_PHPUNIT_PATCHES" = "true" ]; then ./scripts/apply-phpunit-patches.sh; fi - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROME_VERSION=$(google-chrome --product-version); @@ -126,4 +140,4 @@ after_script: - if [ -f ./logs/chromedriver.log ]; then cat ./logs/chromedriver.log; fi after_success: - - travis_retry php vendor/bin/php-coveralls -v + - if [ "$TRAVIS_PHP_VERSION" != "nightly" ]; then travis_retry php vendor/bin/php-coveralls -v; fi diff --git a/composer.json b/composer.json index 0a3e81ae3..88f215101 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "homepage": "https://github.com/php-webdriver/php-webdriver", "license": "MIT", "require": { - "php": "^5.6 || ~7.0", + "php": "^5.6 || ~7.0 || ^8.0", "ext-curl": "*", "ext-json": "*", "ext-zip": "*", @@ -25,12 +25,10 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", "ondram/ci-detector": "^2.1 || ^3.5", - "php-coveralls/php-coveralls": "^2.0", - "php-mock/php-mock-phpunit": "^1.1", + "php-coveralls/php-coveralls": "^2.4", + "php-mock/php-mock-phpunit": "^1.1 || ^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", - "phpunit/phpunit": "^5.7", - "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", - "sminnee/phpunit-mock-objects": "^3.4", + "phpunit/phpunit": "^5.7 || ^7 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5", "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0" }, diff --git a/scripts/apply-phpunit-patches.sh b/scripts/apply-phpunit-patches.sh new file mode 100755 index 000000000..ee1dd00f3 --- /dev/null +++ b/scripts/apply-phpunit-patches.sh @@ -0,0 +1,20 @@ +#!/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 \ No newline at end of file From 6583dd2fddac651d75ead9189cacc9fec0ef504f Mon Sep 17 00:00:00 2001 From: William Desportes Date: Mon, 16 Nov 2020 12:17:56 +0100 Subject: [PATCH 058/265] PHP 8 and phpunit compatibility changes on source files tests/* --- .../Chrome/ChromeDriverServiceTest.php | 2 +- tests/functional/Chrome/ChromeDriverTest.php | 4 +- tests/functional/RemoteTargetLocatorTest.php | 28 +++++----- .../functional/RemoteWebDriverCreateTest.php | 6 +-- .../RemoteWebDriverFindElementTest.php | 4 +- tests/functional/RemoteWebDriverTest.php | 18 +++---- tests/functional/RemoteWebElementTest.php | 6 +-- .../ReportSauceLabsStatusListener.php | 52 ++++++++++++++++--- tests/functional/WebDriverActionsTest.php | 3 +- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverCheckboxesTest.php | 2 +- .../WebDriverOptionsCookiesTest.php | 2 +- tests/functional/WebDriverRadiosTest.php | 2 +- tests/functional/WebDriverSelectTest.php | 2 +- tests/functional/WebDriverTestCase.php | 22 ++++++-- .../WebDriverButtonReleaseActionTest.php | 6 +-- .../Internal/WebDriverClickActionTest.php | 6 +-- .../WebDriverClickAndHoldActionTest.php | 6 +-- .../WebDriverContextClickActionTest.php | 6 +-- .../Internal/WebDriverCoordinatesTest.php | 22 +++----- .../WebDriverDoubleClickActionTest.php | 6 +-- .../Internal/WebDriverKeyDownActionTest.php | 8 +-- .../Internal/WebDriverKeyUpActionTest.php | 8 +-- .../Internal/WebDriverMouseMoveActionTest.php | 6 +-- .../WebDriverMouseToOffsetActionTest.php | 6 +-- .../Internal/WebDriverSendKeysActionTest.php | 8 +-- tests/unit/Remote/HttpCommandExecutorTest.php | 2 +- tests/unit/Remote/RemoteWebDriverTest.php | 2 +- tests/unit/WebDriverExpectedConditionTest.php | 4 +- tests/unit/WebDriverOptionsTest.php | 4 +- 30 files changed, 151 insertions(+), 104 deletions(-) diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php index a21727781..1f5baa4f9 100644 --- a/tests/functional/Chrome/ChromeDriverServiceTest.php +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -11,7 +11,7 @@ */ class ChromeDriverServiceTest extends TestCase { - protected function setUp() + protected function setUp(): void { if (!getenv('BROWSER_NAME') === 'chrome' || getenv('SAUCELABS') || !getenv('CHROMEDRIVER_PATH')) { $this->markTestSkipped('ChromeDriverServiceTest is run only when running against local chrome'); diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index 26c9de81d..fd7581008 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -16,14 +16,14 @@ class ChromeDriverTest extends TestCase /** @var ChromeDriver */ protected $driver; - protected function setUp() + protected function setUp(): void { if (!getenv('BROWSER_NAME') === 'chrome' || getenv('SAUCELABS') || !getenv('CHROMEDRIVER_PATH')) { $this->markTestSkipped('ChromeDriverServiceTest is run only when running against local chrome'); } } - protected function tearDown() + protected function tearDown(): void { if ($this->driver instanceof RemoteWebDriver && $this->driver->getCommandExecutor() !== null) { $this->driver->quit(); diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index 8ee5c96bc..9128502b2 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -23,7 +23,7 @@ public function testShouldSwitchToWindow() ); // At first the window should not be switched - $this->assertContains('open_new_window.html', $this->driver->getCurrentURL()); + $this->compatAssertStringContainsString('open_new_window.html', $this->driver->getCurrentURL()); $this->assertSame($originalWindowHandle, $this->driver->getWindowHandle()); /** @@ -38,7 +38,7 @@ public function testShouldSwitchToWindow() $this->driver->switchTo()->window($newWindowHandle); // After switchTo() is called, the active window should be changed - $this->assertContains('index.html', $this->driver->getCurrentURL()); + $this->compatAssertStringContainsString('index.html', $this->driver->getCurrentURL()); $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); } @@ -64,25 +64,25 @@ public function testShouldSwitchToFrameByItsId() $this->driver->get($this->getTestPageUrl('page_with_frame.html')); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->assertContains($firstChildFrame, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(1); - $this->assertContains($secondChildFrame, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($secondChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->frame(null); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->assertContains($firstChildFrame, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->defaultContent(); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); } public function testShouldSwitchToParentFrame() @@ -92,13 +92,13 @@ public function testShouldSwitchToParentFrame() $this->driver->get($this->getTestPageUrl('page_with_frame.html')); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); $this->driver->switchTo()->frame(0); - $this->assertContains($firstChildFrame, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($firstChildFrame, $this->driver->getPageSource()); $this->driver->switchTo()->parent(); - $this->assertContains($parentPage, $this->driver->getPageSource()); + $this->compatAssertStringContainsString($parentPage, $this->driver->getPageSource()); } public function testShouldSwitchToFrameByElement() @@ -108,7 +108,7 @@ public function testShouldSwitchToFrameByElement() $element = $this->driver->findElement(WebDriverBy::id('iframe_content')); $this->driver->switchTo()->frame($element); - $this->assertContains('This is the content of the iFrame', $this->driver->getPageSource()); + $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } /** @@ -139,6 +139,6 @@ public function testShouldAcceptStringAsFrameIdInJsonWireMode() $this->driver->switchTo()->frame('iframe_content'); - $this->assertContains('This is the content of the iFrame', $this->driver->getPageSource()); + $this->compatAssertStringContainsString('This is the content of the iFrame', $this->driver->getPageSource()); } } diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index db379964d..29302e16a 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -32,7 +32,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); $this->assertNotEmpty($this->driver->getCommandExecutor()->getAddressOfRemoteServer()); - $this->assertInternalType('string', $this->driver->getSessionID()); + $this->assertTrue(is_string($this->driver->getSessionID())); $this->assertNotEmpty($this->driver->getSessionID()); $returnedCapabilities = $this->driver->getCapabilities(); @@ -104,7 +104,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->requestTimeout ); $originalDriver->get($this->getTestPageUrl('index.html')); - $this->assertContains('/index.html', $originalDriver->getCurrentURL()); + $this->compatAssertStringContainsString('/index.html', $originalDriver->getCurrentURL()); // Store session ID $sessionId = $originalDriver->getSessionID(); @@ -114,7 +114,7 @@ public function testShouldCreateInstanceFromExistingSessionId() $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl, null, null, $isW3cCompliant); // Check we reused the previous instance (window) and it has the same URL - $this->assertContains('/index.html', $this->driver->getCurrentURL()); + $this->compatAssertStringContainsString('/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/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 4ac265381..5a7d18e70 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->assertInternalType('array', $elements); + $this->assertTrue(is_array($elements)); $this->assertCount(0, $elements); } @@ -44,7 +44,7 @@ public function testShouldFindMultipleElements() $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); - $this->assertInternalType('array', $elements); + $this->assertTrue(is_array($elements)); $this->assertCount(5, $elements); $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 28b4e52ab..ae289de5d 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -43,8 +43,8 @@ public function testShouldGetPageSource() $this->driver->get($this->getTestPageUrl('index.html')); $source = $this->driver->getPageSource(); - $this->assertContains('

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

    ', $source); + $this->compatAssertStringContainsString('Welcome to the php-webdriver testing page.', $source); } /** @@ -63,7 +63,7 @@ public function testShouldGetSessionId() $sessionId = $this->driver->getSessionID(); - $this->assertInternalType('string', $sessionId); + $this->assertTrue(is_string($sessionId)); $this->assertNotEmpty($sessionId); } @@ -79,7 +79,7 @@ public function testShouldGetAllSessions() $sessions = RemoteWebDriver::getAllSessions($this->serverUrl, 30000); - $this->assertInternalType('array', $sessions); + $this->assertTrue(is_array($sessions)); $this->assertCount(1, $sessions); $this->assertArrayHasKey('capabilities', $sessions[0]); @@ -124,7 +124,7 @@ public function testShouldGetWindowHandles() $windowHandle = $this->driver->getWindowHandle(); $windowHandles = $this->driver->getWindowHandles(); - $this->assertInternalType('string', $windowHandle); + $this->assertTrue(is_string($windowHandle)); $this->assertNotEmpty($windowHandle); $this->assertSame([$windowHandle], $windowHandles); @@ -266,7 +266,7 @@ public function testShouldTakeScreenshot() $outputPng = $this->driver->takeScreenshot(); $image = imagecreatefromstring($outputPng); - $this->assertInternalType('resource', $image); + $this->assertTrue(is_resource($image)); $this->assertGreaterThan(0, imagesx($image)); $this->assertGreaterThan(0, imagesy($image)); @@ -292,7 +292,7 @@ public function testShouldSaveScreenshotToFile() $this->driver->takeScreenshot($screenshotPath); $image = imagecreatefrompng($screenshotPath); - $this->assertInternalType('resource', $image); + $this->assertTrue(is_resource($image)); $this->assertGreaterThan(0, imagesx($image)); $this->assertGreaterThan(0, imagesy($image)); @@ -323,9 +323,9 @@ public function testShouldGetRemoteEndStatus() { $status = $this->driver->getStatus(); - $this->assertInternalType('boolean', $status->isReady()); + $this->assertTrue(is_bool($status->isReady())); $this->assertNotEmpty($status->getMessage()); - $this->assertInternalType('array', $status->getMeta()); + $this->assertTrue(is_array($status->getMeta())); } } diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 021e6ed1a..21d890ccc 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -318,7 +318,7 @@ public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() $childElements = $element->findElements(WebDriverBy::cssSelector('not_existing')); - $this->assertInternalType('array', $childElements); + $this->assertTrue(is_array($childElements)); $this->assertCount(0, $childElements); } @@ -330,7 +330,7 @@ public function testShouldFindMultipleChildElements() $allElements = $this->driver->findElements(WebDriverBy::cssSelector('li')); $childElements = $element->findElements(WebDriverBy::cssSelector('li')); - $this->assertInternalType('array', $childElements); + $this->assertTrue(is_array($childElements)); $this->assertCount(5, $allElements); // there should be 5
  • elements on page $this->assertCount(3, $childElements); // but we should find only subelements of one