From 1fdbab925e26893ba8a0cb93ddf5c6b613e307a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 20 Jan 2017 20:39:13 +0100 Subject: [PATCH 001/487] Specify covered methods to have more accurate code coverage --- tests/functional/RemoteWebElementTest.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 1a04cf1a8..c74b5d384 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -16,10 +16,13 @@ namespace Facebook\WebDriver; /** - * @covers Facebook\WebDriver\Remote\RemoteWebElement + * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebElement */ class RemoteWebElementTest extends WebDriverTestCase { + /** + * @covers ::getText + */ public function testShouldGetText() { $this->driver->get($this->getTestPath('index.html')); @@ -30,6 +33,9 @@ public function testShouldGetText() $this->assertEquals('Multiple spaces are stripped', $elementWithTextWithSpaces->getText()); } + /** + * @covers ::getAttribute + */ public function testShouldGetAttributeValue() { $this->driver->get($this->getTestPath('index.html')); @@ -41,6 +47,9 @@ public function testShouldGetAttributeValue() $this->assertSame('text-simple', $element->getAttribute('id')); } + /** + * @covers ::getLocation + */ public function testShouldGetLocation() { $this->driver->get($this->getTestPath('index.html')); @@ -53,6 +62,9 @@ public function testShouldGetLocation() $this->assertSame(500, $elementLocation->getY()); } + /** + * @covers ::getSize + */ public function testShouldGetSize() { $this->driver->get($this->getTestPath('index.html')); @@ -65,6 +77,9 @@ public function testShouldGetSize() $this->assertSame(66, $elementSize->getHeight()); } + /** + * @covers ::getCSSValue + */ public function testShouldGetCssValue() { $this->driver->get($this->getTestPath('index.html')); From 4ab3d675cd9bf2714d0841e558db7b00231f968d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 20 Jan 2017 21:20:43 +0100 Subject: [PATCH 002/487] Add more functional tests for RemoteWebElement --- lib/Remote/RemoteWebElement.php | 4 +- tests/functional/RemoteWebElementTest.php | 164 ++++++++++++++++++++++ tests/functional/web/form.html | 28 +++- tests/functional/web/index.html | 7 + tests/functional/web/submit.php | 28 ++++ tests/functional/web/upload.html | 2 +- 6 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 tests/functional/web/submit.php diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index c10bfbf32..71fdb4305 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -244,13 +244,13 @@ public function getSize() } /** - * Get the tag name of this element. + * Get the (lowercase) tag name of this element. * * @return string The tag name. */ public function getTagName() { - // Force tag name to be lowercase as expected by protocol for Opera driver + // Force tag name to be lowercase as expected by JsonWire protocol for Opera driver // until this issue is not resolved : // https://github.com/operasoftware/operadriver/issues/102 // Remove it when fixed to be consistent with the protocol. diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index c74b5d384..48983a14e 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -93,4 +93,168 @@ public function testShouldGetCssValue() $this->assertSame('rgba(0, 0, 0, 1)', $elementWithBorder->getCSSValue('border-left-color')); $this->assertSame('rgba(0, 0, 0, 1)', $elementWithoutBorder->getCSSValue('border-left-color')); } + + /** + * @covers ::getTagName + */ + public function testShouldGetTagName() + { + $this->driver->get($this->getTestPath('index.html')); + + $paragraphElement = $this->driver->findElement(WebDriverBy::id('id_test')); + + $this->assertSame('p', $paragraphElement->getTagName()); + } + + /** + * @covers ::click + */ + public function testShouldClick() + { + $this->driver->get($this->getTestPath('index.html')); + $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); + + $linkElement->click(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::urlContains('form.html') + ); + } + + /** + * @covers ::clear + */ + public function testShouldClearFormElementText() + { + $this->driver->get($this->getTestPath('form.html')); + + $input = $this->driver->findElement(WebDriverBy::id('input-text')); + $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); + + $this->assertSame('Default input text', $input->getAttribute('value')); + $input->clear(); + $this->assertSame('', $input->getAttribute('value')); + + $this->assertSame('Default textarea text', $textarea->getAttribute('value')); + $textarea->clear(); + $this->assertSame('', $textarea->getAttribute('value')); + } + + /** + * @covers ::sendKeys + */ + public function testShouldSendKeysToFormElement() + { + $this->driver->get($this->getTestPath('form.html')); + + $input = $this->driver->findElement(WebDriverBy::id('input-text')); + $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); + + $input->clear(); + $input->sendKeys('foo bar'); + $this->assertSame('foo bar', $input->getAttribute('value')); + $input->sendKeys(' baz'); + $this->assertSame('foo bar baz', $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')); + } + + /** + * @covers ::isEnabled + */ + public function testShouldDetectEnabledInputs() + { + $this->driver->get($this->getTestPath('form.html')); + + $inputEnabled = $this->driver->findElement(WebDriverBy::id('input-text')); + $inputDisabled = $this->driver->findElement(WebDriverBy::id('input-text-disabled')); + + $this->assertTrue($inputEnabled->isEnabled()); + $this->assertFalse($inputDisabled->isEnabled()); + } + + /** + * @covers ::isSelected + */ + public function testShouldSelectedInputsOrOptions() + { + $this->driver->get($this->getTestPath('form.html')); + + $checkboxSelected = $this->driver->findElement( + WebDriverBy::cssSelector('input[name=checkbox][value=second]') + ); + $checkboxNotSelected = $this->driver->findElement( + WebDriverBy::cssSelector('input[name=checkbox][value=first]') + ); + $this->assertTrue($checkboxSelected->isSelected()); + $this->assertFalse($checkboxNotSelected->isSelected()); + + $radioSelected = $this->driver->findElement(WebDriverBy::cssSelector('input[name=radio][value=second]')); + $radioNotSelected = $this->driver->findElement(WebDriverBy::cssSelector('input[name=radio][value=first]')); + $this->assertTrue($radioSelected->isSelected()); + $this->assertFalse($radioNotSelected->isSelected()); + + $optionSelected = $this->driver->findElement(WebDriverBy::cssSelector('#select option[value=first]')); + $optionNotSelected = $this->driver->findElement(WebDriverBy::cssSelector('#select option[value=second]')); + $this->assertTrue($optionSelected->isSelected()); + $this->assertFalse($optionNotSelected->isSelected()); + } + + /** + * @covers ::submit + */ + public function testShouldSubmitFormBySubmitEventOnForm() + { + $this->driver->get($this->getTestPageUrl('form.html')); + + $formElement = $this->driver->findElement(WebDriverBy::cssSelector('form')); + + $formElement->submit(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::titleIs('Form submit endpoint') + ); + + $this->assertSame('Received POST data', $this->driver->findElement(WebDriverBy::cssSelector('h2'))->getText()); + } + + /** + * @covers ::submit + */ + public function testShouldSubmitFormBySubmitEventOnFormInputElement() + { + $this->driver->get($this->getTestPageUrl('form.html')); + + $inputTextElement = $this->driver->findElement(WebDriverBy::id('input-text')); + + $inputTextElement->submit(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::titleIs('Form submit endpoint') + ); + + $this->assertSame('Received POST data', $this->driver->findElement(WebDriverBy::cssSelector('h2'))->getText()); + } + + /** + * @covers ::click + */ + public function testShouldSubmitFormByClickOnSubmitInput() + { + $this->driver->get($this->getTestPageUrl('form.html')); + + $submitElement = $this->driver->findElement(WebDriverBy::id('submit')); + + $submitElement->click(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::titleIs('Form submit endpoint') + ); + + $this->assertSame('Received POST data', $this->driver->findElement(WebDriverBy::cssSelector('h2'))->getText()); + } } diff --git a/tests/functional/web/form.html b/tests/functional/web/form.html index ac91336b1..c1570df8a 100644 --- a/tests/functional/web/form.html +++ b/tests/functional/web/form.html @@ -5,16 +5,22 @@ php-webdriver form test page -
+ +
+ +

@@ -38,6 +44,22 @@
+
+ Checkboxes + First
+ Second (preselected)
+ Third (preselected)
+ Fourth
+
+ +
+ Radio buttons + First
+ Second (preselected)
+ Third
+ Fourth
+
+

diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index 282706cce..f0dc03af4 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -6,6 +6,13 @@

Welcome to the facebook/php-webdriver testing page.

+ + Form test page + | + Form with file upload + | + New window opener test page +

Test by ID

Test by Class

Click here diff --git a/tests/functional/web/submit.php b/tests/functional/web/submit.php new file mode 100644 index 000000000..4d8219ec5 --- /dev/null +++ b/tests/functional/web/submit.php @@ -0,0 +1,28 @@ + + + + + Form submit endpoint + + + +POST data not detected'; +} else { + echo '

Received POST data

'; + echo '
    '; + foreach ($_POST as $key => $value) { + echo sprintf( + '
  • %s: %s
  • ' . "\n", + $key, + $key, + $value + ); + } + echo '
'; +} +?> + + + diff --git a/tests/functional/web/upload.html b/tests/functional/web/upload.html index d02c5ed4b..65a622c50 100644 --- a/tests/functional/web/upload.html +++ b/tests/functional/web/upload.html @@ -5,7 +5,7 @@ Upload a file - +

From c8e3f5ac5015773691b92bf224a11909a38a338f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 20 Jan 2017 21:25:51 +0100 Subject: [PATCH 003/487] Test equals() method of RemoteWebElementTest --- tests/functional/RemoteWebElementTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 48983a14e..e5d1ead06 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -257,4 +257,23 @@ public function testShouldSubmitFormByClickOnSubmitInput() $this->assertSame('Received POST data', $this->driver->findElement(WebDriverBy::cssSelector('h2'))->getText()); } + + /** + * @covers ::equals + */ + public function testShouldCompareEqualsElement() + { + $this->driver->get($this->getTestPath('index.html')); + + $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + $differentElement = $this->driver->findElement(WebDriverBy::cssSelector('#text-simple')); + $againTheFirstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + + $this->assertTrue($firstElement->equals($againTheFirstElement)); + $this->assertTrue($againTheFirstElement->equals($firstElement)); + + $this->assertFalse($differentElement->equals($firstElement)); + $this->assertFalse($firstElement->equals($differentElement)); + $this->assertFalse($differentElement->equals($againTheFirstElement)); + } } From 9f7019aeeb975d61a7929a73c5809983aac27ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 25 Jan 2017 23:53:49 +0100 Subject: [PATCH 004/487] Improve phpdoc formatting to make apidoc more readable --- lib/Remote/RemoteWebDriver.php | 3 ++- lib/Remote/RemoteWebElement.php | 2 +- lib/Support/XPathEscaper.php | 2 +- lib/WebDriverExpectedCondition.php | 2 +- lib/WebDriverNavigation.php | 2 +- lib/WebDriverOptions.php | 2 +- lib/WebDriverSelect.php | 2 +- lib/WebDriverSelectInterface.php | 16 ++++++++-------- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index a06beb132..ce7450720 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -340,10 +340,11 @@ public function takeScreenshot($save_as = null) * Construct a new WebDriverWait by the current WebDriver instance. * Sample usage: * + * ``` * $driver->wait(20, 1000)->until( * WebDriverExpectedCondition::titleIs('WebDriver Page') * ); - * + * ``` * @param int $timeout_in_second * @param int $interval_in_millisecond * diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 71fdb4305..17fa258c4 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -349,7 +349,7 @@ public function sendKeys($value) * Basically, if you want WebDriver trying to send a file, set the fileDetector * to be LocalFileDetector. Otherwise, keep it UselessFileDetector. * - * eg. $element->setFileDetector(new LocalFileDetector); + * eg. `$element->setFileDetector(new LocalFileDetector);` * * @param FileDetector $detector * @return RemoteWebElement diff --git a/lib/Support/XPathEscaper.php b/lib/Support/XPathEscaper.php index dc5907f01..c90cefa87 100644 --- a/lib/Support/XPathEscaper.php +++ b/lib/Support/XPathEscaper.php @@ -19,7 +19,7 @@ class XPathEscaper { /** * Converts xpath strings with both quotes and ticks into: - * foo'"bar -> concat('foo', "'" ,'"bar') + * `foo'"bar` -> `concat('foo', "'" ,'"bar')` * * @param string $xpathToEscape The xpath to be converted. * @return string The escaped string. diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 58ba939e9..5612a5bbe 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -66,7 +66,7 @@ function (WebDriver $driver) use ($title) { * An expectation for checking substring of a page Title. * * @param string $title The expected substring of Title. - * @return WebDriverExpectedCondition Condition returns whether current page title contains given string. + * @return WebDriverExpectedCondition Condition returns whether current page title contains given string. */ public static function titleContains($title) { diff --git a/lib/WebDriverNavigation.php b/lib/WebDriverNavigation.php index 6c7857d1d..ccc8cee30 100644 --- a/lib/WebDriverNavigation.php +++ b/lib/WebDriverNavigation.php @@ -25,7 +25,7 @@ * Note that they are all blocking functions until the page is loaded by * by default. It could be overridden by 'webdriver.load.strategy' in the * FirefoxProfile preferences. - * https://code.google.com/p/selenium/wiki/DesiredCapabilities#settings + * https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#firefoxprofile-settings */ class WebDriverNavigation { diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index b728382cc..e02d24136 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -37,7 +37,7 @@ public function __construct(ExecuteMethod $executor) /** * Add a specific cookie. * - * Here are the valid attributes of a cookie array. + * Valid attributes of a cookie array: * 'name' : string The name of the cookie; may not be null or an empty string. * 'value' : string The cookie value; may not be null. * 'path' : string OPTIONAL The path the cookie is visible to. Defaults to "/" if omitted. diff --git a/lib/WebDriverSelect.php b/lib/WebDriverSelect.php index dc6a84038..abe6b54fe 100644 --- a/lib/WebDriverSelect.php +++ b/lib/WebDriverSelect.php @@ -21,7 +21,7 @@ use Facebook\WebDriver\Support\XPathEscaper; /** - * Models a default HTML ` tag, providing helper methods to select and deselect options. */ class WebDriverSelect implements WebDriverSelectInterface { diff --git a/lib/WebDriverSelectInterface.php b/lib/WebDriverSelectInterface.php index cec06a33f..bd4db90e4 100644 --- a/lib/WebDriverSelectInterface.php +++ b/lib/WebDriverSelectInterface.php @@ -27,8 +27,8 @@ public function getAllSelectedOptions(); /** * @throws NoSuchElementException * - * @return WebDriverElement The first selected option in this select tag (or - * the currently selected option in a normal select) + * @return WebDriverElement The first selected option in this select tag (or the currently selected option in a + * normal select) */ public function getFirstSelectedOption(); @@ -45,7 +45,7 @@ public function selectByIndex($index); * Select all options that have value attribute matching the argument. That is, when given "foo" this would * select an option like: * - * ; + * `` * * @param string $value The value to match against. * @@ -57,7 +57,7 @@ public function selectByValue($value); * Select all options that display text matching the argument. That is, when given "Bar" this would * select an option like: * - * ; + * `` * * @param string $text The visible text to match against. * @@ -69,7 +69,7 @@ public function selectByVisibleText($text); * Select all options that display text partially matching the argument. That is, when given "Bar" this would * select an option like: * - * ; + * `` * * @param string $text The visible text to match against. * @@ -96,7 +96,7 @@ public function deselectByIndex($index); * Deselect all options that have value attribute matching the argument. That is, when given "foo" this would * deselect an option like: * - * ; + * `` * * @param string $value The value to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections @@ -107,7 +107,7 @@ public function deselectByValue($value); * Deselect all options that display text matching the argument. That is, when given "Bar" this would * deselect an option like: * - * ; + * `` * * @param string $text The visible text to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections @@ -118,7 +118,7 @@ public function deselectByVisibleText($text); * Deselect all options that display text matching the argument. That is, when given "Bar" this would * deselect an option like: * - * ; + * `` * * @param string $text The visible text to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections From 85aa68020ef2cf4fd261b1f1a690052afe67a9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 26 Jan 2017 00:07:01 +0100 Subject: [PATCH 005/487] Update links to Selenium wiki --- lib/Remote/DesiredCapabilities.php | 4 ++-- lib/Remote/HttpCommandExecutor.php | 3 +-- lib/WebDriverExpectedCondition.php | 2 +- lib/WebDriverOptions.php | 4 ++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 12060ab7b..94d8e1de2 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -136,7 +136,7 @@ public function isJavascriptEnabled() * @param bool $enabled * @throws Exception * @return DesiredCapabilities - * @see https://code.google.com/p/selenium/wiki/DesiredCapabilities#Read-write_capabilities + * @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities */ public function setJavascriptEnabled($enabled) { @@ -144,7 +144,7 @@ public function setJavascriptEnabled($enabled) if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { throw new Exception( 'isJavascriptEnable() is a htmlunit-only option. ' . - 'See https://code.google.com/p/selenium/wiki/DesiredCapabilities#Read-write_capabilities.' + 'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.' ); } diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 2f6c1778f..09bf000d0 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -27,8 +27,7 @@ class HttpCommandExecutor implements WebDriverCommandExecutor { /** - * @see - * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Command_Reference + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#command-reference */ protected static $commands = [ DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/accept_alert'], diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 5612a5bbe..58ba939e9 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -66,7 +66,7 @@ function (WebDriver $driver) use ($title) { * An expectation for checking substring of a page Title. * * @param string $title The expected substring of Title. - * @return WebDriverExpectedCondition Condition returns whether current page title contains given string. + * @return WebDriverExpectedCondition Condition returns whether current page title contains given string. */ public static function titleContains($title) { diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index e02d24136..14cacd176 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -145,7 +145,7 @@ public function window() * * @param string $log_type The log type. * @return array The list of log entries. - * @see https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Type + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#log-type */ public function getLog($log_type) { @@ -159,7 +159,7 @@ public function getLog($log_type) * Get available log types. * * @return array The list of available log types. - * @see https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Type + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#log-type */ public function getAvailableLogTypes() { From 122ae7abd1b495fcbfe432d75d99f5403ad76e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 27 Jan 2017 19:48:01 +0100 Subject: [PATCH 006/487] Add value object to hold cookie data --- lib/Cookie.php | 208 ++++++++++++++++++++++++++++++++++++++ tests/unit/CookieTest.php | 138 +++++++++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 lib/Cookie.php create mode 100644 tests/unit/CookieTest.php diff --git a/lib/Cookie.php b/lib/Cookie.php new file mode 100644 index 000000000..a3b86dbf0 --- /dev/null +++ b/lib/Cookie.php @@ -0,0 +1,208 @@ + null, + 'value' => null, + 'path' => null, + 'domain' => null, + 'expiry' => null, + 'secure' => null, + 'httpOnly' => null, + ]; + + /** + * @param string $name The name of the cookie; may not be null or an empty string. + * @param string $value The cookie value; may not be null. + */ + public function __construct($name, $value) + { + $this->cookie['name'] = $name; + $this->cookie['value'] = $value; + } + + /** + * @param array $cookieArray + * @return Cookie + */ + public static function createFromArray(array $cookieArray) + { + $cookie = new self($cookieArray['name'], $cookieArray['value']); + + if (isset($cookieArray['path'])) { + $cookie->setPath($cookieArray['path']); + } + if (isset($cookieArray['domain'])) { + $cookie->setDomain($cookieArray['domain']); + } + if (isset($cookieArray['expiry'])) { + $cookie->setExpiry($cookieArray['expiry']); + } + if (isset($cookieArray['secure'])) { + $cookie->setSecure($cookieArray['secure']); + } + if (isset($cookieArray['httpOnly'])) { + $cookie->setHttpOnly($cookieArray['httpOnly']); + } + + return $cookie; + } + + /** + * @return string + */ + public function getName() + { + return $this->cookie['name']; + } + + /** + * @return string + */ + public function getValue() + { + return $this->cookie['value']; + } + + /** + * The path the cookie is visible to. Defaults to "/" if omitted. + * + * @param string $path + */ + public function setPath($path) + { + $this->cookie['path'] = $path; + } + + /** + * @return string + */ + public function getPath() + { + return $this->cookie['path']; + } + + /** + * The domain the cookie is visible to. Defaults to the current browsing context's document's URL domain if omitted. + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->cookie['domain'] = $domain; + } + + /** + * @return string + */ + public function getDomain() + { + return $this->cookie['domain']; + } + + /** + * The cookie's expiration date, specified in seconds since Unix Epoch. + * + * @param int $expiry + */ + public function setExpiry($expiry) + { + $this->cookie['expiry'] = (int) $expiry; + } + + /** + * @return int + */ + public function getExpiry() + { + return $this->cookie['expiry']; + } + + /** + * Whether this cookie requires a secure connection (https). Defaults to false if omitted. + * + * @param bool $secure + */ + public function setSecure($secure) + { + $this->cookie['secure'] = $secure; + } + + /** + * @return bool + */ + public function isSecure() + { + return $this->cookie['secure']; + } + + /** + * Whether the cookie is an HTTP only cookie. Defaults to false if omitted. + * + * @param bool $httpOnly + */ + public function setHttpOnly($httpOnly) + { + $this->cookie['httpOnly'] = $httpOnly; + } + + /** + * @return bool + */ + public function isHttpOnly() + { + return $this->cookie['httpOnly']; + } + + /** + * @return array + */ + public function toArray() + { + return $this->cookie; + } + + public function offsetExists($offset) + { + return isset($this->cookie[$offset]); + } + + public function offsetGet($offset) + { + return $this->cookie[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->cookie[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->cookie[$offset]); + } +} diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php new file mode 100644 index 000000000..1ed18b39c --- /dev/null +++ b/tests/unit/CookieTest.php @@ -0,0 +1,138 @@ +setPath('/bar'); + $cookie->setDomain('foo.com'); + $cookie->setExpiry(1485388387); + $cookie->setSecure(true); + $cookie->setHttpOnly(true); + + $this->assertSame('cookieName', $cookie->getName()); + $this->assertSame('someValue', $cookie->getValue()); + $this->assertSame('/bar', $cookie->getPath()); + $this->assertSame('foo.com', $cookie->getDomain()); + $this->assertSame(1485388387, $cookie->getExpiry()); + $this->assertTrue($cookie->isSecure()); + $this->assertTrue($cookie->isHttpOnly()); + + return $cookie; + } + + /** + * @depends testShouldSetAllProperties + * @param Cookie $cookie + */ + public function testShouldBeConvertibleToArray(Cookie $cookie) + { + $this->assertSame( + [ + 'name' => 'cookieName', + 'value' => 'someValue', + 'path' => '/bar', + 'domain' => 'foo.com', + 'expiry' => 1485388387, + 'secure' => true, + 'httpOnly' => true, + ], + $cookie->toArray() + ); + } + + /** + * @depends testShouldSetAllProperties + * @param Cookie $cookie + */ + public function testShouldProvideArrayAccessToProperties(Cookie $cookie) + { + $this->assertSame('cookieName', $cookie['name']); + $this->assertSame('someValue', $cookie['value']); + $this->assertSame('/bar', $cookie['path']); + $this->assertSame('foo.com', $cookie['domain']); + $this->assertSame(1485388387, $cookie['expiry']); + $this->assertTrue($cookie['secure']); + $this->assertTrue($cookie['httpOnly']); + + $cookie->offsetSet('domain', 'bar.com'); + $this->assertSame('bar.com', $cookie['domain']); + $cookie->offsetUnset('domain'); + $this->assertFalse(isset($cookie['domain'])); + } + + public function testShouldBeCreatableFromAnArrayWithBasicValues() + { + $sourceArray = [ + 'name' => 'cookieName', + 'value' => 'someValue', + ]; + + $cookie = Cookie::createFromArray($sourceArray); + + $this->assertSame('cookieName', $cookie['name']); + $this->assertSame('someValue', $cookie['value']); + + $this->assertFalse(isset($cookie['path'])); + $this->assertNull($cookie['path']); + $this->assertNull($cookie->getPath()); + + $this->assertFalse(isset($cookie['domain'])); + $this->assertNull($cookie['domain']); + $this->assertNull($cookie->getDomain()); + + $this->assertFalse(isset($cookie['expiry'])); + $this->assertNull($cookie['expiry']); + $this->assertNull($cookie->getExpiry()); + + $this->assertFalse(isset($cookie['secure'])); + $this->assertNull($cookie['secure']); + $this->assertNull($cookie->isSecure()); + + $this->assertFalse(isset($cookie['httpOnly'])); + $this->assertNull($cookie['httpOnly']); + $this->assertNull($cookie->isHttpOnly()); + } + + public function testShouldBeCreatableFromAnArrayWithAllValues() + { + $sourceArray = [ + 'name' => 'cookieName', + 'value' => 'someValue', + 'path' => '/bar', + 'domain' => 'foo', + 'expiry' => 1485388333, + 'secure' => false, + 'httpOnly' => false, + ]; + + $cookie = Cookie::createFromArray($sourceArray); + + $this->assertSame('cookieName', $cookie['name']); + $this->assertSame('someValue', $cookie['value']); + $this->assertSame('/bar', $cookie['path']); + $this->assertSame('foo', $cookie['domain']); + $this->assertSame(1485388333, $cookie['expiry']); + $this->assertFalse($cookie['secure']); + $this->assertFalse($cookie['httpOnly']); + } +} From 33224c27e3f1fffd4ea62d5c33968496677ac79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 27 Jan 2017 20:15:55 +0100 Subject: [PATCH 007/487] Validate cookie values on creation --- lib/Cookie.php | 33 +++++++++++++++++++++++++++++++ tests/unit/CookieTest.php | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/lib/Cookie.php b/lib/Cookie.php index a3b86dbf0..0434a128b 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -15,6 +15,8 @@ namespace Facebook\WebDriver; +use InvalidArgumentException; + /** * Set values of an cookie. * @@ -41,6 +43,9 @@ class Cookie implements \ArrayAccess */ public function __construct($name, $value) { + $this->validateCookieName($name); + $this->validateCookieValue($value); + $this->cookie['name'] = $name; $this->cookie['value'] = $value; } @@ -113,6 +118,10 @@ public function getPath() */ public function setDomain($domain) { + if (mb_strpos($domain, ':') !== false) { + throw new InvalidArgumentException(sprintf('Cookie domain "%s" should not contain a port', $domain)); + } + $this->cookie['domain'] = $domain; } @@ -205,4 +214,28 @@ public function offsetUnset($offset) { unset($this->cookie[$offset]); } + + /** + * @param string $name + */ + protected function validateCookieName($name) + { + if ($name === null || $name === '') { + throw new InvalidArgumentException('Cookie name should be non-empty'); + } + + if (mb_strpos($name, ';') !== false) { + throw new InvalidArgumentException('Cookie name should not contain a ";"'); + } + } + + /** + * @param string $value + */ + protected function validateCookieValue($value) + { + if ($value === null) { + throw new InvalidArgumentException('Cookie value is required when setting a cookie'); + } + } } diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 1ed18b39c..0742a0789 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -135,4 +135,45 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() $this->assertFalse($cookie['secure']); $this->assertFalse($cookie['httpOnly']); } + + /** + * @dataProvider invalidCookieProvider + * @param string $name + * @param string $value + * @param string $domain + * @param string $expectedMessage + */ + public function testShouldValidateCookie($name, $value, $domain, $expectedMessage) + { + if ($expectedMessage) { + $this->setExpectedException(\InvalidArgumentException::class, $expectedMessage); + } + + $cookie = new Cookie($name, $value); + if ($domain !== null) { + $cookie->setDomain($domain); + } + } + + /** + * @return array[] + */ + public function invalidCookieProvider() + { + return [ + // $name, $value, $domain, $expectedMessage + 'name cannot be empty' => ['', 'foo', null, 'Cookie name should be non-empty'], + 'name cannot be null' => [null, 'foo', null, 'Cookie name should be non-empty'], + 'name cannot contain semicolon' => ['name;semicolon', 'foo', null, 'Cookie name should not contain a ";"'], + 'value could be empty string' => ['name', '', null, null], + 'value cannot be null' => ['name', null, null, 'Cookie value is required when setting a cookie'], + 'domain cannot containt port' => [ + 'name', + 'value', + 'localhost:443', + 'Cookie domain "localhost:443" should not contain a port', + ], + 'cookie with valid values' => ['name', 'value', '*.localhost', null], + ]; + } } From 865e9b2c44eb8b1d7f21b4149f319875e5681d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 27 Jan 2017 20:34:00 +0100 Subject: [PATCH 008/487] Add unit test to cookie setting and retrieval in WebDriverOptions class --- tests/unit/WebDriverOptionsTest.php | 152 ++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tests/unit/WebDriverOptionsTest.php diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php new file mode 100644 index 000000000..f95671470 --- /dev/null +++ b/tests/unit/WebDriverOptionsTest.php @@ -0,0 +1,152 @@ +executor = $this->getMockBuilder(ExecuteMethod::class) + ->disableOriginalConstructor() + ->getMock(); + } + + public function testShouldAddCookieFromArray() + { + $cookieInArray = [ + 'name' => 'cookieName', + 'value' => 'someValue', + 'path' => '/bar', + 'domain' => 'foo', + 'expiry' => 1485388333, + 'secure' => false, + 'httpOnly' => false, + ]; + + $this->executor->expects($this->once()) + ->method('execute') + ->with(DriverCommand::ADD_COOKIE, ['cookie' => $cookieInArray]); + + $options = new WebDriverOptions($this->executor); + + $options->addCookie($cookieInArray); + } + + public function testShouldGetAllCookies() + { + $this->executor->expects($this->once()) + ->method('execute') + ->with(DriverCommand::GET_ALL_COOKIES) + ->willReturn( + [ + [ + 'path' => '/', + 'domain' => '*.seleniumhq.org', + 'name' => 'firstCookie', + 'httpOnly' => false, + 'secure' => true, + 'value' => 'value', + ], + [ + 'path' => '/', + 'domain' => 'docs.seleniumhq.org', + 'name' => 'secondCookie', + 'httpOnly' => false, + 'secure' => false, + 'value' => 'foo', + ], + ] + ); + + $options = new WebDriverOptions($this->executor); + + $cookies = $options->getCookies(); + + $this->assertCount(2, $cookies); + $this->assertSame('firstCookie', $cookies[0]['name']); + $this->assertSame('secondCookie', $cookies[1]['name']); + } + + public function testShouldGetCookieByName() + { + $this->executor->expects($this->once()) + ->method('execute') + ->with(DriverCommand::GET_ALL_COOKIES) + ->willReturn( + [ + [ + 'path' => '/', + 'domain' => '*.seleniumhq.org', + 'name' => 'cookieToFind', + 'httpOnly' => false, + 'secure' => true, + 'value' => 'value', + ], + [ + 'path' => '/', + 'domain' => 'docs.seleniumhq.org', + 'name' => 'otherCookie', + 'httpOnly' => false, + 'secure' => false, + 'value' => 'foo', + ], + ] + ); + + $options = new WebDriverOptions($this->executor); + + $cookie = $options->getCookieNamed('cookieToFind'); + + $this->assertSame('cookieToFind', $cookie['name']); + $this->assertSame('value', $cookie['value']); + $this->assertSame('/', $cookie['path']); + $this->assertSame('*.seleniumhq.org', $cookie['domain']); + $this->assertFalse($cookie['httpOnly']); + $this->assertTrue($cookie['secure']); + } + + public function testShouldReturnNullIfCookieWithNameNotFound() + { + $this->executor->expects($this->once()) + ->method('execute') + ->with(DriverCommand::GET_ALL_COOKIES) + ->willReturn( + [ + [ + 'path' => '/', + 'domain' => '*.seleniumhq.org', + 'name' => 'cookieToNotFind', + 'httpOnly' => false, + 'secure' => true, + 'value' => 'value', + ], + ] + ); + + $options = new WebDriverOptions($this->executor); + + $this->assertNull($options->getCookieNamed('notExistingCookie')); + } +} From c2e3e30af9d6880f2b07cea0e6b280196aea03ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 27 Jan 2017 20:54:04 +0100 Subject: [PATCH 009/487] Set/receive cookie using Cookie value object instead of an array --- CHANGELOG.md | 3 ++ lib/WebDriverOptions.php | 59 ++++++++++------------------- tests/unit/WebDriverOptionsTest.php | 59 +++++++++++++++++++++++++---- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ce2d4dc..1d24e1582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Cookies should now be set using `Cookie` value object instead of an array when passed to to `addCookie()` method of `WebDriverOptions`. +- Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. ## 1.3.0 - 2017-01-13 ### Added diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index 14cacd176..9791b9f1c 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -37,27 +37,22 @@ public function __construct(ExecuteMethod $executor) /** * Add a specific cookie. * - * Valid attributes of a cookie array: - * 'name' : string The name of the cookie; may not be null or an empty string. - * 'value' : string The cookie value; may not be null. - * 'path' : string OPTIONAL The path the cookie is visible to. Defaults to "/" if omitted. - * 'domain' : string OPTIONAL The domain the cookie is visible to. Defaults to the current browsing context's - * document's URL domain if omitted. - * 'secure' : bool OPTIONAL Whether this cookie requires a secure connection (https). Defaults to false if - * omitted. - * 'httpOnly': bool OPTIONAL Whether the cookie is an HTTP only cookie. Defaults to false if omitted. - * 'expiry' : int OPTIONAL The cookie's expiration date, specified in seconds since Unix Epoch. - * - * @see https://w3c.github.io/webdriver/webdriver-spec.html#cookies - * @param array $cookie An array with key as the attributes mentioned above. + * @see Facebook\WebDriver\Cookie for description of possible cookie properties + * @param Cookie|array $cookie Cookie object. May be also created from array for compatibility reasons. * @return WebDriverOptions The current instance. */ - public function addCookie(array $cookie) + public function addCookie($cookie) { - $this->validateCookie($cookie); + if (is_array($cookie)) { + $cookie = Cookie::createFromArray($cookie); + } + if (!$cookie instanceof Cookie) { + throw new InvalidArgumentException('Cookie must be set from instance of Cookie class or from array.'); + } + $this->executor->execute( DriverCommand::ADD_COOKIE, - ['cookie' => $cookie] + ['cookie' => $cookie->toArray()] ); return $this; @@ -95,7 +90,7 @@ public function deleteCookieNamed($name) * Get the cookie with a given name. * * @param string $name - * @return array The cookie, or null if no cookie with the given name is presented. + * @return Cookie The cookie, or null if no cookie with the given name is presented. */ public function getCookieNamed($name) { @@ -112,11 +107,18 @@ public function getCookieNamed($name) /** * Get all the cookies for the current domain. * - * @return array The array of cookies presented. + * @return Cookie[] The array of cookies presented. */ public function getCookies() { - return $this->executor->execute(DriverCommand::GET_ALL_COOKIES); + $cookieArrays = $this->executor->execute(DriverCommand::GET_ALL_COOKIES); + $cookies = []; + + foreach ($cookieArrays as $cookieArray) { + $cookies[] = Cookie::createFromArray($cookieArray); + } + + return $cookies; } /** @@ -165,23 +167,4 @@ public function getAvailableLogTypes() { return $this->executor->execute(DriverCommand::GET_AVAILABLE_LOG_TYPES); } - - /** - * @param array $cookie - * @throws \InvalidArgumentException - */ - private function validateCookie(array $cookie) - { - if (!isset($cookie['name']) || $cookie['name'] === '' || mb_strpos($cookie['name'], ';') !== false) { - throw new InvalidArgumentException('"name" should be non-empty and does not contain a ";"'); - } - - if (!isset($cookie['value'])) { - throw new InvalidArgumentException('"value" is required when setting a cookie.'); - } - - if (isset($cookie['domain']) && mb_strpos($cookie['domain'], ':') !== false) { - throw new InvalidArgumentException('"domain" should not contain a port:' . (string) $cookie['domain']); - } - } } diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index f95671470..38f329726 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -54,6 +54,47 @@ public function testShouldAddCookieFromArray() $options->addCookie($cookieInArray); } + public function testShouldAddCookieFromCookieObject() + { + $cookieObject = new Cookie('cookieName', 'someValue'); + $cookieObject->setPath('/bar'); + $cookieObject->setDomain('foo'); + $cookieObject->setExpiry(1485388333); + $cookieObject->setSecure(false); + $cookieObject->setHttpOnly(false); + + $expectedCookieData = [ + 'name' => 'cookieName', + 'value' => 'someValue', + 'path' => '/bar', + 'domain' => 'foo', + 'expiry' => 1485388333, + 'secure' => false, + 'httpOnly' => false, + ]; + + $this->executor->expects($this->once()) + ->method('execute') + ->with(DriverCommand::ADD_COOKIE, ['cookie' => $expectedCookieData]); + + $options = new WebDriverOptions($this->executor); + + $options->addCookie($cookieObject); + } + + public function testShouldNotAllowToCreateCookieFromDifferentObjectThanCookie() + { + $notCookie = new \stdClass(); + + $options = new WebDriverOptions($this->executor); + + $this->setExpectedException( + \InvalidArgumentException::class, + 'Cookie must be set from instance of Cookie class or from array.' + ); + $options->addCookie($notCookie); + } + public function testShouldGetAllCookies() { $this->executor->expects($this->once()) @@ -85,8 +126,9 @@ public function testShouldGetAllCookies() $cookies = $options->getCookies(); $this->assertCount(2, $cookies); - $this->assertSame('firstCookie', $cookies[0]['name']); - $this->assertSame('secondCookie', $cookies[1]['name']); + $this->assertContainsOnlyInstancesOf(Cookie::class, $cookies); + $this->assertSame('firstCookie', $cookies[0]->getName()); + $this->assertSame('secondCookie', $cookies[1]->getName()); } public function testShouldGetCookieByName() @@ -119,12 +161,13 @@ public function testShouldGetCookieByName() $cookie = $options->getCookieNamed('cookieToFind'); - $this->assertSame('cookieToFind', $cookie['name']); - $this->assertSame('value', $cookie['value']); - $this->assertSame('/', $cookie['path']); - $this->assertSame('*.seleniumhq.org', $cookie['domain']); - $this->assertFalse($cookie['httpOnly']); - $this->assertTrue($cookie['secure']); + $this->assertInstanceOf(Cookie::class, $cookie); + $this->assertSame('cookieToFind', $cookie->getName()); + $this->assertSame('value', $cookie->getValue()); + $this->assertSame('/', $cookie->getPath()); + $this->assertSame('*.seleniumhq.org', $cookie->getDomain()); + $this->assertFalse($cookie->isHttpOnly()); + $this->assertTrue($cookie->isSecure()); } public function testShouldReturnNullIfCookieWithNameNotFound() From e722283fc5b6d9fe8a420b32eb269a13684af527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 28 Jan 2017 12:45:34 +0100 Subject: [PATCH 010/487] Update example.php to use the Cookie object --- example.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example.php b/example.php index 03b16728e..7bebd10b5 100644 --- a/example.php +++ b/example.php @@ -18,10 +18,10 @@ // adding cookie $driver->manage()->deleteAllCookies(); -$driver->manage()->addCookie([ - 'name' => 'cookie_name', - 'value' => 'cookie_value', -]); + +$cookie = new Cookie('cookie_name', 'cookie_value'); +$driver->manage()->addCookie($cookie); + $cookies = $driver->manage()->getCookies(); print_r($cookies); From 1b303fdbed19c7825a92b8e225c25031f338fdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 2 Feb 2017 22:37:31 +0100 Subject: [PATCH 011/487] Add functional tests for WebDriverTimeouts --- tests/functional/WebDriverTimeoutsTest.php | 68 ++++++++++++++++++++++ tests/functional/web/delayed_element.html | 19 ++++++ tests/functional/web/index.html | 4 ++ tests/functional/web/slow_loading.html | 14 +++++ tests/functional/web/slow_pixel.png.php | 10 ++++ 5 files changed, 115 insertions(+) create mode 100644 tests/functional/WebDriverTimeoutsTest.php create mode 100644 tests/functional/web/delayed_element.html create mode 100644 tests/functional/web/slow_loading.html create mode 100644 tests/functional/web/slow_pixel.png.php diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php new file mode 100644 index 000000000..b7a7eb183 --- /dev/null +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -0,0 +1,68 @@ +driver->get($this->getTestPath('delayed_element.html')); + + $this->setExpectedException(NoSuchElementException::class); + $this->driver->findElement(WebDriverBy::id('delayed')); + } + + /** + * @covers ::implicitlyWait + */ + public function testShouldGetDelayedElementWithImplicitWait() + { + $this->driver->get($this->getTestPath('delayed_element.html')); + + $this->driver->manage()->timeouts()->implicitlyWait(1); + $element = $this->driver->findElement(WebDriverBy::id('delayed')); + + $this->assertInstanceOf(RemoteWebElement::class, $element); + } + + /** + * @covers ::pageLoadTimeout + */ + public function testShouldFailIfPageIsLoadingLongerThanPageLoadTimeout() + { + if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $this->driver->manage()->timeouts()->pageLoadTimeout(1); + + try { + $this->driver->get($this->getTestPageUrl('slow_loading.html')); + $this->fail('ScriptTimeoutException or TimeOutException exception should be thrown'); + } catch (TimeOutException $e) { // thrown by Selenium 3.0.0+ + } catch (ScriptTimeoutException $e) { // thrown by Selenium 2 + } + } +} diff --git a/tests/functional/web/delayed_element.html b/tests/functional/web/delayed_element.html new file mode 100644 index 000000000..b0f057a9a --- /dev/null +++ b/tests/functional/web/delayed_element.html @@ -0,0 +1,19 @@ + + + + + php-webdriver test page with delayed element appearing + + + +

+ + + + + diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index f0dc03af4..c3d53c4a3 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -12,6 +12,10 @@

Welcome to the facebook/php-webdriver testing page.

Form with file upload | New window opener test page + | + Delayed render + | + Slow loading page

Test by ID

Test by Class

diff --git a/tests/functional/web/slow_loading.html b/tests/functional/web/slow_loading.html new file mode 100644 index 000000000..984272bf3 --- /dev/null +++ b/tests/functional/web/slow_loading.html @@ -0,0 +1,14 @@ + + + + + php-webdriver test page which is taking long to load + + + +

This page is loading slowly

+ +Slowly loading pixel + + + diff --git a/tests/functional/web/slow_pixel.png.php b/tests/functional/web/slow_pixel.png.php new file mode 100644 index 000000000..d95805e21 --- /dev/null +++ b/tests/functional/web/slow_pixel.png.php @@ -0,0 +1,10 @@ + Date: Fri, 3 Feb 2017 01:08:44 +0100 Subject: [PATCH 012/487] Minor phpdoc formatting fixes --- lib/Remote/RemoteWebDriver.php | 14 ++++++-------- lib/WebDriverTimeouts.php | 9 +++------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index ce7450720..ed69c23ac 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -274,9 +274,8 @@ public function quit() } /** - * Inject a snippet of JavaScript into the page for execution in the context - * of the currently selected frame. The executed script is assumed to be - * synchronous and the result of evaluating the script will be returned. + * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. + * The executed script is assumed to be synchronous and the result of evaluating the script will be returned. * * @param string $script The script to inject. * @param array $arguments The arguments of the script. @@ -293,13 +292,12 @@ public function executeScript($script, array $arguments = []) } /** - * Inject a snippet of JavaScript into the page for asynchronous execution in - * the context of the currently selected frame. + * Inject a snippet of JavaScript into the page for asynchronous execution in the context of the currently selected + * frame. * - * The driver will pass a callback as the last argument to the snippet, and - * block until the callback is invoked. + * The driver will pass a callback as the last argument to the snippet, and block until the callback is invoked. * - * @see WebDriverExecuteAsyncScriptTestCase + * You may need to define script timeout using `setScriptTimeout()` method of `WebDriverTimeouts` first. * * @param string $script The script to inject. * @param array $arguments The arguments of the script. diff --git a/lib/WebDriverTimeouts.php b/lib/WebDriverTimeouts.php index d62f2a124..6903f7080 100644 --- a/lib/WebDriverTimeouts.php +++ b/lib/WebDriverTimeouts.php @@ -34,8 +34,7 @@ public function __construct(ExecuteMethod $executor) } /** - * Specify the amount of time the driver should wait when searching for an - * element if it is not immediately present. + * Specify the amount of time the driver should wait when searching for an element if it is not immediately present. * * @param int $seconds Wait time in second. * @return WebDriverTimeouts The current instance. @@ -51,8 +50,7 @@ public function implicitlyWait($seconds) } /** - * Set the amount of time to wait for an asynchronous script to finish - * execution before throwing an error. + * Set the amount of time to wait for an asynchronous script to finish execution before throwing an error. * * @param int $seconds Wait time in second. * @return WebDriverTimeouts The current instance. @@ -68,8 +66,7 @@ public function setScriptTimeout($seconds) } /** - * Set the amount of time to wait for a page load to complete before throwing - * an error. + * Set the amount of time to wait for a page load to complete before throwing an error. * * @param int $seconds Wait time in second. * @return WebDriverTimeouts The current instance. From 91b92332630c24556b608d7c9b31274d9bd36358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 3 Feb 2017 01:12:28 +0100 Subject: [PATCH 013/487] Add some more unit tests for WebDriverOptions --- tests/unit/WebDriverOptionsTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 38f329726..5e335854f 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -192,4 +192,20 @@ public function testShouldReturnNullIfCookieWithNameNotFound() $this->assertNull($options->getCookieNamed('notExistingCookie')); } + + public function testShouldReturnTimeoutsInstance() + { + $options = new WebDriverOptions($this->executor); + + $timeouts = $options->timeouts(); + $this->assertInstanceOf(WebDriverTimeouts::class, $timeouts); + } + + public function testShouldReturnWindowInstance() + { + $options = new WebDriverOptions($this->executor); + + $window = $options->window(); + $this->assertInstanceOf(WebDriverWindow::class, $window); + } } From 7a6bf0dd9c785e11660c798e3d895d83babf2306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 7 Feb 2017 23:45:13 +0100 Subject: [PATCH 014/487] Fix incorrect covers annotation --- tests/functional/RemoteWebDriverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 918b3514b..847479afa 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -131,7 +131,7 @@ public function testShouldGetWindowHandles() } /** - * @covers ::getWindowHandles + * @covers ::close */ public function testShouldCloseWindow() { From 2a7a3537450331d9c6a7f6f9e06587858a8ce903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 7 Feb 2017 23:52:33 +0100 Subject: [PATCH 015/487] Add missing phpdoc --- lib/Remote/RemoteWebDriver.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index ed69c23ac..58381cff4 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -59,6 +59,11 @@ class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInpu */ protected $executeMethod; + /** + * @param HttpCommandExecutor $commandExecutor + * @param string $sessionId + * @param WebDriverCapabilities|null $capabilities + */ protected function __construct( HttpCommandExecutor $commandExecutor, $sessionId, From ca4300c0be26750dd94eb4694f9ca78ee96cf8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 7 Feb 2017 23:57:58 +0100 Subject: [PATCH 016/487] Add unit tests for parts of RemoteWebDriver which do not interact with the real remote --- tests/unit/Remote/HttpCommandExecutorTest.php | 1 + tests/unit/Remote/RemoteWebDriverTest.php | 117 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 tests/unit/Remote/RemoteWebDriverTest.php diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 268b3e873..63d004e6b 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -20,6 +20,7 @@ class HttpCommandExecutorTest extends \PHPUnit_Framework_TestCase { use PHPMock; + /** @var HttpCommandExecutor */ private $executor; diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php new file mode 100644 index 000000000..48b149f84 --- /dev/null +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -0,0 +1,117 @@ +driver = RemoteWebDriver::createBySessionID('session-id', 'http://foo.bar:4444/'); + } + + /** + * @covers ::manage + */ + public function testShouldCreateWebDriverOptionsInstance() + { + $wait = $this->driver->manage(); + + $this->assertInstanceOf(WebDriverOptions::class, $wait); + } + + /** + * @covers ::navigate + */ + public function testShouldCreateWebDriverNavigationInstance() + { + $wait = $this->driver->navigate(); + + $this->assertInstanceOf(WebDriverNavigation::class, $wait); + } + + /** + * @covers ::switchTo + */ + public function testShouldCreateRemoteTargetLocatorInstance() + { + $wait = $this->driver->switchTo(); + + $this->assertInstanceOf(RemoteTargetLocator::class, $wait); + } + + /** + * @covers ::getMouse + */ + public function testShouldCreateRemoteMouseInstance() + { + $wait = $this->driver->getMouse(); + + $this->assertInstanceOf(RemoteMouse::class, $wait); + } + + /** + * @covers ::getKeyboard + */ + public function testShouldCreateRemoteKeyboardInstance() + { + $wait = $this->driver->getKeyboard(); + + $this->assertInstanceOf(RemoteKeyboard::class, $wait); + } + + /** + * @covers ::getTouch + */ + public function testShouldCreateRemoteTouchScreenInstance() + { + $wait = $this->driver->getTouch(); + + $this->assertInstanceOf(RemoteTouchScreen::class, $wait); + } + + /** + * @covers ::action + */ + public function testShouldCreateWebDriverActionsInstance() + { + $wait = $this->driver->action(); + + $this->assertInstanceOf(WebDriverActions::class, $wait); + } + + /** + * @covers ::wait + */ + public function testShouldCreateWebDriverWaitInstance() + { + $wait = $this->driver->wait(15, 1337); + + $this->assertInstanceOf(WebDriverWait::class, $wait); + } +} From 4eb204b350c4647775b0137908a62a9f3b912c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Feb 2017 00:51:28 +0100 Subject: [PATCH 017/487] Test reusing WebDriver session ID --- lib/Remote/RemoteWebDriver.php | 2 +- tests/functional/RemoteWebDriverCreateTest.php | 17 +++++++++++++++++ tests/functional/WebDriverTestCase.php | 1 - 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 58381cff4..f8b8e5e6e 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -135,7 +135,7 @@ public static function create( * [Experimental] Construct the RemoteWebDriver by an existing session. * * This constructor can boost the performance a lot by reusing the same browser for the whole test suite. - * You cannot the desired capabilities because the session was created before. + * You cannot pass the desired capabilities because the session was created before. * * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param string $session_id The existing session id diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 4f05a4319..161adb2bf 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -60,4 +60,21 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); } + + public function testShouldCreateInstanceFromExistingSessionId() + { + // Create driver instance and load page "index.html" + $originalDriver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities); + $originalDriver->get($this->getTestPath('index.html')); + $this->assertContains('/index.html', $originalDriver->getCurrentURL()); + + // Store session ID + $sessionId = $originalDriver->getSessionID(); + + // Create new RemoteWebDriver instance based on the session ID + $this->driver = RemoteWebDriver::createBySessionID($sessionId); + + // Check we reused the previous instance (window) and it has the same URL + $this->assertContains('/index.html', $this->driver->getCurrentURL()); + } } diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 8c2fd4b8d..5626d1e82 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -29,7 +29,6 @@ class WebDriverTestCase extends \PHPUnit_Framework_TestCase protected $createWebDriver = true; /** @var string */ protected $serverUrl = 'http://localhost:4444/wd/hub'; - /** @var RemoteWebDriver $driver */ protected $driver; /** @var DesiredCapabilities */ From 67cb575fc727530d6c4cb4a852dd34b45f036074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Feb 2017 01:04:42 +0100 Subject: [PATCH 018/487] Add new php-cs-fixer rule --- .php_cs.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/.php_cs.dist b/.php_cs.dist index c40510179..d95f9d0fc 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -71,6 +71,7 @@ return PhpCsFixer\Config::create() 'trailing_comma_in_multiline_array' => true, 'trim_array_spaces' => true, 'unary_operator_spaces' => true, + 'visibility_required' => true, 'whitespace_after_comma_in_array' => true, ]) ->setRiskyAllowed(true) From 7cd8e163883a6dd22f0c5f6372fba05e5b8d2785 Mon Sep 17 00:00:00 2001 From: Nino Date: Wed, 8 Feb 2017 13:09:02 +0100 Subject: [PATCH 019/487] Added ext-zip to require section of composer.json (#410) * Added ext-zip to require section of composer.json, as it is required by file upload and firefox profile. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9114afdd8..3523361f9 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "require": { "php": "^5.5 || ~7.0", "symfony/process": "^2.8 || ^3.1", - "ext-curl": "*" + "ext-curl": "*", + "ext-zip": "*" }, "require-dev": { "phpunit/phpunit": "4.6.* || ~5.0", From 7b5d48850ad5f8085363c839fc2d250a7af62732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Feb 2017 14:30:47 +0100 Subject: [PATCH 020/487] Mark executeSQL command deprecated - as it was removed from officialn java bindings in 2014 --- lib/Remote/DriverCommand.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Remote/DriverCommand.php b/lib/Remote/DriverCommand.php index e2ce958b5..1b86c71c6 100644 --- a/lib/Remote/DriverCommand.php +++ b/lib/Remote/DriverCommand.php @@ -83,6 +83,7 @@ class DriverCommand const SET_TIMEOUT = 'setTimeout'; const IMPLICITLY_WAIT = 'implicitlyWait'; const SET_SCRIPT_TIMEOUT = 'setScriptTimeout'; + /** @deprecated */ const EXECUTE_SQL = 'executeSQL'; const GET_LOCATION = 'getLocation'; const SET_LOCATION = 'setLocation'; @@ -91,18 +92,21 @@ class DriverCommand const CLEAR_APP_CACHE = 'clearAppCache'; const IS_BROWSER_ONLINE = 'isBrowserOnline'; const SET_BROWSER_ONLINE = 'setBrowserOnline'; + // Local storage const GET_LOCAL_STORAGE_ITEM = 'getLocalStorageItem'; const GET_LOCAL_STORAGE_KEYS = 'getLocalStorageKeys'; const SET_LOCAL_STORAGE_ITEM = 'setLocalStorageItem'; const REMOVE_LOCAL_STORAGE_ITEM = 'removeLocalStorageItem'; const CLEAR_LOCAL_STORAGE = 'clearLocalStorage'; const GET_LOCAL_STORAGE_SIZE = 'getLocalStorageSize'; + // Session storage const GET_SESSION_STORAGE_ITEM = 'getSessionStorageItem'; const GET_SESSION_STORAGE_KEYS = 'getSessionStorageKey'; const SET_SESSION_STORAGE_ITEM = 'setSessionStorageItem'; const REMOVE_SESSION_STORAGE_ITEM = 'removeSessionStorageItem'; const CLEAR_SESSION_STORAGE = 'clearSessionStorage'; const GET_SESSION_STORAGE_SIZE = 'getSessionStorageSize'; + // Screen orientation const SET_SCREEN_ORIENTATION = 'setScreenOrientation'; const GET_SCREEN_ORIENTATION = 'getScreenOrientation'; // These belong to the Advanced user interactions - an element is optional for these commands. From 58a942c1091939e02eb6aa2f56c0ebb2d2c9aaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Feb 2017 20:49:38 +0100 Subject: [PATCH 021/487] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d24e1582..6b99a4f66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Cookies should now be set using `Cookie` value object instead of an array when passed to to `addCookie()` method of `WebDriverOptions`. - Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. +- `ext-zip` is now specified as required dependency in composer.json (but the extension was already required by the code, though). ## 1.3.0 - 2017-01-13 ### Added From 47191c71d0024d1158326a5178dc931a1915ebd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Feb 2017 01:53:36 +0100 Subject: [PATCH 022/487] Add WebDriverAlert functional test --- tests/functional/WebDriverAlertTest.php | 103 ++++++++++++++++++++++++ tests/functional/web/alert.html | 35 ++++++++ tests/functional/web/index.html | 2 + 3 files changed, 140 insertions(+) create mode 100644 tests/functional/WebDriverAlertTest.php create mode 100644 tests/functional/web/alert.html diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php new file mode 100644 index 000000000..ce403c549 --- /dev/null +++ b/tests/functional/WebDriverAlertTest.php @@ -0,0 +1,103 @@ +driver->get($this->getTestPath('alert.html')); + } + + public function testShouldAcceptAlert() + { + // Open alert + $this->driver->findElement(WebDriverBy::id('open-alert'))->click(); + + // Wait until present + $this->driver->wait()->until(WebDriverExpectedCondition::alertIsPresent()); + + $this->assertSame('This is alert', $this->driver->switchTo()->alert()->getText()); + + $this->driver->switchTo()->alert()->accept(); + + $this->setExpectedException(NoAlertOpenException::class); + $this->driver->switchTo()->alert()->accept(); + } + + public function testShouldAcceptAndDismissConfirmation() + { + if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + /** @see https://github.com/SeleniumHQ/htmlunit-driver/issues/14 */ + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + // Open confirmation + $this->driver->findElement(WebDriverBy::id('open-confirm'))->click(); + + // Wait until present + $this->driver->wait()->until(WebDriverExpectedCondition::alertIsPresent()); + + $this->assertSame('Do you confirm?', $this->driver->switchTo()->alert()->getText()); + + // Test accepting + $this->driver->switchTo()->alert()->accept(); + $this->assertSame('accepted', $this->getResultText()); + + // Open confirmation + $this->driver->findElement(WebDriverBy::id('open-confirm'))->click(); + + // Test dismissal + $this->driver->switchTo()->alert()->dismiss(); + $this->assertSame('dismissed', $this->getResultText()); + } + + public function testShouldSubmitPromptText() + { + if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + /** @see https://github.com/SeleniumHQ/htmlunit-driver/issues/14 */ + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + // Open confirmation + $this->driver->findElement(WebDriverBy::id('open-prompt'))->click(); + + // Wait until present + $this->driver->wait()->until(WebDriverExpectedCondition::alertIsPresent()); + + $this->assertSame('Enter prompt value', $this->driver->switchTo()->alert()->getText()); + + $this->driver->switchTo()->alert()->sendKeys('Text entered to prompt'); + $this->driver->switchTo()->alert()->accept(); + + $this->assertSame('Text entered to prompt', $this->getResultText()); + } + + private function getResultText() + { + return $this->driver + ->findElement(WebDriverBy::id('result')) + ->getText(); + } +} diff --git a/tests/functional/web/alert.html b/tests/functional/web/alert.html new file mode 100644 index 000000000..079d75130 --- /dev/null +++ b/tests/functional/web/alert.html @@ -0,0 +1,35 @@ + + + + + Open an alert + + + +

+ Open alert +
+ + Open confirm +
+ + Open prompt +
+

+ +
+ + + + + diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index c3d53c4a3..8e314f798 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -16,6 +16,8 @@

Welcome to the facebook/php-webdriver testing page.

Delayed render | Slow loading page + | + Javascript alerts

Test by ID

Test by Class

From ff3c556707cb1524ad99d4752db496ab574bf41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 10 Feb 2017 15:02:01 +0100 Subject: [PATCH 023/487] Prepare toArray() method to be added to WebDriverCapabilities interface in next major version --- lib/WebDriver.php | 2 +- lib/WebDriverCapabilities.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/WebDriver.php b/lib/WebDriver.php index ea413f883..f6e8200ae 100755 --- a/lib/WebDriver.php +++ b/lib/WebDriver.php @@ -128,9 +128,9 @@ public function navigate(); */ public function switchTo(); + // TODO: Add in next major release (BC) ///** // * @return WebDriverTouchScreen - // * @todo Add in next major release (BC) // */ //public function getTouch(); diff --git a/lib/WebDriverCapabilities.php b/lib/WebDriverCapabilities.php index 6fce03c08..b489a1e6e 100644 --- a/lib/WebDriverCapabilities.php +++ b/lib/WebDriverCapabilities.php @@ -48,4 +48,10 @@ public function is($capability_name); * @return bool Whether javascript is enabled. */ public function isJavascriptEnabled(); + + // TODO: Add in next major release (BC) + ///** + // * @return array + // */ + //public function toArray(); } From 85f8ac1e4705f92ea2b4d2abe4f164c36cce6809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 18 Dec 2016 01:27:57 +0100 Subject: [PATCH 024/487] Run tests on SauceLabs --- .travis.yml | 18 ++++++- tests/functional/FileUploadTest.php | 2 +- .../functional/RemoteWebDriverCreateTest.php | 6 +-- .../RemoteWebDriverFindElementTest.php | 8 +-- tests/functional/RemoteWebDriverTest.php | 28 ++++++----- tests/functional/RemoteWebElementTest.php | 24 ++++----- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverByTest.php | 2 +- tests/functional/WebDriverSelectTest.php | 2 +- tests/functional/WebDriverTestCase.php | 50 +++++++++++++------ tests/functional/WebDriverTimeoutsTest.php | 4 +- 11 files changed, 93 insertions(+), 53 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7c0cd3e77..f7229a2c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,25 @@ php: matrix: include: - # Add build to run tests against Firefox (other runs are agains HtmlUnit by default) + # Add build to run tests against Firefox inside Travis environment (other runs are agains HtmlUnit by default) - php: 7.0 env: BROWSER_NAME="firefox" + # Build with lowest possible dependencies - php: 7.0 env: dependencies="--prefer-lowest" + + # Saucelabs builds + - php: 7.0 + env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" + before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + - php: 7.0 + env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="45.0" PLATFORM="Linux" + before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + - php: 7.0 + env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" + before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + # Add PHP 7 build to check codestyle only in PHP 7 build - php: 7.0 env: CHECK_CODESTYLE=1 @@ -66,3 +79,6 @@ addons: apt: packages: - oracle-java8-installer + sauce_connect: true + jwt: + secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index e8878e642..ef6cf1bb2 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -52,7 +52,7 @@ public function testShouldUploadAFile() public function xtestUselessFileDetectorSendKeys() { - $this->driver->get($this->getTestPath('upload.html')); + $this->driver->get($this->getTestPageUrl('upload.html')); $file_input = $this->driver->findElement(WebDriverBy::id('upload')); $file_input->sendKeys($this->getTestFilePath()); diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 161adb2bf..95ff4851c 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -34,7 +34,7 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); - $this->assertSame($this->serverUrl, $this->driver->getCommandExecutor()->getAddressOfRemoteServer()); + $this->assertNotEmpty($this->driver->getCommandExecutor()->getAddressOfRemoteServer()); $this->assertInternalType('string', $this->driver->getSessionID()); $this->assertNotEmpty($this->driver->getSessionID()); @@ -65,14 +65,14 @@ public function testShouldCreateInstanceFromExistingSessionId() { // Create driver instance and load page "index.html" $originalDriver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities); - $originalDriver->get($this->getTestPath('index.html')); + $originalDriver->get($this->getTestPageUrl('index.html')); $this->assertContains('/index.html', $originalDriver->getCurrentURL()); // Store session ID $sessionId = $originalDriver->getSessionID(); // Create new RemoteWebDriver instance based on the session ID - $this->driver = RemoteWebDriver::createBySessionID($sessionId); + $this->driver = RemoteWebDriver::createBySessionID($sessionId, $this->serverUrl); // Check we reused the previous instance (window) and it has the same URL $this->assertContains('/index.html', $this->driver->getCurrentURL()); diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index ad1daf348..808763bad 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -26,7 +26,7 @@ class RemoteWebDriverFindElementTest extends WebDriverTestCase { public function testShouldThrowExceptionOfElementCannotBeFound() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $this->setExpectedException(NoSuchElementException::class, 'Unable to locate element'); $this->driver->findElement(WebDriverBy::id('not_existing')); @@ -34,7 +34,7 @@ public function testShouldThrowExceptionOfElementCannotBeFound() public function testShouldFindElementIfExistsOnAPage() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('id_test')); @@ -43,7 +43,7 @@ public function testShouldFindElementIfExistsOnAPage() public function testShouldReturnEmptyArrayIfElementsCannotBeFound() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $elements = $this->driver->findElements(WebDriverBy::cssSelector('not_existing')); @@ -53,7 +53,7 @@ public function testShouldReturnEmptyArrayIfElementsCannotBeFound() public function testShouldFindMultipleElements() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 847479afa..0f38d0f3c 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -29,7 +29,7 @@ class RemoteWebDriverTest extends WebDriverTestCase */ public function testShouldGetPageTitle() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $this->assertEquals( 'php-webdriver test page', @@ -43,7 +43,7 @@ public function testShouldGetPageTitle() */ public function testShouldGetCurrentUrl() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $this->assertContains( '/index.html', @@ -56,7 +56,7 @@ public function testShouldGetCurrentUrl() */ public function testShouldGetPageSource() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $source = $this->driver->getPageSource(); $this->assertContains('

', $source); @@ -79,7 +79,9 @@ public function testShouldGetSessionId() */ public function testShouldGetAllSessions() { - $sessions = RemoteWebDriver::getAllSessions(); + $this->skipOnSauceLabs('getAllSessions() is not supported on SauceLabs'); + + $sessions = RemoteWebDriver::getAllSessions($this->serverUrl); $this->assertInternalType('array', $sessions); $this->assertCount(1, $sessions); @@ -96,12 +98,14 @@ public function testShouldGetAllSessions() */ public function testShouldQuitAndUnsetExecutor() { - $this->assertCount(1, RemoteWebDriver::getAllSessions()); + $this->skipOnSauceLabs('getAllSessions() is not supported on SauceLabs'); + + $this->assertCount(1, RemoteWebDriver::getAllSessions($this->serverUrl)); $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); $this->driver->quit(); - $this->assertCount(0, RemoteWebDriver::getAllSessions()); + $this->assertCount(0, RemoteWebDriver::getAllSessions($this->serverUrl)); $this->assertNull($this->driver->getCommandExecutor()); } @@ -111,7 +115,7 @@ public function testShouldQuitAndUnsetExecutor() */ public function testShouldGetWindowHandles() { - $this->driver->get($this->getTestPath('open_new_window.html')); + $this->driver->get($this->getTestPageUrl('open_new_window.html')); $windowHandle = $this->driver->getWindowHandle(); $windowHandles = $this->driver->getWindowHandles(); @@ -135,7 +139,7 @@ public function testShouldGetWindowHandles() */ public function testShouldCloseWindow() { - $this->driver->get($this->getTestPath('open_new_window.html')); + $this->driver->get($this->getTestPageUrl('open_new_window.html')); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); $this->assertCount(2, $this->driver->getWindowHandles()); @@ -150,7 +154,7 @@ public function testShouldCloseWindow() */ public function testShouldExecuteScriptAndDoNotBlockExecution() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -176,7 +180,7 @@ public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() { $this->driver->manage()->timeouts()->setScriptTimeout(1); - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); @@ -209,7 +213,7 @@ public function testShouldTakeScreenshot() $this->markTestSkipped('Screenshots are not supported by HtmlUnit browser'); } - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $outputPng = $this->driver->takeScreenshot(); @@ -234,7 +238,7 @@ public function testShouldSaveScreenshotToFile() $screenshotPath = sys_get_temp_dir() . '/selenium-screenshot.png'; - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $this->driver->takeScreenshot($screenshotPath); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index e5d1ead06..26d13f6e2 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -25,7 +25,7 @@ class RemoteWebElementTest extends WebDriverTestCase */ public function testShouldGetText() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $elementWithSimpleText = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithTextWithSpaces = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -38,7 +38,7 @@ public function testShouldGetText() */ public function testShouldGetAttributeValue() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('text-simple')); @@ -52,7 +52,7 @@ public function testShouldGetAttributeValue() */ public function testShouldGetLocation() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); @@ -67,7 +67,7 @@ public function testShouldGetLocation() */ public function testShouldGetSize() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $element = $this->driver->findElement(WebDriverBy::id('element-with-location')); @@ -82,7 +82,7 @@ public function testShouldGetSize() */ public function testShouldGetCssValue() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $elementWithBorder = $this->driver->findElement(WebDriverBy::id('text-simple')); $elementWithoutBorder = $this->driver->findElement(WebDriverBy::id('text-with-spaces')); @@ -99,7 +99,7 @@ public function testShouldGetCssValue() */ public function testShouldGetTagName() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $paragraphElement = $this->driver->findElement(WebDriverBy::id('id_test')); @@ -111,7 +111,7 @@ public function testShouldGetTagName() */ public function testShouldClick() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); $linkElement->click(); @@ -126,7 +126,7 @@ public function testShouldClick() */ public function testShouldClearFormElementText() { - $this->driver->get($this->getTestPath('form.html')); + $this->driver->get($this->getTestPageUrl('form.html')); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -145,7 +145,7 @@ public function testShouldClearFormElementText() */ public function testShouldSendKeysToFormElement() { - $this->driver->get($this->getTestPath('form.html')); + $this->driver->get($this->getTestPageUrl('form.html')); $input = $this->driver->findElement(WebDriverBy::id('input-text')); $textarea = $this->driver->findElement(WebDriverBy::id('textarea')); @@ -168,7 +168,7 @@ public function testShouldSendKeysToFormElement() */ public function testShouldDetectEnabledInputs() { - $this->driver->get($this->getTestPath('form.html')); + $this->driver->get($this->getTestPageUrl('form.html')); $inputEnabled = $this->driver->findElement(WebDriverBy::id('input-text')); $inputDisabled = $this->driver->findElement(WebDriverBy::id('input-text-disabled')); @@ -182,7 +182,7 @@ public function testShouldDetectEnabledInputs() */ public function testShouldSelectedInputsOrOptions() { - $this->driver->get($this->getTestPath('form.html')); + $this->driver->get($this->getTestPageUrl('form.html')); $checkboxSelected = $this->driver->findElement( WebDriverBy::cssSelector('input[name=checkbox][value=second]') @@ -263,7 +263,7 @@ public function testShouldSubmitFormByClickOnSubmitInput() */ public function testShouldCompareEqualsElement() { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); $differentElement = $this->driver->findElement(WebDriverBy::cssSelector('#text-simple')); diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index ce403c549..eddf3a9ae 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -27,7 +27,7 @@ protected function setUp() { parent::setUp(); - $this->driver->get($this->getTestPath('alert.html')); + $this->driver->get($this->getTestPageUrl('alert.html')); } public function testShouldAcceptAlert() diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 753790248..43d8c0457 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -36,7 +36,7 @@ public function testShouldFindTextElementByLocator( $expectedText = null, $expectedAttributeValue = null ) { - $this->driver->get($this->getTestPath('index.html')); + $this->driver->get($this->getTestPageUrl('index.html')); $by = call_user_func([WebDriverBy::class, $webDriverByLocatorMethod], $webDriverByLocatorValue); $element = $this->driver->findElement($by); diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index 3a7d55f24..ac32cbca7 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -29,7 +29,7 @@ protected function setUp() { parent::setUp(); - $this->driver->get($this->getTestPath('form.html')); + $this->driver->get($this->getTestPageUrl('form.html')); } public function testShouldCreateNewInstanceForSelectElementAndDetectIfItIsMultiple() diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 5626d1e82..62f98b5d1 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -38,13 +38,17 @@ protected function setUp() { $this->desiredCapabilities = new DesiredCapabilities(); - if (getenv('BROWSER_NAME')) { - $browserName = getenv('BROWSER_NAME'); + if (getenv('SAUCELABS')) { + $this->setUpSauceLabs(); } else { - $browserName = WebDriverBrowserType::HTMLUNIT; - } + if (getenv('BROWSER_NAME')) { + $browserName = getenv('BROWSER_NAME'); + } else { + $browserName = WebDriverBrowserType::HTMLUNIT; + } - $this->desiredCapabilities->setBrowserName($browserName); + $this->desiredCapabilities->setBrowserName($browserName); + } if ($this->createWebDriver) { $this->driver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities); @@ -63,24 +67,40 @@ protected function tearDown() } /** - * Get the URL of the test html on filesystem. + * Get the URL of given test HTML on running webserver. * - * @param $path + * @param string $path * @return string */ - protected function getTestPath($path) + protected function getTestPageUrl($path) { - return 'file:///' . __DIR__ . '/web/' . $path; + return 'http://localhost:8000/' . $path; + } + + protected function setUpSauceLabs() + { + $this->serverUrl = sprintf( + 'http://%s:%s@ondemand.saucelabs.com/wd/hub', + getenv('SAUCE_USERNAME'), + getenv('SAUCE_ACCESS_KEY') + ); + $this->desiredCapabilities->setBrowserName(getenv('BROWSER_NAME')); + $this->desiredCapabilities->setVersion(getenv('VERSION')); + $this->desiredCapabilities->setPlatform(getenv('PLATFORM')); + + if (getenv('TRAVIS_JOB_NUMBER')) { + $this->desiredCapabilities->setCapability('tunnel-identifier', getenv('TRAVIS_JOB_NUMBER')); + $this->desiredCapabilities->setCapability('build', getenv('TRAVIS_JOB_NUMBER')); + } } /** - * Get the URL of given test HTML on running webserver. - * - * @param string $path - * @return string + * @param string $message */ - protected function getTestPageUrl($path) + protected function skipOnSauceLabs($message = 'Not supported by SauceLabs') { - return 'http://localhost:8000/' . $path; + if (getenv('SAUCELABS')) { + $this->markTestSkipped($message); + } } } diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index b7a7eb183..51be6e13a 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -28,7 +28,7 @@ class WebDriverTimeoutsTest extends WebDriverTestCase { public function testShouldFailGettingDelayedElementWithoutWait() { - $this->driver->get($this->getTestPath('delayed_element.html')); + $this->driver->get($this->getTestPageUrl('delayed_element.html')); $this->setExpectedException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('delayed')); @@ -39,7 +39,7 @@ public function testShouldFailGettingDelayedElementWithoutWait() */ public function testShouldGetDelayedElementWithImplicitWait() { - $this->driver->get($this->getTestPath('delayed_element.html')); + $this->driver->get($this->getTestPageUrl('delayed_element.html')); $this->driver->manage()->timeouts()->implicitlyWait(1); $element = $this->driver->findElement(WebDriverBy::id('delayed')); From 87ec0a905e74b3054ba6eda6aad9941caf4b7c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 10 Feb 2017 18:00:44 +0100 Subject: [PATCH 025/487] Extend wait as an attempt to make the script more stable in slower network environments... --- tests/functional/RemoteWebDriverTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 0f38d0f3c..858bcd786 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -162,14 +162,14 @@ public function testShouldExecuteScriptAndDoNotBlockExecution() $this->driver->executeScript(' setTimeout( function(){document.getElementById("id_test").innerHTML = "Text changed by script"}, - 250 + 500 )'); // Make sure the script don't block the test execution $this->assertSame('Test by ID', $element->getText()); // If we wait, the script should be executed - usleep(300000); // wait 300 ms + usleep(550000); // wait 550 ms $this->assertSame('Text changed by script', $element->getText()); } From 91c98338ea4638890f6d29e662e048c747b9cf41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 11 Feb 2017 14:40:41 +0100 Subject: [PATCH 026/487] Enable sauce connect addon only on SauceLabs builds This will improve performace of other builds, also otherwise we reach the SauceLabs concurrency tunel limit. --- .travis.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7229a2c3..c2aa30aa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,12 +20,24 @@ matrix: - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + addons: + sauce_connect: true + jwt: + secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="45.0" PLATFORM="Linux" before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + addons: + sauce_connect: true + jwt: + secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + addons: + sauce_connect: true + jwt: + secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= # Add PHP 7 build to check codestyle only in PHP 7 build - php: 7.0 @@ -79,6 +91,3 @@ addons: apt: packages: - oracle-java8-installer - sauce_connect: true - jwt: - secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= From 6f3e3403cf8e9215203ec05a7bddbd3c4cda251c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 11 Feb 2017 20:41:58 +0100 Subject: [PATCH 027/487] Install Firefox only on Firefox build (to not slow down other builds) --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c2aa30aa9..f8fc22fe6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ matrix: # Add build to run tests against Firefox inside Travis environment (other runs are agains HtmlUnit by default) - php: 7.0 env: BROWSER_NAME="firefox" + addons: + firefox: "latest-esr" # Build with lowest possible dependencies - php: 7.0 @@ -87,7 +89,6 @@ after_success: - travis_retry php vendor/bin/coveralls -v addons: - firefox: "latest-esr" apt: packages: - oracle-java8-installer From a59ed83f5079fdefa75cf4ac694325b4ef699180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 22 Feb 2017 23:14:33 +0000 Subject: [PATCH 028/487] Upgrade to Selenium 3.1.0 --- .travis.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index f8fc22fe6..b771dd496 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,9 @@ matrix: env: BROWSER_NAME="firefox" addons: firefox: "latest-esr" + apt: + packages: + - oracle-java8-installer # Build with lowest possible dependencies - php: 7.0 @@ -68,13 +71,8 @@ install: before_script: - sh -e /etc/init.d/xvfb start - # TODO: upgrade to Selenium 3.0.2 (with latest HtmlUnit) once released, as HtmlUnit in 3.0.1 is broken - - if [ ! -f jar/selenium-server-standalone-2.53.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar; fi - - if [ ! -f jar/htmlunit-driver-standalone-2.20.jar ]; then wget -q -t 3 -P jar https://github.com/SeleniumHQ/htmlunit-driver/releases/download/2.20/htmlunit-driver-standalone-2.20.jar; fi - # Temporarily run HtmlUnit from standalone jar file (it was not part of Selenium server standalone in version 2.53) - - java -cp "jar/selenium-server-standalone-2.53.1.jar:jar/htmlunit-driver-standalone-2.20.jar" org.openqa.grid.selenium.GridLauncher -log ./logs/selenium.log & - # TODO: use this after upgrade to Selenium 3.0.2 - #- /usr/lib/jvm/java-8-oracle/bin/java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.0.2.jar -log selenium.log & + - if [ ! -f jar/selenium-server-standalone-3.1.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.1/selenium-server-standalone-3.1.0.jar; fi + - /usr/lib/jvm/java-8-oracle/bin/java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & - until $(echo | nc localhost 4444); do sleep 1; echo waiting for selenium-server...; done - php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & From 8900b1fc26f071724c04774ea129ddfc402684f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 23 Feb 2017 19:03:54 +0000 Subject: [PATCH 029/487] Identify SauceLabs tests by name and tags --- tests/functional/WebDriverTestCase.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 62f98b5d1..3673d0a8e 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -87,6 +87,8 @@ protected function setUpSauceLabs() $this->desiredCapabilities->setBrowserName(getenv('BROWSER_NAME')); $this->desiredCapabilities->setVersion(getenv('VERSION')); $this->desiredCapabilities->setPlatform(getenv('PLATFORM')); + $this->desiredCapabilities->setCapability('name', get_class($this) . '::' . $this->getName()); + $this->desiredCapabilities->setCapability('tags', [get_class($this)]); if (getenv('TRAVIS_JOB_NUMBER')) { $this->desiredCapabilities->setCapability('tunnel-identifier', getenv('TRAVIS_JOB_NUMBER')); From bab090739c5bbc7d6c4efc8da1505665b15ca144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 23 Feb 2017 20:04:13 +0000 Subject: [PATCH 030/487] Report test results to saucelabs API to show the results on their dashboard --- composer.json | 2 +- phpunit.xml.dist | 5 ++ .../ReportSauceLabsStatusListener.php | 88 +++++++++++++++++++ tests/functional/WebDriverTestCase.php | 16 +++- 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 tests/functional/ReportSauceLabsStatusListener.php diff --git a/composer.json b/composer.json index 3523361f9..a80697c47 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ }, "autoload-dev": { "psr-4": { - "Facebook\\WebDriver\\": "tests/unit" + "Facebook\\WebDriver\\": ["tests/unit", "tests/functional"] }, "classmap": ["tests/functional/"] } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 894ba470a..430a8fb5c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,4 +29,9 @@ ./lib + + + + + diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php new file mode 100644 index 000000000..b88b1cdf7 --- /dev/null +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -0,0 +1,88 @@ +driver instanceof RemoteWebDriver) { + return; + } + + /** @var WebDriverTestCase $test */ + if (!$test->isSauceLabsBuild()) { + return; + } + + $testStatus = $test->getStatus(); + + if ($this->testWasSkippedOrIncomplete($testStatus)) { + return; + } + + $endpointUrl = sprintf( + 'https://saucelabs.com/rest/v1/%s/jobs/%s', + getenv('SAUCE_USERNAME'), + $test->driver->getSessionID() + ); + + $data = [ + 'passed' => ($testStatus === \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED), + 'custom-data' => ['message' => $test->getStatusMessage()], + ]; + + $this->submitToSauceLabs($endpointUrl, $data); + } + + /** + * @param int $testStatus + * @return bool + */ + private function testWasSkippedOrIncomplete($testStatus) + { + if ($testStatus === \PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED + || $testStatus === \PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) { + return true; + } + + return false; + } + + /** + * @param string $url + * @param array $data + */ + private function submitToSauceLabs($url, array $data) + { + $curl = curl_init($url); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($curl, CURLOPT_USERPWD, getenv('SAUCE_USERNAME') . ':' . getenv('SAUCE_ACCESS_KEY')); + curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); + + curl_exec($curl); + + if (curl_errno($curl)) { + throw new \Exception(sprintf('Error publishing test results to SauceLabs: %s', curl_error($curl))); + } + + curl_close($curl); + } +} diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 3673d0a8e..a2639bae7 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -25,12 +25,12 @@ */ class WebDriverTestCase extends \PHPUnit_Framework_TestCase { + /** @var RemoteWebDriver $driver */ + public $driver; /** @var bool Indicate whether WebDriver should be created on setUp */ protected $createWebDriver = true; /** @var string */ protected $serverUrl = 'http://localhost:4444/wd/hub'; - /** @var RemoteWebDriver $driver */ - protected $driver; /** @var DesiredCapabilities */ protected $desiredCapabilities; @@ -38,7 +38,7 @@ protected function setUp() { $this->desiredCapabilities = new DesiredCapabilities(); - if (getenv('SAUCELABS')) { + if ($this->isSauceLabsBuild()) { $this->setUpSauceLabs(); } else { if (getenv('BROWSER_NAME')) { @@ -66,6 +66,14 @@ protected function tearDown() } } + /** + * @return bool + */ + public function isSauceLabsBuild() + { + return getenv('SAUCELABS') ? true : false; + } + /** * Get the URL of given test HTML on running webserver. * @@ -101,7 +109,7 @@ protected function setUpSauceLabs() */ protected function skipOnSauceLabs($message = 'Not supported by SauceLabs') { - if (getenv('SAUCELABS')) { + if ($this->isSauceLabsBuild()) { $this->markTestSkipped($message); } } From 1cdbf31156271381c65eab169923c3f744648a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 24 Feb 2017 00:36:39 +0000 Subject: [PATCH 031/487] Extend wait once more to make the script more stable in slower network environments like with SauceLabs --- tests/functional/RemoteWebDriverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 858bcd786..52f1d9cb7 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -169,7 +169,7 @@ function(){document.getElementById("id_test").innerHTML = "Text changed by scrip $this->assertSame('Test by ID', $element->getText()); // If we wait, the script should be executed - usleep(550000); // wait 550 ms + usleep(1000000); // wait 1000 ms $this->assertSame('Text changed by script', $element->getText()); } From 3c4c512e15011b674bb9f409ef365854ed7bd3a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 24 Feb 2017 11:17:29 +0000 Subject: [PATCH 032/487] Do not even start (ie. exclude) tests not working on saucelabs, instead of skipping them --- .travis.yml | 3 ++- tests/functional/RemoteWebDriverTest.php | 6 ++---- tests/functional/WebDriverTestCase.php | 10 ---------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index b771dd496..353936986 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,8 @@ before_script: - php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & script: - - ./vendor/bin/phpunit --coverage-clover ./logs/coverage-clover.xml + - if [ -n "$SAUCELABS" ]; then EXTRA_PARAMS="--exclude-group exclude-saucelabs"; fi + - ./vendor/bin/phpunit --coverage-clover ./logs/coverage-clover.xml $EXTRA_PARAMS after_script: - cat ./logs/selenium.log diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 52f1d9cb7..7b60b69d7 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -75,12 +75,11 @@ public function testShouldGetSessionId() } /** + * @group exclude-saucelabs * @covers ::getAllSessions */ public function testShouldGetAllSessions() { - $this->skipOnSauceLabs('getAllSessions() is not supported on SauceLabs'); - $sessions = RemoteWebDriver::getAllSessions($this->serverUrl); $this->assertInternalType('array', $sessions); @@ -92,14 +91,13 @@ public function testShouldGetAllSessions() } /** + * @group exclude-saucelabs * @covers ::getAllSessions * @covers ::getCommandExecutor * @covers ::quit */ public function testShouldQuitAndUnsetExecutor() { - $this->skipOnSauceLabs('getAllSessions() is not supported on SauceLabs'); - $this->assertCount(1, RemoteWebDriver::getAllSessions($this->serverUrl)); $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index a2639bae7..84883574a 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -103,14 +103,4 @@ protected function setUpSauceLabs() $this->desiredCapabilities->setCapability('build', getenv('TRAVIS_JOB_NUMBER')); } } - - /** - * @param string $message - */ - protected function skipOnSauceLabs($message = 'Not supported by SauceLabs') - { - if ($this->isSauceLabsBuild()) { - $this->markTestSkipped($message); - } - } } From 6fb60b020e39b89dc3e913c294bc780bf545b9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 26 Feb 2017 18:45:37 +0000 Subject: [PATCH 033/487] Add travis and saucelabs badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d2a8d6132..cfdf0bc3b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # php-webdriver – Selenium WebDriver bindings for PHP [![Latest Stable Version](https://img.shields.io/packagist/v/facebook/webdriver.svg?style=flat-square)](https://packagist.org/packages/facebook/webdriver) +[![Travis Build](https://img.shields.io/travis/facebook/php-webdriver/community.svg?style=flat-square)](https://travis-ci.org/facebook/php-webdriver) +[![Sauce Test Status](https://saucelabs.com/buildstatus/php-webdriver)](https://saucelabs.com/u/php-webdriver) [![Total Downloads](https://img.shields.io/packagist/dt/facebook/webdriver.svg?style=flat-square)](https://packagist.org/packages/facebook/webdriver) [![License](https://img.shields.io/packagist/l/facebook/webdriver.svg?style=flat-square)](https://packagist.org/packages/facebook/webdriver) From 75af11ac9661ff76cf857ead31b30cfac9528ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 6 Mar 2017 21:49:43 +0000 Subject: [PATCH 034/487] Do not throw fatal error when null is passed to sendKeys (fixes #419) --- CHANGELOG.md | 3 ++ lib/Interactions/WebDriverCompositeAction.php | 2 +- lib/WebDriverKeys.php | 12 +++-- tests/unit/WebDriverKeysTest.php | 54 +++++++++++++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 tests/unit/WebDriverKeysTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b99a4f66..87407e0c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. - `ext-zip` is now specified as required dependency in composer.json (but the extension was already required by the code, though). +### Fixed +- Do not throw fatal error when `null` is passed to `sendKeys()`. + ## 1.3.0 - 2017-01-13 ### Added - Added `getCapabilities()` method of `RemoteWebDriver`, to retrieve actual capabilities acknowledged by the remote driver on startup. diff --git a/lib/Interactions/WebDriverCompositeAction.php b/lib/Interactions/WebDriverCompositeAction.php index 75b879082..75a4837e1 100644 --- a/lib/Interactions/WebDriverCompositeAction.php +++ b/lib/Interactions/WebDriverCompositeAction.php @@ -23,7 +23,7 @@ class WebDriverCompositeAction implements WebDriverAction { /** - * @var array + * @var WebDriverAction[] */ private $actions = []; diff --git a/lib/WebDriverKeys.php b/lib/WebDriverKeys.php index 57d37c1eb..c25748e8c 100644 --- a/lib/WebDriverKeys.php +++ b/lib/WebDriverKeys.php @@ -88,24 +88,28 @@ class WebDriverKeys /** * Encode input of `sendKeys()`. - * @param string|array $keys + * @param string|array|int|float $keys * @return array */ public static function encode($keys) { if (is_numeric($keys)) { - $keys = '' . $keys; + $keys = (string) $keys; } if (is_string($keys)) { $keys = [$keys]; } + if (!is_array($keys)) { + return []; + } + $encoded = []; foreach ($keys as $key) { if (is_array($key)) { - // handle modified keys - $key = implode('', $key) . self::NULL; + // handle key modifiers + $key = implode('', $key) . self::NULL; // the NULL clears the input state (eg. previous modifiers) } $encoded[] = (string) $key; } diff --git a/tests/unit/WebDriverKeysTest.php b/tests/unit/WebDriverKeysTest.php new file mode 100644 index 000000000..9a205c7de --- /dev/null +++ b/tests/unit/WebDriverKeysTest.php @@ -0,0 +1,54 @@ +assertSame($expectedOutput, WebDriverKeys::encode($keys)); + } + + /** + * @return array[] + */ + 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']], + '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], + ], + 'null' => [null, []], + ]; + } +} From ecc272a4ece0975ee320e87e2ed5f5ee737cfdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Mar 2017 10:28:01 +0000 Subject: [PATCH 035/487] Add composer scripts for simple codestyle check/fix --- .travis.yml | 4 +--- CONTRIBUTING.md | 21 +++++++++++++++++---- composer.json | 10 ++++++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 353936986..2eaa8fd02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,9 +48,7 @@ matrix: - php: 7.0 env: CHECK_CODESTYLE=1 before_script: ~ - script: - - ./vendor/bin/php-cs-fixer fix --diff --dry-run - - ./vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/ + script: composer codestyle:check after_script: ~ after_success: ~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a73c9eafd..3bea30bba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,15 +29,28 @@ To execute all tests simply run: If you want to execute just the unit tests, run: ./vendor/bin/phpunit --testsuite unit + +For the functional tests you must first [download](http://selenium-release.storage.googleapis.com/index.html) and start +the selenium standalone server, start the local PHP server which will serve the test pages and then run the `functional` +test suite: + + java -jar selenium-server-standalone-2.53.1.jar -log selenium.log & + php -S localhost:8000 -t tests/functional/web/ & + ./vendor/bin/phpunit --testsuite functional -For the functional tests you must first download and start the selenium server, then run the `functional` test suite: +The functional tests will be started in HtmlUnit headless browser by default. If you want to run them in eg. Firefox, +simply set the `BROWSER` environment variable: - java -jar selenium-server-standalone-2.48.2.jar -log selenium.log & + ... + export BROWSER_NAME="firefox" ./vendor/bin/phpunit --testsuite functional ### Check coding style Your code-style should comply with [PSR-2](http://www.php-fig.org/psr/psr-2/). To make sure your code matches this requirement run: - ./vendor/bin/php-cs-fixer fix --diff --dry-run - ./vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/ + composer codestyle:check + +To auto-fix the codestyle simply run: + + composer codestyle:fix diff --git a/composer.json b/composer.json index a80697c47..24825d658 100644 --- a/composer.json +++ b/composer.json @@ -33,5 +33,15 @@ "Facebook\\WebDriver\\": ["tests/unit", "tests/functional"] }, "classmap": ["tests/functional/"] + }, + "scripts": { + "codestyle:check": [ + "vendor/bin/php-cs-fixer fix --diff --dry-run", + "vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/" + ], + "codestyle:fix": [ + "vendor/bin/php-cs-fixer fix --diff || exit 0", + "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" + ] } } From b10102f60f097108d783aa97fa5f3186a146c961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 11 Mar 2017 22:46:23 +0000 Subject: [PATCH 036/487] Deprecate isJavascriptEnabled method. See https://github.com/SeleniumHQ/selenium/commit/5b0f88ef3256595148b357dec6209735cab25bea --- CHANGELOG.md | 1 + lib/Remote/DesiredCapabilities.php | 2 ++ lib/WebDriverCapabilities.php | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87407e0c1..b63abff80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Cookies should now be set using `Cookie` value object instead of an array when passed to to `addCookie()` method of `WebDriverOptions`. - Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. - `ext-zip` is now specified as required dependency in composer.json (but the extension was already required by the code, though). +- Deprecate `WebDriverCapabilities::isJavascriptEnabled()` method. ### Fixed - Do not throw fatal error when `null` is passed to `sendKeys()`. diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 94d8e1de2..3990343af 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -123,6 +123,8 @@ public function is($capability_name) } /** + * @todo Remove in next major release (BC) + * @deprecated All browsers are always JS enabled except HtmlUnit and it's not meaningful to disable JS execution. * @return bool Whether javascript is enabled. */ public function isJavascriptEnabled() diff --git a/lib/WebDriverCapabilities.php b/lib/WebDriverCapabilities.php index b489a1e6e..9ac851d90 100644 --- a/lib/WebDriverCapabilities.php +++ b/lib/WebDriverCapabilities.php @@ -45,6 +45,8 @@ public function getVersion(); public function is($capability_name); /** + * @todo Remove in next major release (BC) + * @deprecated All browsers are always JS enabled except HtmlUnit and it's not meaningful to disable JS execution. * @return bool Whether javascript is enabled. */ public function isJavascriptEnabled(); From 89a6ce780387202ccbdb511eab613a5691613034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 16 Mar 2017 13:02:52 +0000 Subject: [PATCH 037/487] Workaround in example script for Selenium bug (fixes #402) --- example.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/example.php b/example.php index 7bebd10b5..263d0c1d6 100644 --- a/example.php +++ b/example.php @@ -13,8 +13,8 @@ $capabilities = DesiredCapabilities::firefox(); $driver = RemoteWebDriver::create($host, $capabilities, 5000); -// navigate to 'http://docs.seleniumhq.org/' -$driver->get('http://docs.seleniumhq.org/'); +// navigate to 'http://www.seleniumhq.org/' +$driver->get('http://www.seleniumhq.org/'); // adding cookie $driver->manage()->deleteAllCookies(); @@ -31,17 +31,24 @@ ); $link->click(); +// wait until the page is loaded +$driver->wait()->until( + WebDriverExpectedCondition::titleContains('About') +); + // 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"; -// Search 'php' in the search box -$input = $driver->findElement( - WebDriverBy::id('q') -); -$input->sendKeys('php')->submit(); +// write 'php' in the search box +$driver->findElement(WebDriverBy::id('q')) + ->sendKeys('php'); + +// submit the form +$driver->findElement(WebDriverBy::id('submit')) + ->click(); // submit() does not work in Selenium 3 because of bug https://github.com/SeleniumHQ/selenium/issues/3398 // wait at most 10 seconds until at least one result is shown $driver->wait(10)->until( From f6ae7db59f4be0a5a8f791448c72d9d4d920e514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 16 Mar 2017 14:44:20 +0000 Subject: [PATCH 038/487] Use previous Firefox ESR, as new ESR is based on Firefox 52 (which requires Geckodriver) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2eaa8fd02..a42bfbe20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - php: 7.0 env: BROWSER_NAME="firefox" addons: - firefox: "latest-esr" + firefox: "45.8.0esr" apt: packages: - oracle-java8-installer From 4f141e058d55ebd762c11201ac129c7cb98bd925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 18 Mar 2017 12:00:54 +0000 Subject: [PATCH 039/487] Remove Firefox 45 build, bacause it is unstable and slow --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a42bfbe20..0c987bc87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,13 +29,6 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.0 - env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="45.0" PLATFORM="Linux" - before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & - addons: - sauce_connect: true - jwt: - secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & From 48b9c7aebed26b252ead3e15a621db46b405fd78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 18 Mar 2017 12:57:15 +0000 Subject: [PATCH 040/487] Setup branch alias to allow simplier installation of development versions --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 24825d658..1381c13d5 100644 --- a/composer.json +++ b/composer.json @@ -43,5 +43,10 @@ "vendor/bin/php-cs-fixer fix --diff || exit 0", "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" ] + }, + "extra": { + "branch-alias": { + "dev-community": "1.4-dev" + } } } From 51ef9da2b689d2680ad006d4cc3c9ec95977e015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 19 Mar 2017 11:17:02 +0000 Subject: [PATCH 041/487] Rename textToBePresentInElementValue expected condition to make its name systematic with other conditions --- CHANGELOG.md | 1 + lib/WebDriverExpectedCondition.php | 14 ++++++++ tests/unit/WebDriverExpectedConditionTest.php | 35 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b63abff80..2f6b5e772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. - `ext-zip` is now specified as required dependency in composer.json (but the extension was already required by the code, though). - Deprecate `WebDriverCapabilities::isJavascriptEnabled()` method. +- Deprecate `textToBePresentInElementValue` expected condition in favor of `elementValueContains`. ### Fixed - Do not throw fatal error when `null` is passed to `sendKeys()`. diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 58ba939e9..347a5d28a 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -292,11 +292,25 @@ function (WebDriver $driver) use ($by, $regexp) { /** * An expectation for checking if the given text is present in the specified elements value attribute. * + * @codeCoverageIgnore + * @deprecated Use WebDriverExpectedCondition::elementValueContains() instead * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element value. * @return WebDriverExpectedCondition Condition returns whether the text is present in value attribute. */ public static function textToBePresentInElementValue(WebDriverBy $by, $text) + { + return self::elementValueContains($by, $text); + } + + /** + * An expectation for checking if the given text is present in the specified elements value attribute. + * + * @param WebDriverBy $by The locator used to find the element. + * @param string $text The text to be presented in the element value. + * @return WebDriverExpectedCondition Condition returns whether the text is present in value attribute. + */ + public static function elementValueContains(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index 93d98b392..c2605a97d 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -292,6 +292,41 @@ public function testShouldDetectElementTextMatchesCondition() $this->assertTrue($this->wait->until($condition)); } + public function testShouldDetectElementValueContainsCondition() + { + // Set-up the consecutive calls to apply() as follows: + // Call #1: throws NoSuchElementException + // Call #2: return Element, but getAttribute will throw StaleElementReferenceException + // Call #3: return Element, getAttribute('value') will return not-matching text + // Call #4: return Element, getAttribute('value') will return matching text + + $element = $this->createRemoteWebElementMock(); + + $element->expects($this->at(0)) + ->method('getAttribute') + ->with('value') + ->willThrowException(new StaleElementReferenceException('')); + + $element->expects($this->at(1)) + ->method('getAttribute') + ->with('value') + ->willReturn('wrong text'); + + $element->expects($this->at(2)) + ->method('getAttribute') + ->with('value') + ->willReturn('matching text'); + + $this->setupDriverToReturnElementAfterAnException($element, 4); + + $condition = WebDriverExpectedCondition::elementValueContains( + WebDriverBy::cssSelector('.foo'), + 'matching' + ); + + $this->assertTrue($this->wait->until($condition)); + } + public function testShouldDetectNumberOfWindowsToBeCondition() { $this->driverMock->expects($this->any()) From 7a855dd1b82d350e4fff9509b5031b37bcd0434f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 19 Mar 2017 11:17:30 +0000 Subject: [PATCH 042/487] Add UT for invisibilityOfElementLocated expected contition --- tests/unit/WebDriverExpectedConditionTest.php | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index c2605a97d..e786167a8 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -185,6 +185,73 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() $this->assertSame($element, $this->wait->until($condition)); } + public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchElementException() + { + $element = $this->createRemoteWebElementMock(); + + $this->driverMock->expects($this->at(0)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willReturn($element); + + $element->expects($this->at(0)) + ->method('isDisplayed') + ->willReturn(true); + + $this->driverMock->expects($this->at(1)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willThrowException(new NoSuchElementException('')); + + $condition = WebDriverExpectedCondition::invisibilityOfElementLocated(WebDriverBy::cssSelector('.foo')); + + $this->assertTrue($this->wait->until($condition)); + } + + public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElementReferenceException() + { + $element = $this->createRemoteWebElementMock(); + + $this->driverMock->expects($this->exactly(2)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willReturn($element); + + $element->expects($this->at(0)) + ->method('isDisplayed') + ->willReturn(true); + + $element->expects($this->at(1)) + ->method('isDisplayed') + ->willThrowException(new StaleElementReferenceException('')); + + $condition = WebDriverExpectedCondition::invisibilityOfElementLocated(WebDriverBy::cssSelector('.foo')); + + $this->assertTrue($this->wait->until($condition)); + } + + public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElementBecamesInvisible() + { + $element = $this->createRemoteWebElementMock(); + + $this->driverMock->expects($this->exactly(2)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willReturn($element); + + $element->expects($this->at(0)) + ->method('isDisplayed') + ->willReturn(true); + + $element->expects($this->at(1)) + ->method('isDisplayed') + ->willReturn(false); + + $condition = WebDriverExpectedCondition::invisibilityOfElementLocated(WebDriverBy::cssSelector('.foo')); + + $this->assertTrue($this->wait->until($condition)); + } + public function testShouldDetectVisibilityOfCondition() { $element = $this->createRemoteWebElementMock(); From 3ea034c056189e11c0ce7985332a9f4b5b2b5db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 22 Mar 2017 11:55:12 +0100 Subject: [PATCH 043/487] Release version 1.4.0 --- CHANGELOG.md | 2 ++ composer.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f6b5e772..89c93fadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.4.0 - 2017-03-22 ### Changed - Cookies should now be set using `Cookie` value object instead of an array when passed to to `addCookie()` method of `WebDriverOptions`. - Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility. diff --git a/composer.json b/composer.json index 1381c13d5..0a87cb3ed 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ }, "extra": { "branch-alias": { - "dev-community": "1.4-dev" + "dev-community": "1.5-dev" } } } From 905b0e3ea21286d7839c939ae0a70ff47c20c34c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 24 Apr 2017 23:25:02 +0200 Subject: [PATCH 044/487] Update https link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bea30bba..6c59bbe84 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ send a pull request (see bellow) with your contribution. 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 -Note before any pull request can be accepted, a [Contributors Licensing Agreement](http://developers.facebook.com/opensource/cla) must be signed. +Note before any pull request can be accepted, a [Contributors Licensing Agreement](https://developers.facebook.com/opensource/cla) must be signed. 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/) From d06347f10ef20989f1fe705b7692c23a4ebe00dc Mon Sep 17 00:00:00 2001 From: toniperic Date: Thu, 27 Apr 2017 10:13:47 +0200 Subject: [PATCH 045/487] Correct constant checking Prior to this change I would be getting `Constant CURLOPT_CONNECTTIMEOUT_MS already defined` since PHP would try to re-define a constant. --- lib/Net/URLChecker.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Net/URLChecker.php b/lib/Net/URLChecker.php index 575daac56..00ad62593 100644 --- a/lib/Net/URLChecker.php +++ b/lib/Net/URLChecker.php @@ -66,7 +66,7 @@ private function getHTTPResponseCode($url) curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // The PHP doc indicates that CURLOPT_CONNECTTIMEOUT_MS constant is added in cURL 7.16.2 // available since PHP 5.2.3. - if (!defined(CURLOPT_CONNECTTIMEOUT_MS)) { + if (!defined('CURLOPT_CONNECTTIMEOUT_MS')) { define('CURLOPT_CONNECTTIMEOUT_MS', 156); // default value for CURLOPT_CONNECTTIMEOUT_MS } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, self::CONNECT_TIMEOUT_MS); From 94f64e54e50b2b4b0883d25aefb743d006eaa98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 28 Apr 2017 12:37:04 +0200 Subject: [PATCH 046/487] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c93fadd..9a4410c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Fixed +- Do not throw notice `Constant CURLOPT_CONNECTTIMEOUT_MS already defined`. ## 1.4.0 - 2017-03-22 ### Changed From 75c22b51131ed6b11276672a8a39b5b7976c0ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 28 Apr 2017 02:52:01 +0200 Subject: [PATCH 047/487] Make sure sudo mode is disabled in all travis builds --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0c987bc87..1c2146bb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ php: - 7.0 - 7.1 +sudo: false + matrix: include: # Add build to run tests against Firefox inside Travis environment (other runs are agains HtmlUnit by default) From 4a3bb63e73b9e30f6ac601a7b237a44d3c07f1f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 28 Apr 2017 12:30:57 +0200 Subject: [PATCH 048/487] Print logs in travis only if created --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c2146bb3..c957fd41e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,8 +74,8 @@ script: - ./vendor/bin/phpunit --coverage-clover ./logs/coverage-clover.xml $EXTRA_PARAMS after_script: - - cat ./logs/selenium.log - - cat ./logs/php-server.log + - if [ -f ./logs/selenium.log ]; then cat ./logs/selenium.log; fi + - if [ -f ./logs/php-server.log ]; then cat ./logs/php-server.log; fi after_success: - travis_retry php vendor/bin/coveralls -v From 66e37cfb00b698bfc96dd47f71ae1bd4659e6bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 28 Apr 2017 15:07:40 +0200 Subject: [PATCH 049/487] Bind PHP server to 127.0.0.1 to overcome Bad Gateway error when using 'localhost' --- .travis.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c957fd41e..5c5ca4c0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,14 +26,18 @@ matrix: # Saucelabs builds - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" - before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + 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: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.0 env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" - before_script: php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + 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: @@ -66,8 +70,9 @@ before_script: - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.1.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.1/selenium-server-standalone-3.1.0.jar; fi - /usr/lib/jvm/java-8-oracle/bin/java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & - - until $(echo | nc localhost 4444); do sleep 1; echo waiting for selenium-server...; done - - php -S localhost:8000 -t tests/functional/web/ &>>./logs/php-server.log & + - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" + - 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" script: - if [ -n "$SAUCELABS" ]; then EXTRA_PARAMS="--exclude-group exclude-saucelabs"; fi From eadb0b7a7c3e6578185197fd40158b08c3164c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 28 Apr 2017 16:54:49 +0200 Subject: [PATCH 050/487] Release version 1.4.1 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4410c09..c7d018435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.4.1 - 2017-04-28 ### Fixed - Do not throw notice `Constant CURLOPT_CONNECTTIMEOUT_MS already defined`. From 10c8c8449713fc79a672b2636e10284ab92b110e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 21 Jun 2017 11:15:17 +0200 Subject: [PATCH 051/487] Add LICENCE.md --- LICENCE.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENCE.md diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 000000000..0b3f67bd7 --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2004-present Facebook + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 8fee7dceac5e3f867fe91d45743e3bc74b3b6efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 4 Aug 2017 21:37:11 +0200 Subject: [PATCH 052/487] Build on PHP 7.1 by default --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c5ca4c0e..fde500451 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ sudo: false matrix: include: - # Add build to run tests against Firefox inside Travis environment (other runs are agains HtmlUnit by default) - - php: 7.0 + # Add build to run tests against Firefox inside Travis environment (other runs are against HtmlUnit by default) + - php: 7.1 env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" @@ -20,11 +20,11 @@ matrix: - oracle-java8-installer # Build with lowest possible dependencies - - php: 7.0 + - php: 7.1 env: dependencies="--prefer-lowest" # Saucelabs builds - - php: 7.0 + - php: 7.1 env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -33,7 +33,7 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.0 + - php: 7.1 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 & @@ -43,8 +43,8 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - # Add PHP 7 build to check codestyle only in PHP 7 build - - php: 7.0 + # Codestyle check build + - php: 7.1 env: CHECK_CODESTYLE=1 before_script: ~ script: composer codestyle:check From 6a3b3520d85c63434f00eb15bd4eebfcc0d5e750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 4 Aug 2017 21:38:09 +0200 Subject: [PATCH 053/487] Build on Ubuntu Trusty, so we don't need to install Java 8 by ourselves --- .travis.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index fde500451..5ae58d50e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: php +dist: trusty + php: - 5.5 - 5.6 @@ -15,9 +17,6 @@ matrix: env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" - apt: - packages: - - oracle-java8-installer # Build with lowest possible dependencies - php: 7.1 @@ -69,7 +68,7 @@ install: before_script: - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.1.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.1/selenium-server-standalone-3.1.0.jar; fi - - /usr/lib/jvm/java-8-oracle/bin/java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & + - java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - 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" @@ -84,8 +83,3 @@ after_script: after_success: - travis_retry php vendor/bin/coveralls -v - -addons: - apt: - packages: - - oracle-java8-installer From 3289cd39ab82d53b09853d98cf5bb780685eb791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 4 Aug 2017 21:49:57 +0200 Subject: [PATCH 054/487] Add headless chrome travis build --- .travis.yml | 24 ++++++++++++++++-------- tests/functional/WebDriverAlertTest.php | 6 ++++++ tests/functional/WebDriverTestCase.php | 7 +++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ae58d50e..b12b404db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: php - +sudo: false dist: trusty php: @@ -8,16 +8,26 @@ php: - 7.0 - 7.1 -sudo: false +env: + global: + - DISPLAY=:99.0 + - BROWSER_NAME="htmlunit" + - CHROMEDRIVER_VERSION="2.31" matrix: include: - # Add build to run tests against Firefox inside Travis environment (other runs are against HtmlUnit by default) + # Add build to run tests against Firefox inside Travis environment - php: 7.1 env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" + # Add build to run tests against Chrome inside Travis environment + - php: 7.1 + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" + addons: + chrome: beta + # Build with lowest possible dependencies - php: 7.1 env: dependencies="--prefer-lowest" @@ -50,10 +60,6 @@ matrix: after_script: ~ after_success: ~ -env: - global: - - DISPLAY=:99.0 - cache: directories: - $HOME/.composer/cache @@ -66,9 +72,11 @@ install: - travis_retry composer update --no-interaction $dependencies before_script: + - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi + - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=./chromedriver/chromedriver; fi - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.1.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.1/selenium-server-standalone-3.1.0.jar; fi - - java -Dwebdriver.firefox.marionette=false -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & + - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - 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" diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index eddf3a9ae..61126b9bd 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -25,6 +25,12 @@ class WebDriverAlertTest extends WebDriverTestCase { protected function setUp() { + if (getenv('CHROME_HEADLESS') === '1') { + // Alerts in headless mode should be available in next Chrome version (61), see: + // https://bugs.chromium.org/p/chromium/issues/detail?id=718235 + $this->markTestSkipped('Alerts not yet supported by headless Chrome'); + } + parent::setUp(); $this->driver->get($this->getTestPageUrl('alert.html')); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 84883574a..0c11e3fb8 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -15,6 +15,7 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Exception\NoSuchWindowException; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; @@ -47,6 +48,12 @@ protected function setUp() $browserName = WebDriverBrowserType::HTMLUNIT; } + if ($browserName === WebDriverBrowserType::CHROME) { + $chromeOptions = new ChromeOptions(); + $chromeOptions->addArguments(['--headless', 'window-size=1024,768']); + $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); + } + $this->desiredCapabilities->setBrowserName($browserName); } From 23b1d70c7671443ed686313f71c5ae293e676d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 4 Aug 2017 22:43:45 +0200 Subject: [PATCH 055/487] Upgrade Selenium server in Travis buils to 3.4.0 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b12b404db..68db86dfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,8 +75,8 @@ before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=./chromedriver/chromedriver; fi - sh -e /etc/init.d/xvfb start - - if [ ! -f jar/selenium-server-standalone-3.1.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.1/selenium-server-standalone-3.1.0.jar; fi - - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.1.0.jar -log ./logs/selenium.log & + - if [ ! -f jar/selenium-server-standalone-3.4.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar; fi + - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.4.0.jar -log ./logs/selenium.log & - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - 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" From 5f5d0fd96e984d31d80ec06e866ff126dfce496e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 5 Aug 2017 01:40:55 +0200 Subject: [PATCH 056/487] Add MS Edge build on SauceLabs --- .travis.yml | 13 +++++- tests/functional/FileUploadTest.php | 14 ++---- .../RemoteWebDriverFindElementTest.php | 2 +- tests/functional/RemoteWebElementTest.php | 10 ++++- tests/functional/WebDriverSelectTest.php | 44 +++++++++++++++++-- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 68db86dfb..65e48e036 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,15 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= + - php: 7.1 + env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="15.15063" 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: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= # Codestyle check build - php: 7.1 @@ -82,7 +91,9 @@ before_script: - until $(echo | nc localhost 8000); do sleep 1; echo waiting for PHP server on port 8000...; done; echo "PHP server started" script: - - if [ -n "$SAUCELABS" ]; then EXTRA_PARAMS="--exclude-group exclude-saucelabs"; fi + - if [ -n "$SAUCELABS" ]; then EXCLUDE_GROUP+="exclude-saucelabs,"; fi + - if [ "$BROWSER_NAME" = "MicrosoftEdge" ]; then EXCLUDE_GROUP+="exclude-edge,"; fi + - if [ -n "$EXCLUDE_GROUP" ]; then EXTRA_PARAMS+=" --exclude-group $EXCLUDE_GROUP"; fi - ./vendor/bin/phpunit --coverage-clover ./logs/coverage-clover.xml $EXTRA_PARAMS after_script: diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index ef6cf1bb2..e88139953 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -23,6 +23,10 @@ */ class FileUploadTest extends WebDriverTestCase { + /** + * @group exclude-edge + * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6052385/ + */ public function testShouldUploadAFile() { $this->driver->get($this->getTestPageUrl('upload.html')); @@ -50,16 +54,6 @@ public function testShouldUploadAFile() $this->assertSame('10', $uploadedFileSize); } - public function xtestUselessFileDetectorSendKeys() - { - $this->driver->get($this->getTestPageUrl('upload.html')); - - $file_input = $this->driver->findElement(WebDriverBy::id('upload')); - $file_input->sendKeys($this->getTestFilePath()); - - $this->assertEquals($this->getTestFilePath(), $file_input->getAttribute('value')); - } - private function getTestFilePath() { return __DIR__ . '/Fixtures/FileUploadTestFile.txt'; diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 808763bad..004a554b8 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -28,7 +28,7 @@ public function testShouldThrowExceptionOfElementCannotBeFound() { $this->driver->get($this->getTestPageUrl('index.html')); - $this->setExpectedException(NoSuchElementException::class, 'Unable to locate element'); + $this->setExpectedException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('not_existing')); } diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 26d13f6e2..643e2a336 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -22,6 +22,8 @@ class RemoteWebElementTest extends WebDriverTestCase { /** * @covers ::getText + * @group exclude-edge + * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/5569343/ */ public function testShouldGetText() { @@ -90,8 +92,11 @@ public function testShouldGetCssValue() $this->assertSame('solid', $elementWithBorder->getCSSValue('border-left-style')); $this->assertSame('none', $elementWithoutBorder->getCSSValue('border-left-style')); - $this->assertSame('rgba(0, 0, 0, 1)', $elementWithBorder->getCSSValue('border-left-color')); - $this->assertSame('rgba(0, 0, 0, 1)', $elementWithoutBorder->getCSSValue('border-left-color')); + // Browser could report color in either rgb (like MS Edge) or rgba (like everyone else) + $this->assertRegExp( + '/rgba?\(0, 0, 0(, 1)?\)/', + $elementWithBorder->getCSSValue('border-left-color') + ); } /** @@ -206,6 +211,7 @@ public function testShouldSelectedInputsOrOptions() /** * @covers ::submit + * @group exclude-edge */ public function testShouldSubmitFormBySubmitEventOnForm() { diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index ac32cbca7..33ebe38f9 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -129,6 +129,10 @@ public function testShouldSelectOptionOfSimpleSelectByIndex() $this->assertSame('fourth', $select->getFirstSelectedOption()->getAttribute('value')); } + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ public function testShouldSelectOptionOfMultipleSelectByIndex() { $select = $this->getWebDriverSelectForMultipleSelect(); @@ -171,6 +175,10 @@ public function testShouldSelectOptionOfSimpleSelectByValue() $this->assertSame('fourth', $select->getFirstSelectedOption()->getAttribute('value')); } + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ public function testShouldSelectOptionOfMultipleSelectByValue() { $select = $this->getWebDriverSelectForMultipleSelect(); @@ -213,6 +221,10 @@ public function testShouldSelectOptionOfSimpleSelectByVisibleText() $this->assertSame('fifth', $select->getFirstSelectedOption()->getAttribute('value')); } + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ public function testShouldSelectOptionOfMultipleSelectByVisibleText() { $select = $this->getWebDriverSelectForMultipleSelect(); @@ -255,6 +267,10 @@ public function testShouldSelectOptionOfSimpleSelectByVisiblePartialText() $this->assertSame('fourth', $select->getFirstSelectedOption()->getAttribute('value')); } + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ public function testShouldSelectOptionOfMultipleSelectByVisiblePartialText() { $select = $this->getWebDriverSelectForMultipleSelect(); @@ -298,6 +314,10 @@ public function testShouldThrowExceptionWhenDeselectingOnSimpleSelect() $select->deselectAll(); } + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ public function testShouldDeselectAllOptionsOnMultipleSelect() { $select = $this->getWebDriverSelectForMultipleSelect(); @@ -312,7 +332,11 @@ public function testShouldDeselectAllOptionsOnMultipleSelect() $this->assertCount(0, $select->getAllSelectedOptions()); } - public function testShouldDeselectOptionByIndex() + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ + public function testShouldDeselectOptionOnMultipleSelectByIndex() { $select = $this->getWebDriverSelectForMultipleSelect(); $select->selectByValue('fourth'); // index 3 @@ -336,7 +360,11 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByIndex() $select->deselectByIndex(0); } - public function testShouldDeselectOptionByValue() + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ + public function testShouldDeselectOptionOnMultipleSelectByValue() { $select = $this->getWebDriverSelectForMultipleSelect(); $select->selectByValue('third'); @@ -360,7 +388,11 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByValue() $select->deselectByValue('first'); } - public function testShouldDeselectOptionByVisibleText() + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ + public function testShouldDeselectOptionOnMultipleSelectByVisibleText() { $select = $this->getWebDriverSelectForMultipleSelect(); $select->selectByValue('fourth'); // text 'Fourth with spaces inside' @@ -386,7 +418,11 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByVisibleText() $select->deselectByVisibleText('First'); } - public function testShouldDeselectOptionByVisiblePartialText() + /** + * @group exclude-edge + * https://connect.microsoft.com/IE/feedback/details/2020772/-microsoft-edge-webdriver-cannot-select-multiple-on-select-html-tag + */ + public function testShouldDeselectOptionOnMultipleSelectByVisiblePartialText() { $select = $this->getWebDriverSelectForMultipleSelect(); $select->selectByValue('fourth'); // text 'Fourth with spaces inside' From 735a6156617d62fe95ed20e6d038daa22b98e74b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 5 Aug 2017 15:20:28 +0200 Subject: [PATCH 057/487] Extend connection timeout, because Edge is slow... --- tests/functional/RemoteWebDriverCreateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 95ff4851c..466e1601c 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -29,7 +29,7 @@ class RemoteWebDriverCreateTest extends WebDriverTestCase public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() { - $this->driver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities, 10000, 13370); + $this->driver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities, 30000, 33370); $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); From cb1f8bd54058ff3ded938e811d50e72e6c3a13ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 4 Aug 2017 22:20:51 +0200 Subject: [PATCH 058/487] Drop PHP 5.5 support --- .travis.yml | 1 - CHANGELOG.md | 2 ++ lib/Support/Events/EventFiringWebDriver.php | 5 ++--- lib/Support/Events/EventFiringWebDriverNavigation.php | 5 ++--- lib/Support/Events/EventFiringWebElement.php | 6 +++--- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 65e48e036..9b2b5894e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ sudo: false dist: trusty php: - - 5.5 - 5.6 - 7.0 - 7.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d018435..182aeda87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6. ## 1.4.1 - 2017-04-28 ### Fixed diff --git a/lib/Support/Events/EventFiringWebDriver.php b/lib/Support/Events/EventFiringWebDriver.php index ffac00e5c..c129ef015 100644 --- a/lib/Support/Events/EventFiringWebDriver.php +++ b/lib/Support/Events/EventFiringWebDriver.php @@ -396,15 +396,14 @@ protected function newElement(WebDriverElement $element) /** * @param mixed $method + * @param mixed $arguments,... */ - protected function dispatch($method) + protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } - $arguments = func_get_args(); - unset($arguments[0]); $this->dispatcher->dispatch($method, $arguments); } diff --git a/lib/Support/Events/EventFiringWebDriverNavigation.php b/lib/Support/Events/EventFiringWebDriverNavigation.php index 5aeaf510b..0008ebbdc 100644 --- a/lib/Support/Events/EventFiringWebDriverNavigation.php +++ b/lib/Support/Events/EventFiringWebDriverNavigation.php @@ -149,15 +149,14 @@ public function to($url) /** * @param mixed $method + * @param mixed $arguments,... */ - protected function dispatch($method) + protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } - $arguments = func_get_args(); - unset($arguments[0]); $this->dispatcher->dispatch($method, $arguments); } diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index f932b20a3..099bb3573 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -392,14 +392,14 @@ protected function dispatchOnException(WebDriverException $exception) /** * @param mixed $method + * @param mixed $arguments,... */ - protected function dispatch($method) + protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } - $arguments = func_get_args(); - unset($arguments[0]); + $this->dispatcher->dispatch($method, $arguments); } From 1c5e7f08d670f1ad921d3425903f852204fd8977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 25 Aug 2017 20:29:48 +0200 Subject: [PATCH 059/487] Extend connection timeout further more to make SauceLabs tests hopefully more stable --- tests/functional/RemoteWebDriverCreateTest.php | 18 ++++++++++++++---- tests/functional/WebDriverTestCase.php | 11 ++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 466e1601c..3140b26b0 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -29,7 +29,12 @@ class RemoteWebDriverCreateTest extends WebDriverTestCase public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() { - $this->driver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities, 30000, 33370); + $this->driver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); @@ -51,8 +56,8 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() $this->driver = RemoteWebDriver::create( $this->serverUrl, $this->desiredCapabilities, - null, - null, + $this->connectionTimeout, + $this->requestTimeout, null, null, $requiredCapabilities @@ -64,7 +69,12 @@ public function testShouldCreateWebDriverWithRequiredCapabilities() public function testShouldCreateInstanceFromExistingSessionId() { // Create driver instance and load page "index.html" - $originalDriver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities); + $originalDriver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); $originalDriver->get($this->getTestPageUrl('index.html')); $this->assertContains('/index.html', $originalDriver->getCurrentURL()); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 0c11e3fb8..77a98f884 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -34,6 +34,10 @@ class WebDriverTestCase extends \PHPUnit_Framework_TestCase protected $serverUrl = 'http://localhost:4444/wd/hub'; /** @var DesiredCapabilities */ protected $desiredCapabilities; + /** @var int */ + protected $connectionTimeout = 60000; + /** @var int */ + protected $requestTimeout = 60000; protected function setUp() { @@ -58,7 +62,12 @@ protected function setUp() } if ($this->createWebDriver) { - $this->driver = RemoteWebDriver::create($this->serverUrl, $this->desiredCapabilities); + $this->driver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); } } From 3581da1e816335f09b3508c6d64baf2a91512cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 31 Aug 2017 17:13:20 +0200 Subject: [PATCH 060/487] Use only PHPUnit 5.4+, drop deprecated calls --- composer.json | 4 +- .../RemoteWebDriverFindElementTest.php | 2 +- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverSelectTest.php | 66 +++++++------------ tests/functional/WebDriverTimeoutsTest.php | 2 +- tests/unit/CookieTest.php | 3 +- tests/unit/WebDriverOptionsTest.php | 6 +- 7 files changed, 31 insertions(+), 54 deletions(-) diff --git a/composer.json b/composer.json index 0a87cb3ed..cbf3155a9 100644 --- a/composer.json +++ b/composer.json @@ -11,13 +11,13 @@ "source": "https://github.com/facebook/php-webdriver" }, "require": { - "php": "^5.5 || ~7.0", + "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1", "ext-curl": "*", "ext-zip": "*" }, "require-dev": { - "phpunit/phpunit": "4.6.* || ~5.0", + "phpunit/phpunit": "^5.4", "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 004a554b8..106d5d90f 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -28,7 +28,7 @@ public function testShouldThrowExceptionOfElementCannotBeFound() { $this->driver->get($this->getTestPageUrl('index.html')); - $this->setExpectedException(NoSuchElementException::class); + $this->expectException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('not_existing')); } diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 61126b9bd..08d8ef73c 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -48,7 +48,7 @@ public function testShouldAcceptAlert() $this->driver->switchTo()->alert()->accept(); - $this->setExpectedException(NoAlertOpenException::class); + $this->expectException(NoAlertOpenException::class); $this->driver->switchTo()->alert()->accept(); } diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index 33ebe38f9..9cd6cf250 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -51,10 +51,8 @@ public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() { $notSelectElement = $this->driver->findElement(WebDriverBy::cssSelector('textarea')); - $this->setExpectedException( - UnexpectedTagNameException::class, - 'Element should have been "select" but was "textarea"' - ); + $this->expectException(UnexpectedTagNameException::class); + $this->expectExceptionMessage('Element should have been "select" but was "textarea"'); new WebDriverSelect($notSelectElement); } @@ -109,10 +107,8 @@ public function testShouldThrowExceptionIfThereIsNoFirstSelectedOptionOfMultiple { $select = $this->getWebDriverSelectForMultipleSelect(); - $this->setExpectedException( - NoSuchElementException::class, - 'No options are selected' - ); + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('No options are selected'); $select->getFirstSelectedOption(); } @@ -155,10 +151,8 @@ public function testShouldThrowExceptionIfThereIsNoOptionIndexToSelect() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - NoSuchElementException::class, - 'Cannot locate option with index: 1337' - ); + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate option with index: 1337'); $select->selectByIndex(1337); } @@ -201,10 +195,8 @@ public function testShouldThrowExceptionIfThereIsNoOptionValueToSelect() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - NoSuchElementException::class, - 'Cannot locate option with value: 1337' - ); + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate option with value: 1337'); $select->selectByValue(1337); } @@ -247,10 +239,8 @@ public function testShouldThrowExceptionIfThereIsNoOptionVisibleTextToSelect() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - NoSuchElementException::class, - 'Cannot locate option with text: second' - ); + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate option with text: second'); $select->selectByVisibleText('second'); // the option is "This is second option" } @@ -296,10 +286,8 @@ public function testShouldThrowExceptionIfThereIsNoOptionVisiblePartialTextToSel { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - NoSuchElementException::class, - 'Cannot locate option with text: Not existing option' - ); + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate option with text: Not existing option'); $select->selectByVisiblePartialText('Not existing option'); } @@ -307,10 +295,8 @@ public function testShouldThrowExceptionWhenDeselectingOnSimpleSelect() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - UnsupportedOperationException::class, - 'You may only deselect all options of a multi-select' - ); + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You may only deselect all options of a multi-select'); $select->deselectAll(); } @@ -353,10 +339,8 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByIndex() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - UnsupportedOperationException::class, - 'You may only deselect options of a multi-select' - ); + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You may only deselect options of a multi-select'); $select->deselectByIndex(0); } @@ -381,10 +365,8 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByValue() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - UnsupportedOperationException::class, - 'You may only deselect options of a multi-select' - ); + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You may only deselect options of a multi-select'); $select->deselectByValue('first'); } @@ -411,10 +393,8 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByVisibleText() { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - UnsupportedOperationException::class, - 'You may only deselect options of a multi-select' - ); + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You may only deselect options of a multi-select'); $select->deselectByVisibleText('First'); } @@ -447,10 +427,8 @@ public function testShouldThrowExceptionIfDeselectingSimpleSelectByVisiblePartia { $select = $this->getWebDriverSelectForSimpleSelect(); - $this->setExpectedException( - UnsupportedOperationException::class, - 'You may only deselect options of a multi-select' - ); + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You may only deselect options of a multi-select'); $select->deselectByVisiblePartialText('First'); } diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 51be6e13a..47edc886d 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -30,7 +30,7 @@ public function testShouldFailGettingDelayedElementWithoutWait() { $this->driver->get($this->getTestPageUrl('delayed_element.html')); - $this->setExpectedException(NoSuchElementException::class); + $this->expectException(NoSuchElementException::class); $this->driver->findElement(WebDriverBy::id('delayed')); } diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 0742a0789..ccf2a1e6f 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -146,7 +146,8 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() public function testShouldValidateCookie($name, $value, $domain, $expectedMessage) { if ($expectedMessage) { - $this->setExpectedException(\InvalidArgumentException::class, $expectedMessage); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage($expectedMessage); } $cookie = new Cookie($name, $value); diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 5e335854f..8bc47b96f 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -88,10 +88,8 @@ public function testShouldNotAllowToCreateCookieFromDifferentObjectThanCookie() $options = new WebDriverOptions($this->executor); - $this->setExpectedException( - \InvalidArgumentException::class, - 'Cookie must be set from instance of Cookie class or from array.' - ); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Cookie must be set from instance of Cookie class or from array.'); $options->addCookie($notCookie); } From 11b1d4dcf40a0b382acb9322358cea2d97679d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 31 Aug 2017 16:47:09 +0200 Subject: [PATCH 061/487] Add functional tests for findElement for child elements --- .../RemoteWebDriverFindElementTest.php | 4 +- tests/functional/RemoteWebElementTest.php | 52 +++++++++++++++++++ tests/functional/web/index.html | 5 ++ tests/unit/Remote/RemoteWebElementTest.php | 36 +++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Remote/RemoteWebElementTest.php diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 106d5d90f..46affc025 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -24,7 +24,7 @@ */ class RemoteWebDriverFindElementTest extends WebDriverTestCase { - public function testShouldThrowExceptionOfElementCannotBeFound() + public function testShouldThrowExceptionIfElementCannotBeFound() { $this->driver->get($this->getTestPageUrl('index.html')); @@ -58,7 +58,7 @@ public function testShouldFindMultipleElements() $elements = $this->driver->findElements(WebDriverBy::cssSelector('ul > li')); $this->assertInternalType('array', $elements); - $this->assertCount(3, $elements); + $this->assertCount(5, $elements); $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } } diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 643e2a336..51f414a93 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -15,6 +15,9 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Exception\NoSuchElementException; +use Facebook\WebDriver\Remote\RemoteWebElement; + /** * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebElement */ @@ -282,4 +285,53 @@ public function testShouldCompareEqualsElement() $this->assertFalse($firstElement->equals($differentElement)); $this->assertFalse($differentElement->equals($againTheFirstElement)); } + + /** + * @covers ::findElement + */ + public function testShouldThrowExceptionIfChildElementCannotBeFound() + { + $this->driver->get($this->getTestPageUrl('index.html')); + $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + + $this->expectException(NoSuchElementException::class); + $element->findElement(WebDriverBy::id('not_existing')); + } + + public function testShouldFindChildElementIfExistsOnAPage() + { + $this->driver->get($this->getTestPageUrl('index.html')); + $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + + $childElement = $element->findElement(WebDriverBy::cssSelector('li')); + + $this->assertInstanceOf(RemoteWebElement::class, $childElement); + $this->assertSame('li', $childElement->getTagName()); + $this->assertSame('First', $childElement->getText()); + } + + public function testShouldReturnEmptyArrayIfChildElementsCannotBeFound() + { + $this->driver->get($this->getTestPageUrl('index.html')); + $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + + $childElements = $element->findElements(WebDriverBy::cssSelector('not_existing')); + + $this->assertInternalType('array', $childElements); + $this->assertCount(0, $childElements); + } + + public function testShouldFindMultipleChildElements() + { + $this->driver->get($this->getTestPageUrl('index.html')); + $element = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); + + $allElements = $this->driver->findElements(WebDriverBy::cssSelector('li')); + $childElements = $element->findElements(WebDriverBy::cssSelector('li')); + + $this->assertInternalType('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
      + $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $childElements); + } } diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index 8e314f798..1a92f255e 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -30,6 +30,11 @@

      Welcome to the facebook/php-webdriver testing page.

    • Third
    +
      +
    • Foo
    • +
    • Bar
    • +
    +

    Foo bar text

    Multiple spaces are diff --git a/tests/unit/Remote/RemoteWebElementTest.php b/tests/unit/Remote/RemoteWebElementTest.php new file mode 100644 index 000000000..45c057751 --- /dev/null +++ b/tests/unit/Remote/RemoteWebElementTest.php @@ -0,0 +1,36 @@ +createMock(RemoteExecuteMethod::class); + $element = new RemoteWebElement($executeMethod, 333); + + $this->assertSame(333, $element->getID()); + } +} From e8ebc12da32a019612e8874c7e81a884535d1894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 5 Sep 2017 23:37:38 +0200 Subject: [PATCH 062/487] Improve code-coverage settings --- lib/Firefox/FirefoxDriver.php | 3 +++ lib/Firefox/FirefoxPreferences.php | 2 ++ lib/Remote/DriverCommand.php | 2 ++ lib/Remote/WebDriverBrowserType.php | 4 +++- lib/Remote/WebDriverCapabilityType.php | 2 ++ lib/WebDriverPlatform.php | 2 ++ tests/functional/RemoteWebDriverTest.php | 6 ++---- tests/functional/WebDriverTimeoutsTest.php | 2 ++ 8 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/Firefox/FirefoxDriver.php b/lib/Firefox/FirefoxDriver.php index 9e0751238..f2883e642 100644 --- a/lib/Firefox/FirefoxDriver.php +++ b/lib/Firefox/FirefoxDriver.php @@ -15,6 +15,9 @@ namespace Facebook\WebDriver\Firefox; +/** + * @codeCoverageIgnore + */ class FirefoxDriver { const PROFILE = 'firefox_profile'; diff --git a/lib/Firefox/FirefoxPreferences.php b/lib/Firefox/FirefoxPreferences.php index b869a2667..be1199c24 100644 --- a/lib/Firefox/FirefoxPreferences.php +++ b/lib/Firefox/FirefoxPreferences.php @@ -18,6 +18,8 @@ /** * Constants of common Firefox profile preferences (about:config values). * @see http://kb.mozillazine.org/Firefox_:_FAQs_:_About:config_Entries + * + * @codeCoverageIgnore */ class FirefoxPreferences { diff --git a/lib/Remote/DriverCommand.php b/lib/Remote/DriverCommand.php index 1b86c71c6..c69052030 100644 --- a/lib/Remote/DriverCommand.php +++ b/lib/Remote/DriverCommand.php @@ -17,6 +17,8 @@ /** * This list of command defined in the WebDriver json wire protocol. + * + * @codeCoverageIgnore */ class DriverCommand { diff --git a/lib/Remote/WebDriverBrowserType.php b/lib/Remote/WebDriverBrowserType.php index 5dd61505e..53b6ab977 100644 --- a/lib/Remote/WebDriverBrowserType.php +++ b/lib/Remote/WebDriverBrowserType.php @@ -16,7 +16,9 @@ namespace Facebook\WebDriver\Remote; /** - * All the browsers supported by selenium + * All the browsers supported by selenium. + * + * @codeCoverageIgnore */ class WebDriverBrowserType { diff --git a/lib/Remote/WebDriverCapabilityType.php b/lib/Remote/WebDriverCapabilityType.php index 131b16561..c452e771c 100644 --- a/lib/Remote/WebDriverCapabilityType.php +++ b/lib/Remote/WebDriverCapabilityType.php @@ -17,6 +17,8 @@ /** * WebDriverCapabilityType contains all constants defined in the WebDriver Wire Protocol. + * + * @codeCoverageIgnore */ class WebDriverCapabilityType { diff --git a/lib/WebDriverPlatform.php b/lib/WebDriverPlatform.php index bb1923556..5609eb0b8 100644 --- a/lib/WebDriverPlatform.php +++ b/lib/WebDriverPlatform.php @@ -17,6 +17,8 @@ /** * The platforms supported by WebDriver. + * + * @codeCoverageIgnore */ class WebDriverPlatform { diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 7b60b69d7..006b17c7b 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -45,10 +45,7 @@ public function testShouldGetCurrentUrl() { $this->driver->get($this->getTestPageUrl('index.html')); - $this->assertContains( - '/index.html', - $this->driver->getCurrentURL() - ); + $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); } /** @@ -173,6 +170,7 @@ function(){document.getElementById("id_test").innerHTML = "Text changed by scrip /** * @covers ::executeAsyncScript + * @covers Facebook\WebDriver\WebDriverTimeouts::setScriptTimeout */ public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() { diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 47edc886d..808a494ab 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -36,6 +36,7 @@ public function testShouldFailGettingDelayedElementWithoutWait() /** * @covers ::implicitlyWait + * @covers ::__construct */ public function testShouldGetDelayedElementWithImplicitWait() { @@ -49,6 +50,7 @@ public function testShouldGetDelayedElementWithImplicitWait() /** * @covers ::pageLoadTimeout + * @covers ::__construct */ public function testShouldFailIfPageIsLoadingLongerThanPageLoadTimeout() { From 7672fe48b2c11784e71156108ae5b3568d31278c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 6 Sep 2017 01:18:38 +0200 Subject: [PATCH 063/487] Add functional tests for WebDriverNavigation --- lib/WebDriverNavigation.php | 1 + tests/functional/WebDriverNavigationTest.php | 85 ++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tests/functional/WebDriverNavigationTest.php diff --git a/lib/WebDriverNavigation.php b/lib/WebDriverNavigation.php index ccc8cee30..6de59b035 100644 --- a/lib/WebDriverNavigation.php +++ b/lib/WebDriverNavigation.php @@ -75,6 +75,7 @@ public function refresh() /** * Navigate to the given URL. * + * @see WebDriver::get() * @param string $url * @return WebDriverNavigation The instance. */ diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php new file mode 100644 index 000000000..4f7dba252 --- /dev/null +++ b/tests/functional/WebDriverNavigationTest.php @@ -0,0 +1,85 @@ +driver->navigate()->to($this->getTestPageUrl('index.html')); + + $this->assertStringEndsWith('/index.html', $this->driver->getCurrentURL()); + } + + /** + * @covers ::back + * @covers ::forward + */ + public function testShouldNavigateBackAndForward() + { + $this->driver->get($this->getTestPageUrl('index.html')); + $linkElement = $this->driver->findElement(WebDriverBy::id('a-form')); + + $linkElement->click(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::urlContains('form.html') + ); + + $this->driver->navigate()->back(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::urlContains('index.html') + ); + + $this->driver->navigate()->forward(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::urlContains('form.html') + ); + } + + /** + * @covers ::refresh + */ + public function testShouldRefreshPage() + { + $this->driver->get($this->getTestPageUrl('index.html')); + + // Change input element content, to make sure it was refreshed (=> cleared to original value) + $inputElement = $this->driver->findElement(WebDriverBy::name('test_name')); + $inputElementOriginalValue = $inputElement->getAttribute('value'); + $inputElement->clear()->sendKeys('New value'); + $this->assertSame('New value', $inputElement->getAttribute('value')); + + $this->driver->navigate()->refresh(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::stalenessOf($inputElement) + ); + + $inputElementAfterRefresh = $this->driver->findElement(WebDriverBy::name('test_name')); + + $this->assertSame($inputElementOriginalValue, $inputElementAfterRefresh->getAttribute('value')); + } +} From 61f547414294b8188e149594486d1a91d0f7c8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 17 Sep 2017 09:54:20 +0200 Subject: [PATCH 064/487] Add symfony/var-dumper dev dependency for easier development --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cbf3155a9..8eff450f7 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", - "satooshi/php-coveralls": "^1.0" + "satooshi/php-coveralls": "^1.0", + "symfony/var-dumper": "^3.3" }, "autoload": { "psr-4": { From 944c76eb61403ee9479a58285c4cd2abae00526e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 13 Oct 2017 23:59:03 +0200 Subject: [PATCH 065/487] Update readme - passThrough mode, mention Java 8 is required for Selenium server --- README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cfdf0bc3b..5b331d349 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,14 @@ Then install the library: All you need as the server for this client is the `selenium-server-standalone-#.jar` file provided here: http://selenium-release.storage.googleapis.com/index.html -Download and run that file, replacing # with the current server version. +Download and run that file, replacing # with the current server version. Keep in mind you must have Java 8+ installed to start this command. java -jar selenium-server-standalone-#.jar +When using Selenium server 3.5 and newer with some remote end clients (eg. Firefox with Geckodriver), you MUST disable so called "pass-through" mode, so that remote browser's protocol is translated to the protocol supported by php-webdriver (see [issue #469](https://github.com/facebook/php-webdriver/issues/469)): + + java -jar selenium-server-standalone-#.jar -enablePassThrough false + Then when you create a session, be sure to pass the url to where your server is running. ```php @@ -50,19 +54,23 @@ Then when you create a session, be sure to pass the url to where your server is $host = 'http://localhost:4444/wd/hub'; // this is the default ``` -* Launch Firefox: +##### Launch Firefox + +Make sure to have latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases) installed. + +```php +$driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); +``` - ```php - $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); - ``` +##### Launch Chrome -* Launch Chrome: +Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) installed. - ```php - $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); - ``` +```php +$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); +``` -You can also customize the desired capabilities: +##### You can also customize the desired capabilities ```php $desired_capabilities = DesiredCapabilities::firefox(); From 4c2710810148b8c266ced5aad55ac4ad70a36aa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 14 Oct 2017 03:50:51 +0200 Subject: [PATCH 066/487] Add functional tests for basic actions (click, double click etc.) --- lib/Interactions/WebDriverCompositeAction.php | 2 +- tests/functional/WebDriverActionsTest.php | 140 ++++++++++++++++++ tests/functional/web/events.html | 86 +++++++++++ tests/functional/web/index.html | 2 + 4 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 tests/functional/WebDriverActionsTest.php create mode 100644 tests/functional/web/events.html diff --git a/lib/Interactions/WebDriverCompositeAction.php b/lib/Interactions/WebDriverCompositeAction.php index 75a4837e1..d4ca94c5d 100644 --- a/lib/Interactions/WebDriverCompositeAction.php +++ b/lib/Interactions/WebDriverCompositeAction.php @@ -51,7 +51,7 @@ public function getNumberOfActions() } /** - * Perform the seqeunce of actions. + * Perform the sequence of actions. */ public function perform() { diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php new file mode 100644 index 000000000..173f7794c --- /dev/null +++ b/tests/functional/WebDriverActionsTest.php @@ -0,0 +1,140 @@ +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'); + } + + $element = $this->driver->findElement(WebDriverBy::id('item-1')); + + $this->driver->action() + ->click($element) + ->perform(); + + $this->assertSame( + ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], + $this->retrieveLoggedEvents() + ); + } + + /** + * @covers ::__construct + * @covers ::clickAndHold + * @covers ::release + * @covers ::perform + */ + public function testShouldClickAndHoldOnElementAndRelease() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $element = $this->driver->findElement(WebDriverBy::id('item-1')); + + $this->driver->action() + ->clickAndHold($element) + ->release() + ->perform(); + + $this->assertSame( + ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], + $this->retrieveLoggedEvents() + ); + } + + /** + * @covers ::__construct + * @covers ::contextClick + * @covers ::perform + */ + public function testShouldContextClickOnElement() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::MICROSOFT_EDGE) { + $this->markTestSkipped('Getting stuck in EdgeDriver'); + } + + $element = $this->driver->findElement(WebDriverBy::id('item-2')); + + $this->driver->action() + ->contextClick($element) + ->perform(); + + $loggedEvents = $this->retrieveLoggedEvents(); + + $this->assertContains('mousedown item-2', $loggedEvents); + $this->assertContains('mouseup item-2', $loggedEvents); + $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'); + } + + $element = $this->driver->findElement(WebDriverBy::id('item-3')); + + $this->driver->action() + ->doubleClick($element) + ->perform(); + + $this->assertSame( + ['mouseover item-3', 'mousedown item-3', 'mouseup item-3', 'click item-3', 'dblclick item-3'], + $this->retrieveLoggedEvents() + ); + } + + /** + * @return array + */ + private function retrieveLoggedEvents() + { + $logElement = $this->driver->findElement(WebDriverBy::id('log')); + + return explode("\n", $logElement->getText()); + } +} diff --git a/tests/functional/web/events.html b/tests/functional/web/events.html new file mode 100644 index 000000000..e915a89a0 --- /dev/null +++ b/tests/functional/web/events.html @@ -0,0 +1,86 @@ + + + + + Events + + + + +

      + +
    + +
      +
    • First item
    • +
    • Second item
    • +
    • Third item
    • +
    + +
    
    +
    +
    +
    +
    +
    diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html
    index 1a92f255e..cf5b9c6c1 100644
    --- a/tests/functional/web/index.html
    +++ b/tests/functional/web/index.html
    @@ -18,6 +18,8 @@ 

    Welcome to the facebook/php-webdriver testing page.

    Slow loading page | Javascript alerts + | + Events

    Test by ID

    Test by Class

    From cae27962e3b1e4fae70c951769c00204bd927eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 25 Oct 2017 23:05:52 +0200 Subject: [PATCH 067/487] Add tests for ChromeDriverService and ChromeDriver --- .travis.yml | 12 ++- .../Chrome/ChromeDriverServiceTest.php | 82 +++++++++++++++++++ tests/functional/Chrome/ChromeDriverTest.php | 60 ++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 tests/functional/Chrome/ChromeDriverServiceTest.php create mode 100644 tests/functional/Chrome/ChromeDriverTest.php diff --git a/.travis.yml b/.travis.yml index 9b2b5894e..7484f1cfc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,13 @@ matrix: # Build with lowest possible dependencies - php: 7.1 - env: dependencies="--prefer-lowest" + env: DEPENDENCIES="--prefer-lowest" + + # Chrome on Travis build with lowest possible dependencies + - php: 7.1 + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" DEPENDENCIES="--prefer-lowest" + addons: + chrome: beta # Saucelabs builds - php: 7.1 @@ -77,11 +83,11 @@ before_install: - travis_retry composer self-update install: - - travis_retry composer update --no-interaction $dependencies + - travis_retry composer update --no-interaction $DEPENDENCIES before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=./chromedriver/chromedriver; fi + - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.4.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar; fi - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.4.0.jar -log ./logs/selenium.log & diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php new file mode 100644 index 000000000..6f6073fda --- /dev/null +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -0,0 +1,82 @@ +markTestSkipped('ChromeDriverServiceTest is run only when running against local chrome'); + } + } + + public function testShouldStartAndStopServiceCreatedUsingShortcutConstructor() + { + // The createDefaultService() method expect path to the executable to be present in the environment variable + putenv(ChromeDriverService::CHROME_DRIVER_EXE_PROPERTY . '=' . getenv('CHROMEDRIVER_PATH')); + + $driverService = ChromeDriverService::createDefaultService(); + + $this->assertSame('http://localhost:9515/', $driverService->getURL()); + + $this->assertInstanceOf(ChromeDriverService::class, $driverService->start()); + $this->assertTrue($driverService->isRunning()); + + $this->assertInstanceOf(ChromeDriverService::class, $driverService->start()); + + $this->assertInstanceOf(ChromeDriverService::class, $driverService->stop()); + $this->assertFalse($driverService->isRunning()); + + $this->assertInstanceOf(ChromeDriverService::class, $driverService->stop()); + } + + public function testShouldStartAndStopServiceCreatedUsingDefaultConstructor() + { + $driverService = new ChromeDriverService(getenv('CHROMEDRIVER_PATH'), 9515, ['--port=9515']); + + $this->assertSame('http://localhost:9515/', $driverService->getURL()); + + $driverService->start(); + $this->assertTrue($driverService->isRunning()); + + $driverService->stop(); + $this->assertFalse($driverService->isRunning()); + } + + public function testShouldThrowExceptionIfExecutableCannotBeFound() + { + putenv(ChromeDriverService::CHROME_DRIVER_EXE_PROPERTY . '=/not/existing'); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('\'/not/existing\' is not a file.'); + ChromeDriverService::createDefaultService(); + } + + public function testShouldThrowExceptionIfExecutableIsNotExecutable() + { + putenv(ChromeDriverService::CHROME_DRIVER_EXE_PROPERTY . '=' . __FILE__); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('is not executable'); + ChromeDriverService::createDefaultService(); + } +} diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php new file mode 100644 index 000000000..f3567d9d7 --- /dev/null +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -0,0 +1,60 @@ +markTestSkipped('ChromeDriverServiceTest is run only when running against local chrome'); + } + } + + protected function tearDown() + { + if ($this->driver instanceof RemoteWebDriver && $this->driver->getCommandExecutor() !== null) { + $this->driver->quit(); + } + } + + public function testShouldStartChromeDriver() + { + // The createDefaultService() method expect path to the executable to be present in the environment variable + putenv(ChromeDriverService::CHROME_DRIVER_EXE_PROPERTY . '=' . getenv('CHROMEDRIVER_PATH')); + + $this->driver = ChromeDriver::start(); + + $this->assertInstanceOf(ChromeDriver::class, $this->driver); + $this->assertInstanceOf(DriverCommandExecutor::class, $this->driver->getCommandExecutor()); + + $this->driver->get('http://localhost:8000/'); + + $this->assertSame('http://localhost:8000/', $this->driver->getCurrentURL()); + + $this->driver->quit(); + } +} From 2b1d15359f88ea246d4a18f5dda4cbdeec48eb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 26 Oct 2017 10:14:17 +0200 Subject: [PATCH 068/487] Bump sebastian/environment version to fix code-coverage in lowest dependencies build See https://github.com/sebastianbergmann/phpunit/issues/1976 --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8eff450f7..6203b6238 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,11 @@ }, "require-dev": { "phpunit/phpunit": "^5.4", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", - "satooshi/php-coveralls": "^1.0", + "php-coveralls/php-coveralls": "^1.0.2", "symfony/var-dumper": "^3.3" }, "autoload": { From c85b872ba733b303ca363ecf27f87ae45a543b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 26 Oct 2017 15:17:50 +0200 Subject: [PATCH 069/487] Bump guzzle version to fix coveralls in lowest dependencies build --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 6203b6238..6b7685be9 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", "php-coveralls/php-coveralls": "^1.0.2", + "guzzle/guzzle": "^3.4.1", "symfony/var-dumper": "^3.3" }, "autoload": { From f6e0ad46640248112b636593d59db8032fa13c73 Mon Sep 17 00:00:00 2001 From: VolCh Date: Thu, 2 Nov 2017 19:07:27 +0200 Subject: [PATCH 070/487] Fix code style --- lib/WebDriverSelectInterface.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/WebDriverSelectInterface.php b/lib/WebDriverSelectInterface.php index bd4db90e4..030a783e9 100644 --- a/lib/WebDriverSelectInterface.php +++ b/lib/WebDriverSelectInterface.php @@ -1,4 +1,5 @@ Date: Fri, 3 Nov 2017 10:41:52 +0200 Subject: [PATCH 071/487] Fix code style --- lib/WebDriverDimension.php | 2 +- lib/WebDriverExpectedCondition.php | 4 ++-- lib/WebDriverPoint.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/WebDriverDimension.php b/lib/WebDriverDimension.php index 111a77821..308c1990f 100644 --- a/lib/WebDriverDimension.php +++ b/lib/WebDriverDimension.php @@ -65,7 +65,7 @@ public function getWidth() * @param WebDriverDimension $dimension The dimension to be compared with. * @return bool Whether the height and the width are the same as the instance. */ - public function equals(WebDriverDimension $dimension) + public function equals(self $dimension) { return $this->height === $dimension->getHeight() && $this->width === $dimension->getWidth(); } diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 347a5d28a..bce6bfee4 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -453,7 +453,7 @@ function () use ($element) { * @return WebDriverExpectedCondition Condition returns the return value of the getApply() of the given * condition. */ - public static function refreshed(WebDriverExpectedCondition $condition) + public static function refreshed(self $condition) { return new static( function (WebDriver $driver) use ($condition) { @@ -558,7 +558,7 @@ function (WebDriver $driver) use ($expectedNumberOfWindows) { * @param WebDriverExpectedCondition $condition The condition to be negated. * @return mixed The negation of the result of the given condition. */ - public static function not(WebDriverExpectedCondition $condition) + public static function not(self $condition) { return new static( function (WebDriver $driver) use ($condition) { diff --git a/lib/WebDriverPoint.php b/lib/WebDriverPoint.php index e12977b22..4e2dbd211 100644 --- a/lib/WebDriverPoint.php +++ b/lib/WebDriverPoint.php @@ -85,7 +85,7 @@ public function moveBy($x_offset, $y_offset) * @param WebDriverPoint $point The point to be compared with. * @return bool Whether the x and y coordinates are the same as the instance. */ - public function equals(WebDriverPoint $point) + public function equals(self $point) { return $this->x === $point->getX() && $this->y === $point->getY(); From 4eee996a85b6fee638f5f95990c95f8832a18755 Mon Sep 17 00:00:00 2001 From: Quentin Headen Date: Tue, 7 Nov 2017 04:37:32 +0000 Subject: [PATCH 072/487] Add WebDriverExpectedCondition::visibilityOfAnyElementsLocated --- CHANGELOG.md | 3 ++ lib/WebDriverExpectedCondition.php | 29 ++++++++++++++++++ tests/unit/WebDriverExpectedConditionTest.php | 30 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 182aeda87..c8b503b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6. +### Added +- Added a visibilityOfAnyElementsLocated method to WebDriverExpectedCondition. + ## 1.4.1 - 2017-04-28 ### Fixed - Do not throw notice `Constant CURLOPT_CONNECTTIMEOUT_MS already defined`. diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index bce6bfee4..433ee1a3a 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -193,6 +193,35 @@ function (WebDriver $driver) use ($by) { ); } + /** + * An expectation for checking than at least one element in an array of elements is present on the + * DOM of a page and visible. + * Visibility means that the element is not only displayed but also has a height and width that is greater than 0. + * + * @param WebDriverBy $by The located used to find the element. + * @return WebDriverExpectedCondition Condition returns the elements that are located and visible. + */ + public static function visibilityOfAnyElementLocated(WebDriverBy $by) + { + return new static( + function (WebDriver $driver) use ($by) { + $elements = $driver->findElements($by); + $visibleElements = []; + + foreach ($elements as $element) { + try { + if ($element->isDisplayed()) { + $visibleElements[] = $element; + } + } catch (StateElementReferenceException $e) { + } + } + + return count($visibleElements) > 0 ? $visibleElements : null; + } + ); + } + /** * An expectation for checking that an element, known to be present on the DOM of a page, is visible. * Visibility means that the element is not only displayed but also has a height and width that is greater than 0. diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index e786167a8..603829c02 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -185,6 +185,36 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() $this->assertSame($element, $this->wait->until($condition)); } + public function testShouldDetectVisibilityOfAnyElementLocated() + { + $elementList = [ + $this->createRemoteWebElementMock(), + $this->createRemoteWebElementMock(), + $this->createRemoteWebElementMock(), + ]; + + $elementList[0]->expects($this->once()) + ->method('isDisplayed') + ->willReturn(false); + + $elementList[1]->expects($this->once()) + ->method('isDisplayed') + ->willReturn(true); + + $elementList[2]->expects($this->once()) + ->method('isDisplayed') + ->willReturn(true); + + $this->driverMock->expects($this->once()) + ->method('findElements') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willReturn($elementList); + + $condition = WebDriverExpectedCondition::visibilityOfAnyElementLocated(WebDriverBy::cssSelector('.foo')); + + $this->assertSame([$elementList[1], $elementList[2]], $this->wait->until($condition)); + } + public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchElementException() { $element = $this->createRemoteWebElementMock(); From 017b8d7daee5c070235b8c74d335180f2bd15aa2 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Sat, 4 Nov 2017 17:27:25 -0200 Subject: [PATCH 073/487] Use namespaces for PHPUnit forward compatiblitiy --- composer.json | 2 +- tests/functional/Chrome/ChromeDriverServiceTest.php | 4 +++- tests/functional/Chrome/ChromeDriverTest.php | 3 ++- tests/functional/ReportSauceLabsStatusListener.php | 3 ++- tests/functional/WebDriverTestCase.php | 3 ++- tests/unit/CookieTest.php | 4 +++- tests/unit/Exception/WebDriverExceptionTest.php | 4 +++- .../Internal/WebDriverButtonReleaseActionTest.php | 3 ++- tests/unit/Interactions/Internal/WebDriverClickActionTest.php | 3 ++- .../Interactions/Internal/WebDriverClickAndHoldActionTest.php | 3 ++- .../Interactions/Internal/WebDriverContextClickActionTest.php | 3 ++- tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php | 4 +++- .../Interactions/Internal/WebDriverDoubleClickActionTest.php | 3 ++- .../unit/Interactions/Internal/WebDriverKeyDownActionTest.php | 3 ++- tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php | 3 ++- .../Interactions/Internal/WebDriverMouseMoveActionTest.php | 3 ++- .../Internal/WebDriverMouseToOffsetActionTest.php | 3 ++- .../Interactions/Internal/WebDriverSendKeysActionTest.php | 3 ++- tests/unit/Remote/DesiredCapabilitiesTest.php | 3 ++- tests/unit/Remote/HttpCommandExecutorTest.php | 3 ++- tests/unit/Remote/RemoteWebDriverTest.php | 3 ++- tests/unit/Remote/RemoteWebElementTest.php | 4 +++- tests/unit/Remote/WebDriverCommandTest.php | 4 +++- tests/unit/Support/XPathEscaperTest.php | 4 +++- tests/unit/WebDriverExpectedConditionTest.php | 3 ++- tests/unit/WebDriverKeysTest.php | 4 +++- tests/unit/WebDriverOptionsTest.php | 3 ++- 27 files changed, 61 insertions(+), 27 deletions(-) diff --git a/composer.json b/composer.json index 6b7685be9..94b7f8a36 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ "ext-zip": "*" }, "require-dev": { - "phpunit/phpunit": "^5.4", + "phpunit/phpunit": "^5.7", "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^2.6", diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php index 6f6073fda..bc1a1b92f 100644 --- a/tests/functional/Chrome/ChromeDriverServiceTest.php +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -15,12 +15,14 @@ namespace Facebook\WebDriver\Chrome; +use PHPUnit\Framework\TestCase; + /** * @group exclude-saucelabs * @covers Facebook\WebDriver\Chrome\ChromeDriverService * @covers Facebook\WebDriver\Remote\Service\DriverService */ -class ChromeDriverServiceTest extends \PHPUnit_Framework_TestCase +class ChromeDriverServiceTest extends TestCase { protected function setUp() { diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index f3567d9d7..476033839 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -17,12 +17,13 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; +use PHPUnit\Framework\TestCase; /** * @group exclude-saucelabs * @covers Facebook\WebDriver\Chrome\ChromeDriver */ -class ChromeDriverTest extends \PHPUnit_Framework_TestCase +class ChromeDriverTest extends TestCase { /** @var ChromeDriver */ protected $driver; diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php index b88b1cdf7..f2a3c9f7d 100644 --- a/tests/functional/ReportSauceLabsStatusListener.php +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -16,8 +16,9 @@ namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\RemoteWebDriver; +use PHPUnit\Framework\BaseTestListener; -class ReportSauceLabsStatusListener extends \PHPUnit_Framework_BaseTestListener +class ReportSauceLabsStatusListener extends BaseTestListener { public function endTest(\PHPUnit_Framework_Test $test, $time) { diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 77a98f884..4abaf7c03 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -20,11 +20,12 @@ use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\WebDriverBrowserType; +use PHPUnit\Framework\TestCase; /** * The base class for test cases. */ -class WebDriverTestCase extends \PHPUnit_Framework_TestCase +class WebDriverTestCase extends TestCase { /** @var RemoteWebDriver $driver */ public $driver; diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index ccf2a1e6f..5fc62fb6c 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -15,10 +15,12 @@ namespace Facebook\WebDriver; +use PHPUnit\Framework\TestCase; + /** * @covers Facebook\WebDriver\Cookie */ -class CookieTest extends \PHPUnit_Framework_TestCase +class CookieTest extends TestCase { public function testShouldSetAllProperties() { diff --git a/tests/unit/Exception/WebDriverExceptionTest.php b/tests/unit/Exception/WebDriverExceptionTest.php index 1dfd94fac..f9aadd207 100644 --- a/tests/unit/Exception/WebDriverExceptionTest.php +++ b/tests/unit/Exception/WebDriverExceptionTest.php @@ -15,7 +15,9 @@ namespace Facebook\WebDriver\Exception; -class WebDriverExceptionTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class WebDriverExceptionTest extends TestCase { public function testShouldStoreResultsOnInstantiation() { diff --git a/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php b/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php index f2bb1b647..db8574a0b 100644 --- a/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverButtonReleaseActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverButtonReleaseActionTest extends \PHPUnit_Framework_TestCase +class WebDriverButtonReleaseActionTest extends TestCase { /** @var WebDriverButtonReleaseAction */ private $webDriverButtonReleaseAction; diff --git a/tests/unit/Interactions/Internal/WebDriverClickActionTest.php b/tests/unit/Interactions/Internal/WebDriverClickActionTest.php index fdb868141..8041d7ae7 100644 --- a/tests/unit/Interactions/Internal/WebDriverClickActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverClickActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverClickActionTest extends \PHPUnit_Framework_TestCase +class WebDriverClickActionTest extends TestCase { /** @var WebDriverClickAction */ private $webDriverClickAction; diff --git a/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php b/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php index eb3f4bc6f..ebe933f8c 100644 --- a/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverClickAndHoldActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverClickAndHoldActionTest extends \PHPUnit_Framework_TestCase +class WebDriverClickAndHoldActionTest extends TestCase { /** @var WebDriverClickAndHoldAction */ private $webDriverClickAndHoldAction; diff --git a/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php b/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php index e9162f239..67ca63e4c 100644 --- a/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverContextClickActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverContextClickActionTest extends \PHPUnit_Framework_TestCase +class WebDriverContextClickActionTest extends TestCase { /** @var WebDriverContextClickAction */ private $webDriverContextClickAction; diff --git a/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php b/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php index d782edf8c..209daa63f 100644 --- a/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php +++ b/tests/unit/Interactions/Internal/WebDriverCoordinatesTest.php @@ -15,7 +15,9 @@ namespace Facebook\WebDriver\Interactions\Internal; -class WebDriverCoordinatesTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class WebDriverCoordinatesTest extends TestCase { public function testConstruct() { diff --git a/tests/unit/Interactions/Internal/WebDriverDoubleClickActionTest.php b/tests/unit/Interactions/Internal/WebDriverDoubleClickActionTest.php index acd74550f..86710d330 100644 --- a/tests/unit/Interactions/Internal/WebDriverDoubleClickActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverDoubleClickActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverDoubleClickActionTest extends \PHPUnit_Framework_TestCase +class WebDriverDoubleClickActionTest extends TestCase { /** @var WebDriverDoubleClickAction */ private $webDriverDoubleClickAction; diff --git a/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php b/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php index 267b58b3e..a03791fd9 100644 --- a/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverKeyDownActionTest.php @@ -18,8 +18,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverKeyDownActionTest extends \PHPUnit_Framework_TestCase +class WebDriverKeyDownActionTest extends TestCase { /** @var WebDriverKeyDownAction */ private $webDriverKeyDownAction; diff --git a/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php b/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php index 970a35b0f..7ebc17ac0 100644 --- a/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverKeyUpActionTest.php @@ -18,8 +18,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverKeyUpActionTest extends \PHPUnit_Framework_TestCase +class WebDriverKeyUpActionTest extends TestCase { /** @var WebDriverKeyUpAction */ private $webDriverKeyUpAction; diff --git a/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php b/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php index 565530ad8..584231f9d 100644 --- a/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverMouseMoveActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverMouseMoveActionTest extends \PHPUnit_Framework_TestCase +class WebDriverMouseMoveActionTest extends TestCase { /** @var WebDriverMouseMoveAction */ private $webDriverMouseMoveAction; diff --git a/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php b/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php index d2d5286a1..a055c746d 100644 --- a/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverMouseToOffsetActionTest.php @@ -17,8 +17,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverMouseToOffsetActionTest extends \PHPUnit_Framework_TestCase +class WebDriverMouseToOffsetActionTest extends TestCase { /** * @type WebDriverMoveToOffsetAction diff --git a/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php b/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php index 9b06b3df7..22cb47e9d 100644 --- a/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php +++ b/tests/unit/Interactions/Internal/WebDriverSendKeysActionTest.php @@ -18,8 +18,9 @@ use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; +use PHPUnit\Framework\TestCase; -class WebDriverSendKeysActionTest extends \PHPUnit_Framework_TestCase +class WebDriverSendKeysActionTest extends TestCase { /** @var WebDriverSendKeysAction */ private $webDriverSendKeysAction; diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index dacdcd139..559a21f7f 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -19,8 +19,9 @@ use Facebook\WebDriver\Firefox\FirefoxPreferences; use Facebook\WebDriver\Firefox\FirefoxProfile; use Facebook\WebDriver\WebDriverPlatform; +use PHPUnit\Framework\TestCase; -class DesiredCapabilitiesTest extends \PHPUnit_Framework_TestCase +class DesiredCapabilitiesTest extends TestCase { public function testShouldInstantiateWithCapabilitiesGivenInConstructor() { diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 63d004e6b..a3810e731 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -16,8 +16,9 @@ namespace Facebook\WebDriver\Remote; use phpmock\phpunit\PHPMock; +use PHPUnit\Framework\TestCase; -class HttpCommandExecutorTest extends \PHPUnit_Framework_TestCase +class HttpCommandExecutorTest extends TestCase { use PHPMock; diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index 48b149f84..c8fee7e5d 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -19,13 +19,14 @@ use Facebook\WebDriver\WebDriverNavigation; use Facebook\WebDriver\WebDriverOptions; use Facebook\WebDriver\WebDriverWait; +use PHPUnit\Framework\TestCase; /** * Unit part of RemoteWebDriver tests. Ie. tests for behavior which do not interact with the real remote server. * * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebDriver */ -class RemoteWebDriverTest extends \PHPUnit_Framework_TestCase +class RemoteWebDriverTest extends TestCase { /** @var RemoteWebDriver */ private $driver; diff --git a/tests/unit/Remote/RemoteWebElementTest.php b/tests/unit/Remote/RemoteWebElementTest.php index 45c057751..80bc92931 100644 --- a/tests/unit/Remote/RemoteWebElementTest.php +++ b/tests/unit/Remote/RemoteWebElementTest.php @@ -15,12 +15,14 @@ namespace Facebook\WebDriver\Remote; +use PHPUnit\Framework\TestCase; + /** * Unit part of RemoteWebDriver tests. Ie. tests for behavior which do not interact with the real remote server. * * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebElement */ -class RemoteWebElementTest extends \PHPUnit_Framework_TestCase +class RemoteWebElementTest extends TestCase { /** * @covers ::__construct diff --git a/tests/unit/Remote/WebDriverCommandTest.php b/tests/unit/Remote/WebDriverCommandTest.php index 9993c6ce5..2e1a0ad9f 100644 --- a/tests/unit/Remote/WebDriverCommandTest.php +++ b/tests/unit/Remote/WebDriverCommandTest.php @@ -15,7 +15,9 @@ namespace Facebook\WebDriver\Remote; -class WebDriverCommandTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class WebDriverCommandTest extends TestCase { public function testShouldSetOptionsUsingConstructor() { diff --git a/tests/unit/Support/XPathEscaperTest.php b/tests/unit/Support/XPathEscaperTest.php index 21ac4aea0..96853ed4f 100644 --- a/tests/unit/Support/XPathEscaperTest.php +++ b/tests/unit/Support/XPathEscaperTest.php @@ -15,7 +15,9 @@ namespace Facebook\WebDriver\Support; -class XPathEscaperTest extends \PHPUnit_Framework_TestCase +use PHPUnit\Framework\TestCase; + +class XPathEscaperTest extends TestCase { /** * @dataProvider xpathProvider diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index e786167a8..1860e4585 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -20,11 +20,12 @@ use Facebook\WebDriver\Remote\RemoteExecuteMethod; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\RemoteWebElement; +use PHPUnit\Framework\TestCase; /** * @covers Facebook\WebDriver\WebDriverExpectedCondition */ -class WebDriverExpectedConditionTest extends \PHPUnit_Framework_TestCase +class WebDriverExpectedConditionTest extends TestCase { /** @var RemoteWebDriver|\PHPUnit_Framework_MockObject_MockObject */ private $driverMock; diff --git a/tests/unit/WebDriverKeysTest.php b/tests/unit/WebDriverKeysTest.php index 9a205c7de..a309402b8 100644 --- a/tests/unit/WebDriverKeysTest.php +++ b/tests/unit/WebDriverKeysTest.php @@ -15,10 +15,12 @@ namespace Facebook\WebDriver; +use PHPUnit\Framework\TestCase; + /** * @covers Facebook\WebDriver\WebDriverKeys */ -class WebDriverKeysTest extends \PHPUnit_Framework_TestCase +class WebDriverKeysTest extends TestCase { /** * @dataProvider provideKeys diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 8bc47b96f..07c721c48 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -17,11 +17,12 @@ use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; +use PHPUnit\Framework\TestCase; /** * @covers Facebook\WebDriver\WebDriverOptions */ -class WebDriverOptionsTest extends \PHPUnit_Framework_TestCase +class WebDriverOptionsTest extends TestCase { /** @var ExecuteMethod|\PHPUnit_Framework_MockObject_MockObject */ private $executor; From d3af34e78f8ca4edc7cff3dbcfbe8c0219d43c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 8 Nov 2017 13:45:10 +0100 Subject: [PATCH 074/487] Avoid useless test warning on PHPUnit 6 --- tests/unit/CookieTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 5fc62fb6c..43215acb8 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -145,7 +145,7 @@ public function testShouldBeCreatableFromAnArrayWithAllValues() * @param string $domain * @param string $expectedMessage */ - public function testShouldValidateCookie($name, $value, $domain, $expectedMessage) + public function testShouldValidateCookieOnConstruction($name, $value, $domain, $expectedMessage) { if ($expectedMessage) { $this->expectException(\InvalidArgumentException::class); @@ -156,6 +156,8 @@ public function testShouldValidateCookie($name, $value, $domain, $expectedMessag if ($domain !== null) { $cookie->setDomain($domain); } + + $this->assertInstanceOf(Cookie::class, $cookie); } /** From 1bb077b2fd7273569f63293bf623406d8a6c3cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Re=C3=A9nse?= Date: Tue, 6 Jun 2017 19:19:18 +0200 Subject: [PATCH 075/487] Fix typo in isJavascriptEnabled --- lib/Remote/DesiredCapabilities.php | 2 +- tests/unit/Remote/DesiredCapabilitiesTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 3990343af..940fcf98c 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -145,7 +145,7 @@ public function setJavascriptEnabled($enabled) $browser = $this->getBrowserName(); if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { throw new Exception( - 'isJavascriptEnable() is a htmlunit-only option. ' . + 'isJavascriptEnabled() is a htmlunit-only option. ' . 'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.' ); } diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index dacdcd139..1edea2522 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -63,7 +63,7 @@ public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters() /** * @expectedException \Exception - * @expectedExceptionMessage isJavascriptEnable() is a htmlunit-only option + * @expectedExceptionMessage isJavascriptEnabled() is a htmlunit-only option */ public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser() { From b643bf3a465ca8eec35c781e8ac8c40d003e3151 Mon Sep 17 00:00:00 2001 From: VolCh Date: Sat, 4 Nov 2017 15:52:32 +0200 Subject: [PATCH 076/487] Extract DriverService::createProcess() --- lib/Remote/Service/DriverService.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/Remote/Service/DriverService.php b/lib/Remote/Service/DriverService.php index c15ef94f9..e3fea5823 100644 --- a/lib/Remote/Service/DriverService.php +++ b/lib/Remote/Service/DriverService.php @@ -81,12 +81,7 @@ public function start() return $this; } - $processBuilder = (new ProcessBuilder()) - ->setPrefix($this->executable) - ->setArguments($this->args) - ->addEnvironmentVariables($this->environment); - - $this->process = $processBuilder->getProcess(); + $this->process = $this->createProcess(); $this->process->start(); $checker = new URLChecker(); @@ -144,4 +139,17 @@ protected static function checkExecutable($executable) return $executable; } + + /** + * @return Process + */ + private function createProcess() + { + $processBuilder = (new ProcessBuilder()) + ->setPrefix($this->executable) + ->setArguments($this->args) + ->addEnvironmentVariables($this->environment); + + return $processBuilder->getProcess(); + } } From 3a2ecdf0e4a10b9e4c91e3aece999fbf6ccb1776 Mon Sep 17 00:00:00 2001 From: VolCh Date: Sat, 4 Nov 2017 16:13:40 +0200 Subject: [PATCH 077/487] Enable beta stability for project development only --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 94b7f8a36..8392c5738 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "forum": "https://www.facebook.com/groups/phpwebdriver/", "source": "https://github.com/facebook/php-webdriver" }, + "minimum-stability": "beta", "require": { "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1", From a19477c5a1177ef5da36884b7144232ba5a65d4d Mon Sep 17 00:00:00 2001 From: VolCh Date: Sat, 4 Nov 2017 16:16:11 +0200 Subject: [PATCH 078/487] Allow Symfony 4 and avoid deprecation warning with Symfony 3.4 --- composer.json | 4 ++-- lib/Remote/Service/DriverService.php | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 8392c5738..069cb6df5 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "minimum-stability": "beta", "require": { "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1", + "symfony/process": "^2.8 || ^3.1 || ^4.0", "ext-curl": "*", "ext-zip": "*" }, @@ -25,7 +25,7 @@ "php-mock/php-mock-phpunit": "^1.1", "php-coveralls/php-coveralls": "^1.0.2", "guzzle/guzzle": "^3.4.1", - "symfony/var-dumper": "^3.3" + "symfony/var-dumper": "^3.3 || ^4.0" }, "autoload": { "psr-4": { diff --git a/lib/Remote/Service/DriverService.php b/lib/Remote/Service/DriverService.php index e3fea5823..eabf24e7a 100644 --- a/lib/Remote/Service/DriverService.php +++ b/lib/Remote/Service/DriverService.php @@ -145,11 +145,20 @@ protected static function checkExecutable($executable) */ private function createProcess() { - $processBuilder = (new ProcessBuilder()) - ->setPrefix($this->executable) - ->setArguments($this->args) - ->addEnvironmentVariables($this->environment); + // BC: ProcessBuilder deprecated since Symfony 3.4 and removed in Symfony 4.0. + if (class_exists(ProcessBuilder::class) + && false === mb_strpos('@deprecated', (new \ReflectionClass(ProcessBuilder::class))->getDocComment()) + ) { + $processBuilder = (new ProcessBuilder()) + ->setPrefix($this->executable) + ->setArguments($this->args) + ->addEnvironmentVariables($this->environment); + + return $processBuilder->getProcess(); + } + // Safe to use since Symfony 3.3 + $commandLine = array_merge([$this->executable], $this->args); - return $processBuilder->getProcess(); + return new Process($commandLine, null, $this->environment); } } From 8d08a8d34ab20e3663ac2c1ff88e30edbc25a287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 15 Nov 2017 02:04:18 +0100 Subject: [PATCH 079/487] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b503b1d..0d71ee44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,10 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Changed - Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6. +- Allow installation of Symfony 4 components. ### Added -- Added a visibilityOfAnyElementsLocated method to WebDriverExpectedCondition. +- Add a `visibilityOfAnyElementsLocated()` method to `WebDriverExpectedCondition`. ## 1.4.1 - 2017-04-28 ### Fixed From 86b5ca2f67173c9d34340845dd690149c886a605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 15 Nov 2017 12:08:09 +0100 Subject: [PATCH 080/487] Release version 1.5.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d71ee44d..3f42c31ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.5.0 - 2017-11-15 ### Changed - Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6. - Allow installation of Symfony 4 components. From d9623ed86e1f4b88609ff0dd054b62682c2419e4 Mon Sep 17 00:00:00 2001 From: Flarnie Marchan Date: Sun, 26 Nov 2017 16:25:29 -0800 Subject: [PATCH 081/487] Link to COC from `CONTRIBUTING.md` We are about to add a `CODE_OF_CONDUCT.md` doc and it makes sense to surface it here. --- CONTRIBUTING.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c59bbe84..d7b93736c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,9 @@ 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 send a pull request (see bellow) with your contribution. +## Code of Conduct +The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) + ## Workflow when contributing a patch 1. Fork the project on GitHub From 90c7dcbff5a545d63520c83ab490d6e24ad9ba3a Mon Sep 17 00:00:00 2001 From: Flarnie Marchan Date: Sun, 26 Nov 2017 16:27:33 -0800 Subject: [PATCH 082/487] Add `CODE_OF_CONDUCT.md` In the past Facebook didn't promote including a Code of Conduct when creating new projects, and many projects skipped this important document. Let's fix it. :) **why make this change?:** Facebook Open Source provides a Code of Conduct statement for all projects to follow, to promote a welcoming and safe open source community. Exposing the COC via a separate markdown file is a standard being promoted by Github via the Community Profile in order to meet their Open Source Guide's recommended community standards. As you can see, adding this file will improve [the php-webdriver community profile](https://github.com/facebook/php-webdriver/community) checklist and increase the visibility of our COC. **test plan:** Viewing it on my branch - (Flarnie will insert screenshots) **issue:** internal task t23481323 --- CODE_OF_CONDUCT.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0a45f9bd5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. From 52052a7e21075653de8a8b449097d16461ce5bc2 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Thu, 30 Nov 2017 19:13:05 -0200 Subject: [PATCH 083/487] Test against PHP 7.2 --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7484f1cfc..81266034f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ php: - 5.6 - 7.0 - 7.1 + - 7.2 env: global: @@ -16,29 +17,29 @@ env: matrix: include: # Add build to run tests against Firefox inside Travis environment - - php: 7.1 + - php: 7.2 env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" # Add build to run tests against Chrome inside Travis environment - - php: 7.1 + - php: 7.2 env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" addons: chrome: beta # Build with lowest possible dependencies - - php: 7.1 + - php: 7.2 env: DEPENDENCIES="--prefer-lowest" # Chrome on Travis build with lowest possible dependencies - - php: 7.1 + - php: 7.2 env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" DEPENDENCIES="--prefer-lowest" addons: chrome: beta # Saucelabs builds - - php: 7.1 + - php: 7.2 env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -47,7 +48,7 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.1 + - php: 7.2 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 & @@ -56,7 +57,7 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.1 + - php: 7.2 env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="15.15063" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -67,7 +68,7 @@ matrix: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= # Codestyle check build - - php: 7.1 + - php: 7.2 env: CHECK_CODESTYLE=1 before_script: ~ script: composer codestyle:check From d7cabc7fd357c1d8ccf43b5dbfb2116a74a193e0 Mon Sep 17 00:00:00 2001 From: Gabriel Caruso Date: Wed, 6 Dec 2017 05:31:52 -0200 Subject: [PATCH 084/487] Use assertArrayNotHasKey --- tests/unit/CookieTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 43215acb8..ade9af68f 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -79,7 +79,7 @@ public function testShouldProvideArrayAccessToProperties(Cookie $cookie) $cookie->offsetSet('domain', 'bar.com'); $this->assertSame('bar.com', $cookie['domain']); $cookie->offsetUnset('domain'); - $this->assertFalse(isset($cookie['domain'])); + $this->assertArrayNotHasKey('domain', $cookie); } public function testShouldBeCreatableFromAnArrayWithBasicValues() @@ -94,23 +94,23 @@ public function testShouldBeCreatableFromAnArrayWithBasicValues() $this->assertSame('cookieName', $cookie['name']); $this->assertSame('someValue', $cookie['value']); - $this->assertFalse(isset($cookie['path'])); + $this->assertArrayNotHasKey('path', $cookie); $this->assertNull($cookie['path']); $this->assertNull($cookie->getPath()); - $this->assertFalse(isset($cookie['domain'])); + $this->assertArrayNotHasKey('domain', $cookie); $this->assertNull($cookie['domain']); $this->assertNull($cookie->getDomain()); - $this->assertFalse(isset($cookie['expiry'])); + $this->assertArrayNotHasKey('expiry', $cookie); $this->assertNull($cookie['expiry']); $this->assertNull($cookie->getExpiry()); - $this->assertFalse(isset($cookie['secure'])); + $this->assertArrayNotHasKey('secure', $cookie); $this->assertNull($cookie['secure']); $this->assertNull($cookie->isSecure()); - $this->assertFalse(isset($cookie['httpOnly'])); + $this->assertArrayNotHasKey('httpOnly', $cookie); $this->assertNull($cookie['httpOnly']); $this->assertNull($cookie->isHttpOnly()); } From 5f41a0bbec16fc719712089fc12399b0c1342f59 Mon Sep 17 00:00:00 2001 From: Dmitry Matora Date: Fri, 3 Nov 2017 20:29:01 +0300 Subject: [PATCH 085/487] Implemented timeout support. Solves #496 --- lib/Remote/RemoteWebDriver.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index f8b8e5e6e..3a8d3563f 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -139,11 +139,20 @@ public static function create( * * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @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 * @return RemoteWebDriver */ - public static function createBySessionID($session_id, $selenium_server_url = 'http://localhost:4444/wd/hub') + public static function createBySessionID($session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', $connection_timeout_in_ms = null, $request_timeout_in_ms = null) { + $executor = new HttpCommandExecutor($selenium_server_url); + if ($connection_timeout_in_ms !== null) { + $executor->setConnectionTimeout($connection_timeout_in_ms); + } + if ($request_timeout_in_ms !== null) { + $executor->setRequestTimeout($request_timeout_in_ms); + } return new static($executor, $session_id); } From 21b62c4ceec300bbe63482c9027e3fc639aed3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 6 Dec 2017 18:35:19 +0100 Subject: [PATCH 086/487] Update chagnelog --- CHANGELOG.md | 2 ++ lib/Remote/RemoteWebDriver.php | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f42c31ef..a3adb0988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID. ## 1.5.0 - 2017-11-15 ### Changed diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 3a8d3563f..becab6b98 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -85,7 +85,7 @@ protected function __construct( * @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 string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through - * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through + * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through * @param DesiredCapabilities $required_capabilities The required capabilities * @return RemoteWebDriver */ @@ -143,9 +143,12 @@ public static function create( * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server * @return RemoteWebDriver */ - public static function createBySessionID($session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', $connection_timeout_in_ms = null, $request_timeout_in_ms = null) - { - + public static function createBySessionID( + $session_id, + $selenium_server_url = 'http://localhost:4444/wd/hub', + $connection_timeout_in_ms = null, + $request_timeout_in_ms = null + ) { $executor = new HttpCommandExecutor($selenium_server_url); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); From 32d5b8d8ba1161bff5838ebc70689560f6964211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 7 Dec 2017 00:35:05 +0100 Subject: [PATCH 087/487] Fix ChromeDriver declaration to match the parent class --- lib/Chrome/ChromeDriver.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Chrome/ChromeDriver.php b/lib/Chrome/ChromeDriver.php index 8ba76583c..832ca17a8 100644 --- a/lib/Chrome/ChromeDriver.php +++ b/lib/Chrome/ChromeDriver.php @@ -82,13 +82,16 @@ public static function create( * * @param string $session_id The existing session id * @param string $selenium_server_url The url of the remote Selenium WebDriver server - * + * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server + * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server * @throws WebDriverException * @return RemoteWebDriver|void */ public static function createBySessionID( $session_id, - $selenium_server_url = 'http://localhost:4444/wd/hub' + $selenium_server_url = 'http://localhost:4444/wd/hub', + $connection_timeout_in_ms = null, + $request_timeout_in_ms = null ) { throw new WebDriverException('Please use ChromeDriver::start() instead.'); } From 6baf65f9c62378a775900da2c6168888649b6f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Dec 2017 12:19:10 +0100 Subject: [PATCH 088/487] Upgrade php-coveralls to v 2.0.0 --- .travis.yml | 2 +- composer.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7484f1cfc..18db2fc36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -106,4 +106,4 @@ after_script: - if [ -f ./logs/php-server.log ]; then cat ./logs/php-server.log; fi after_success: - - travis_retry php vendor/bin/coveralls -v + - travis_retry php vendor/bin/php-coveralls -v diff --git a/composer.json b/composer.json index 069cb6df5..6aa6ef61b 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,7 @@ "friendsofphp/php-cs-fixer": "^2.0", "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", - "php-coveralls/php-coveralls": "^1.0.2", - "guzzle/guzzle": "^3.4.1", + "php-coveralls/php-coveralls": "^2.0", "symfony/var-dumper": "^3.3 || ^4.0" }, "autoload": { From cec193f66be978d107ad50112d1ad4546b255e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 Dec 2017 01:04:31 +0100 Subject: [PATCH 089/487] Use PHPStan for static analysis --- .travis.yml | 5 ++++- composer.json | 3 +++ lib/Firefox/FirefoxProfile.php | 2 +- lib/Support/Events/EventFiringWebDriver.php | 6 +++--- lib/Support/Events/EventFiringWebDriverNavigation.php | 2 +- lib/Support/Events/EventFiringWebElement.php | 2 +- lib/WebDriverExpectedCondition.php | 4 ++-- lib/WebDriverHasInputDevices.php | 2 +- phpstan.neon | 10 ++++++++++ tests/functional/WebDriverByTest.php | 4 ++-- 10 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 phpstan.neon diff --git a/.travis.yml b/.travis.yml index 18db2fc36..07687e845 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,10 @@ matrix: - php: 7.1 env: CHECK_CODESTYLE=1 before_script: ~ - script: composer codestyle:check + script: + - composer require phpstan/phpstan-shim # Not part of require-dev, because it won't install on PHP 5.6 + - composer analyze + - composer codestyle:check after_script: ~ after_success: ~ diff --git a/composer.json b/composer.json index 6aa6ef61b..c865ad691 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,9 @@ "codestyle:fix": [ "vendor/bin/php-cs-fixer fix --diff || exit 0", "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" + ], + "analyze": [ + "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" ] }, "extra": { diff --git a/lib/Firefox/FirefoxProfile.php b/lib/Firefox/FirefoxProfile.php index 8086198ca..b9d36b30e 100644 --- a/lib/Firefox/FirefoxProfile.php +++ b/lib/Firefox/FirefoxProfile.php @@ -110,7 +110,7 @@ public function setPreference($key, $value) } /** - * @param $key + * @param mixed $key * @return mixed */ public function getPreference($key) diff --git a/lib/Support/Events/EventFiringWebDriver.php b/lib/Support/Events/EventFiringWebDriver.php index c129ef015..5a75dbe1f 100644 --- a/lib/Support/Events/EventFiringWebDriver.php +++ b/lib/Support/Events/EventFiringWebDriver.php @@ -133,7 +133,7 @@ public function findElement(WebDriverBy $by) } /** - * @param $script + * @param string $script * @param array $arguments * @throws WebDriverException * @return mixed @@ -161,7 +161,7 @@ public function executeScript($script, array $arguments = []) } /** - * @param $script + * @param string $script * @param array $arguments * @throws WebDriverException * @return mixed @@ -396,7 +396,7 @@ protected function newElement(WebDriverElement $element) /** * @param mixed $method - * @param mixed $arguments,... + * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { diff --git a/lib/Support/Events/EventFiringWebDriverNavigation.php b/lib/Support/Events/EventFiringWebDriverNavigation.php index 0008ebbdc..7d8920eb1 100644 --- a/lib/Support/Events/EventFiringWebDriverNavigation.php +++ b/lib/Support/Events/EventFiringWebDriverNavigation.php @@ -149,7 +149,7 @@ public function to($url) /** * @param mixed $method - * @param mixed $arguments,... + * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index 099bb3573..9595953cf 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -392,7 +392,7 @@ protected function dispatchOnException(WebDriverException $exception) /** * @param mixed $method - * @param mixed $arguments,... + * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 433ee1a3a..d9ceb5dde 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -213,7 +213,7 @@ function (WebDriver $driver) use ($by) { if ($element->isDisplayed()) { $visibleElements[] = $element; } - } catch (StateElementReferenceException $e) { + } catch (StaleElementReferenceException $e) { } } @@ -399,7 +399,7 @@ function (WebDriver $driver) use ($by) { /** * An expectation for checking that an element with text is either invisible or not present on the DOM. * - * @param WebdriverBy $by The locator used to find the element. + * @param WebDriverBy $by The locator used to find the element. * @param string $text The text of the element. * @return WebDriverExpectedCondition Condition returns whether the text is found in the element located. */ diff --git a/lib/WebDriverHasInputDevices.php b/lib/WebDriverHasInputDevices.php index 93e42a30a..7cb1992eb 100644 --- a/lib/WebDriverHasInputDevices.php +++ b/lib/WebDriverHasInputDevices.php @@ -21,7 +21,7 @@ interface WebDriverHasInputDevices { /** - * @return WebDriverKeyBoard + * @return WebDriverKeyboard */ public function getKeyboard(); diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..7023c466e --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,10 @@ +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\(\)#' + - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' + - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 43d8c0457..534b145f0 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -25,8 +25,8 @@ class WebDriverByTest extends WebDriverTestCase { /** * @dataProvider textElementsProvider - * @param $webDriverByLocatorMethod - * @param $webDriverByLocatorValue + * @param string $webDriverByLocatorMethod + * @param string $webDriverByLocatorValue * @param string $expectedText * @param string $expectedAttributeValue */ From cc69cf0bbd807be3b8111098e03c46621d47e695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 Dec 2017 01:06:04 +0100 Subject: [PATCH 090/487] Use createMock shortcut contained in new PHPUnit --- tests/unit/WebDriverExpectedConditionTest.php | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index c4d734015..abcd5537b 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -34,11 +34,7 @@ class WebDriverExpectedConditionTest extends TestCase protected function setUp() { - // TODO: replace with createMock() once PHP 5.5 support is dropped - $this->driverMock = $this - ->getMockBuilder(RemoteWebDriver::class) - ->disableOriginalConstructor() - ->getMock(); + $this->driverMock = $this->createMock(RemoteWebDriver::class); $this->wait = new WebDriverWait($this->driverMock, 1, 1); } @@ -141,7 +137,7 @@ public function testShouldDetectPresenceOfElementLocatedCondition() public function testShouldDetectPresenceOfAllElementsLocatedByCondition() { - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $this->driverMock->expects($this->at(0)) ->method('findElements') @@ -166,7 +162,7 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() // Call #3: return Element, but isDisplayed will return false // Call #4: return Element, isDisplayed will true and condition will match - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('isDisplayed') ->willThrowException(new StaleElementReferenceException('')); @@ -189,9 +185,9 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() public function testShouldDetectVisibilityOfAnyElementLocated() { $elementList = [ - $this->createRemoteWebElementMock(), - $this->createRemoteWebElementMock(), - $this->createRemoteWebElementMock(), + $this->createMock(RemoteWebElement::class), + $this->createMock(RemoteWebElement::class), + $this->createMock(RemoteWebElement::class), ]; $elementList[0]->expects($this->once()) @@ -218,7 +214,7 @@ public function testShouldDetectVisibilityOfAnyElementLocated() public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchElementException() { - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $this->driverMock->expects($this->at(0)) ->method('findElement') @@ -241,7 +237,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionOnNoSuchEle public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElementReferenceException() { - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $this->driverMock->expects($this->exactly(2)) ->method('findElement') @@ -263,7 +259,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionOnStaleElem public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElementBecamesInvisible() { - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $this->driverMock->expects($this->exactly(2)) ->method('findElement') @@ -285,7 +281,7 @@ public function testShouldDetectInvisibilityOfElementLocatedConditionWhenElement public function testShouldDetectVisibilityOfCondition() { - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('isDisplayed') ->willReturn(false); @@ -307,7 +303,7 @@ public function testShouldDetectElementTextContainsCondition() // Call #3: return Element, but getText will throw StaleElementReferenceException // Call #4: return Element, getText will return new text and condition will match - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('getText') ->willReturn('this is an old text'); @@ -335,7 +331,7 @@ public function testShouldDetectElementTextIsCondition() // Call #3: return Element, getText will return not-matching text // Call #4: return Element, getText will return new text and condition will match - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('getText') ->willThrowException(new StaleElementReferenceException('')); @@ -366,7 +362,7 @@ public function testShouldDetectElementTextMatchesCondition() // Call #3: return Element, getText will return not-matching text // Call #4: return Element, getText will return matching text - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('getText') @@ -398,7 +394,7 @@ public function testShouldDetectElementValueContainsCondition() // Call #3: return Element, getAttribute('value') will return not-matching text // Call #4: return Element, getAttribute('value') will return matching text - $element = $this->createRemoteWebElementMock(); + $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) ->method('getAttribute') @@ -456,17 +452,4 @@ private function setupDriverToReturnElementAfterAnException($element, $expectedN ->willReturn($element); } } - - /** - * @todo Replace with createMock() once PHP 5.5 support is dropped - * @return \PHPUnit_Framework_MockObject_MockObject|RemoteWebElement - */ - private function createRemoteWebElementMock() - { - return $this->getMockBuilder(RemoteWebElement::class) - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->getMock(); - } } From 4962d348f5e71c284bebc59ab7dd54607518ee4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 Dec 2017 01:07:34 +0100 Subject: [PATCH 091/487] Use absolute FQCN in annotation --- tests/functional/Chrome/ChromeDriverServiceTest.php | 4 ++-- tests/functional/Chrome/ChromeDriverTest.php | 2 +- tests/functional/FileUploadTest.php | 4 ++-- tests/functional/RemoteWebDriverCreateTest.php | 4 ++-- tests/functional/RemoteWebDriverFindElementTest.php | 2 +- tests/functional/RemoteWebDriverTest.php | 4 ++-- tests/functional/RemoteWebElementTest.php | 2 +- tests/functional/WebDriverActionsTest.php | 2 +- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverByTest.php | 2 +- tests/functional/WebDriverNavigationTest.php | 2 +- tests/functional/WebDriverSelectTest.php | 4 ++-- tests/functional/WebDriverTimeoutsTest.php | 2 +- tests/unit/CookieTest.php | 2 +- tests/unit/Remote/RemoteWebDriverTest.php | 2 +- tests/unit/Remote/RemoteWebElementTest.php | 2 +- tests/unit/WebDriverExpectedConditionTest.php | 2 +- tests/unit/WebDriverKeysTest.php | 2 +- tests/unit/WebDriverOptionsTest.php | 2 +- 19 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/functional/Chrome/ChromeDriverServiceTest.php b/tests/functional/Chrome/ChromeDriverServiceTest.php index bc1a1b92f..781a68bae 100644 --- a/tests/functional/Chrome/ChromeDriverServiceTest.php +++ b/tests/functional/Chrome/ChromeDriverServiceTest.php @@ -19,8 +19,8 @@ /** * @group exclude-saucelabs - * @covers Facebook\WebDriver\Chrome\ChromeDriverService - * @covers Facebook\WebDriver\Remote\Service\DriverService + * @covers \Facebook\WebDriver\Chrome\ChromeDriverService + * @covers \Facebook\WebDriver\Remote\Service\DriverService */ class ChromeDriverServiceTest extends TestCase { diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index 476033839..b8066156e 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -21,7 +21,7 @@ /** * @group exclude-saucelabs - * @covers Facebook\WebDriver\Chrome\ChromeDriver + * @covers \Facebook\WebDriver\Chrome\ChromeDriver */ class ChromeDriverTest extends TestCase { diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index e88139953..5b2f6d2d3 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -18,8 +18,8 @@ use Facebook\WebDriver\Remote\LocalFileDetector; /** - * @covers Facebook\WebDriver\Remote\LocalFileDetector - * @covers Facebook\WebDriver\Remote\RemoteWebElement + * @covers \Facebook\WebDriver\Remote\LocalFileDetector + * @covers \Facebook\WebDriver\Remote\RemoteWebElement */ class FileUploadTest extends WebDriverTestCase { diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 3140b26b0..f9d7e809e 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -20,8 +20,8 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; /** - * @covers Facebook\WebDriver\Remote\RemoteWebDriver - * @covers Facebook\WebDriver\Remote\HttpCommandExecutor + * @covers \Facebook\WebDriver\Remote\RemoteWebDriver + * @covers \Facebook\WebDriver\Remote\HttpCommandExecutor */ class RemoteWebDriverCreateTest extends WebDriverTestCase { diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 46affc025..eea14b6e5 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -20,7 +20,7 @@ /** * Tests for findElement() and findElements() method of RemoteWebDriver. - * @covers Facebook\WebDriver\Remote\RemoteWebDriver + * @covers \Facebook\WebDriver\Remote\RemoteWebDriver */ class RemoteWebDriverFindElementTest extends WebDriverTestCase { diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 006b17c7b..440776ad5 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -20,7 +20,7 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebDriver + * @coversDefaultClass \Facebook\WebDriver\Remote\RemoteWebDriver */ class RemoteWebDriverTest extends WebDriverTestCase { @@ -170,7 +170,7 @@ function(){document.getElementById("id_test").innerHTML = "Text changed by scrip /** * @covers ::executeAsyncScript - * @covers Facebook\WebDriver\WebDriverTimeouts::setScriptTimeout + * @covers \Facebook\WebDriver\WebDriverTimeouts::setScriptTimeout */ public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() { diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index 51f414a93..d4614a431 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -19,7 +19,7 @@ use Facebook\WebDriver\Remote\RemoteWebElement; /** - * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebElement + * @coversDefaultClass \Facebook\WebDriver\Remote\RemoteWebElement */ class RemoteWebElementTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 173f7794c..188c76cd9 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -18,7 +18,7 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @coversDefaultClass Facebook\WebDriver\Interactions\WebDriverActions + * @coversDefaultClass \Facebook\WebDriver\Interactions\WebDriverActions */ class WebDriverActionsTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 08d8ef73c..163bee157 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -19,7 +19,7 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @covers Facebook\WebDriver\WebDriverAlert + * @covers \Facebook\WebDriver\WebDriverAlert */ class WebDriverAlertTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 534b145f0..028db7920 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -19,7 +19,7 @@ /** * Tests for locator strategies provided by WebDriverBy. - * @covers Facebook\WebDriver\WebDriverBy + * @covers \Facebook\WebDriver\WebDriverBy */ class WebDriverByTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php index 4f7dba252..d3aaebcb6 100644 --- a/tests/functional/WebDriverNavigationTest.php +++ b/tests/functional/WebDriverNavigationTest.php @@ -16,7 +16,7 @@ namespace Facebook\WebDriver; /** - * @coversDefaultClass Facebook\WebDriver\WebDriverNavigation + * @coversDefaultClass \Facebook\WebDriver\WebDriverNavigation */ class WebDriverNavigationTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index 9cd6cf250..37ad416af 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -20,8 +20,8 @@ use Facebook\WebDriver\Exception\UnsupportedOperationException; /** - * @covers Facebook\WebDriver\WebDriverSelect - * @covers Facebook\WebDriver\Exception\UnexpectedTagNameException + * @covers \Facebook\WebDriver\WebDriverSelect + * @covers \Facebook\WebDriver\Exception\UnexpectedTagNameException */ class WebDriverSelectTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 808a494ab..69d143cc4 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -22,7 +22,7 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @coversDefaultClass Facebook\WebDriver\WebDriverTimeouts + * @coversDefaultClass \Facebook\WebDriver\WebDriverTimeouts */ class WebDriverTimeoutsTest extends WebDriverTestCase { diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index ade9af68f..3a0679d2c 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase; /** - * @covers Facebook\WebDriver\Cookie + * @covers \Facebook\WebDriver\Cookie */ class CookieTest extends TestCase { diff --git a/tests/unit/Remote/RemoteWebDriverTest.php b/tests/unit/Remote/RemoteWebDriverTest.php index c8fee7e5d..c5e397e00 100644 --- a/tests/unit/Remote/RemoteWebDriverTest.php +++ b/tests/unit/Remote/RemoteWebDriverTest.php @@ -24,7 +24,7 @@ /** * Unit part of RemoteWebDriver tests. Ie. tests for behavior which do not interact with the real remote server. * - * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebDriver + * @coversDefaultClass \Facebook\WebDriver\Remote\RemoteWebDriver */ class RemoteWebDriverTest extends TestCase { diff --git a/tests/unit/Remote/RemoteWebElementTest.php b/tests/unit/Remote/RemoteWebElementTest.php index 80bc92931..e1b0a2b47 100644 --- a/tests/unit/Remote/RemoteWebElementTest.php +++ b/tests/unit/Remote/RemoteWebElementTest.php @@ -20,7 +20,7 @@ /** * Unit part of RemoteWebDriver tests. Ie. tests for behavior which do not interact with the real remote server. * - * @coversDefaultClass Facebook\WebDriver\Remote\RemoteWebElement + * @coversDefaultClass \Facebook\WebDriver\Remote\RemoteWebElement */ class RemoteWebElementTest extends TestCase { diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index abcd5537b..8cd4203a9 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -23,7 +23,7 @@ use PHPUnit\Framework\TestCase; /** - * @covers Facebook\WebDriver\WebDriverExpectedCondition + * @covers \Facebook\WebDriver\WebDriverExpectedCondition */ class WebDriverExpectedConditionTest extends TestCase { diff --git a/tests/unit/WebDriverKeysTest.php b/tests/unit/WebDriverKeysTest.php index a309402b8..71fef3b9e 100644 --- a/tests/unit/WebDriverKeysTest.php +++ b/tests/unit/WebDriverKeysTest.php @@ -18,7 +18,7 @@ use PHPUnit\Framework\TestCase; /** - * @covers Facebook\WebDriver\WebDriverKeys + * @covers \Facebook\WebDriver\WebDriverKeys */ class WebDriverKeysTest extends TestCase { diff --git a/tests/unit/WebDriverOptionsTest.php b/tests/unit/WebDriverOptionsTest.php index 07c721c48..dd7494c67 100644 --- a/tests/unit/WebDriverOptionsTest.php +++ b/tests/unit/WebDriverOptionsTest.php @@ -20,7 +20,7 @@ use PHPUnit\Framework\TestCase; /** - * @covers Facebook\WebDriver\WebDriverOptions + * @covers \Facebook\WebDriver\WebDriverOptions */ class WebDriverOptionsTest extends TestCase { From bc2b2d520ac8dfb6576b2c86d00999538c4c57d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 9 Dec 2017 01:19:25 +0100 Subject: [PATCH 092/487] Lint PHP files --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c865ad691..aaff428b5 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", "php-coveralls/php-coveralls": "^2.0", - "symfony/var-dumper": "^3.3 || ^4.0" + "symfony/var-dumper": "^3.3 || ^4.0", + "jakub-onderka/php-parallel-lint": "^0.9.2" }, "autoload": { "psr-4": { @@ -47,6 +48,7 @@ "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" ], "analyze": [ + "vendor/bin/parallel-lint -j 10 ./lib ./tests", "vendor/bin/phpstan.phar analyze ./lib ./tests --level 2 -c phpstan.neon --ansi" ] }, From 0750ede674372496e7564882e7d673265b35f055 Mon Sep 17 00:00:00 2001 From: Alexandr Motuzov Date: Fri, 29 Dec 2017 23:03:50 +0500 Subject: [PATCH 093/487] Add `ext-mbstring` to requirements `mb_strpos` function is used in several places in the project. Many features will not work without `ext-mbstring` installed. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index aaff428b5..a96b82fe5 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1 || ^4.0", "ext-curl": "*", - "ext-zip": "*" + "ext-zip": "*", + "ext-mbstring": "*" }, "require-dev": { "phpunit/phpunit": "^5.7", From 7c69d51ed8b2ce845b4b39fe01b822628f429587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 2 Jan 2018 12:32:43 +0100 Subject: [PATCH 094/487] Remove xdebug to spped up codestyle check build --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 07687e845..1c6c75cd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,6 +69,8 @@ matrix: # Codestyle check build - php: 7.1 env: CHECK_CODESTYLE=1 + before_install: + - phpenv config-rm xdebug.ini before_script: ~ script: - composer require phpstan/phpstan-shim # Not part of require-dev, because it won't install on PHP 5.6 @@ -82,10 +84,8 @@ cache: - $HOME/.composer/cache - jar -before_install: - - travis_retry composer self-update - install: + - travis_retry composer self-update - travis_retry composer update --no-interaction $DEPENDENCIES before_script: From fda6f7f6d45ed5baa2f6ce2cd9ba3d57e165b42d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 14 Jan 2018 00:21:39 +0100 Subject: [PATCH 095/487] Use a workaround for Chrome crashing on startup on Travis https://github.com/SeleniumHQ/selenium/issues/4961 --- tests/functional/Chrome/ChromeDriverTest.php | 9 ++++++++- tests/functional/WebDriverTestCase.php | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/functional/Chrome/ChromeDriverTest.php b/tests/functional/Chrome/ChromeDriverTest.php index b8066156e..89eb766cd 100644 --- a/tests/functional/Chrome/ChromeDriverTest.php +++ b/tests/functional/Chrome/ChromeDriverTest.php @@ -15,6 +15,7 @@ namespace Facebook\WebDriver\Chrome; +use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; use PHPUnit\Framework\TestCase; @@ -47,7 +48,13 @@ public function testShouldStartChromeDriver() // The createDefaultService() method expect path to the executable to be present in the environment variable putenv(ChromeDriverService::CHROME_DRIVER_EXE_PROPERTY . '=' . getenv('CHROMEDRIVER_PATH')); - $this->driver = ChromeDriver::start(); + // Add --no-sandbox as a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 + $chromeOptions = new ChromeOptions(); + $chromeOptions->addArguments(['--no-sandbox']); + $desiredCapabilities = DesiredCapabilities::chrome(); + $desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); + + $this->driver = ChromeDriver::start($desiredCapabilities); $this->assertInstanceOf(ChromeDriver::class, $this->driver); $this->assertInstanceOf(DriverCommandExecutor::class, $this->driver->getCommandExecutor()); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 4abaf7c03..e9714a87c 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -55,7 +55,8 @@ protected function setUp() if ($browserName === WebDriverBrowserType::CHROME) { $chromeOptions = new ChromeOptions(); - $chromeOptions->addArguments(['--headless', 'window-size=1024,768']); + // --no-sandbox is a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 + $chromeOptions->addArguments(['--headless', 'window-size=1024,768', '--no-sandbox']); $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); } From 21412fe9ce48ec8c23508879b73d78aa36c038ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 14 Jan 2018 00:25:19 +0100 Subject: [PATCH 096/487] Update Chromedriver and use stable chrome in Travis builds --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c6c75cd9..3ee125011 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: global: - DISPLAY=:99.0 - BROWSER_NAME="htmlunit" - - CHROMEDRIVER_VERSION="2.31" + - CHROMEDRIVER_VERSION="2.35" matrix: include: @@ -25,7 +25,7 @@ matrix: - php: 7.1 env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" addons: - chrome: beta + chrome: stable # Build with lowest possible dependencies - php: 7.1 @@ -35,7 +35,7 @@ matrix: - php: 7.1 env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" DEPENDENCIES="--prefer-lowest" addons: - chrome: beta + chrome: stable # Saucelabs builds - php: 7.1 From 04b741060ea0f4fc0a4e3822a10b461005362a52 Mon Sep 17 00:00:00 2001 From: Stephanyan Date: Thu, 8 Feb 2018 22:53:17 +0100 Subject: [PATCH 097/487] Add .vscode folder to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 42ef8b562..deb25f3ae 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ logs/ *~ *.swp .idea +.vscode From 5c724f1b73e20a8241179df36db1b636170cd405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 16 Feb 2018 00:33:29 +0000 Subject: [PATCH 098/487] Remove invalid phpDoc return tags --- lib/WebDriverExpectedCondition.php | 58 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index d9ceb5dde..505474dfd 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -51,7 +51,7 @@ public function getApply() * An expectation for checking the title of a page. * * @param string $title The expected title, which must be an exact match. - * @return WebDriverExpectedCondition Condition returns whether current page title equals given string. + * @return static Condition returns whether current page title equals given string. */ public static function titleIs($title) { @@ -66,7 +66,7 @@ function (WebDriver $driver) use ($title) { * An expectation for checking substring of a page Title. * * @param string $title The expected substring of Title. - * @return WebDriverExpectedCondition Condition returns whether current page title contains given string. + * @return static Condition returns whether current page title contains given string. */ public static function titleContains($title) { @@ -81,8 +81,7 @@ function (WebDriver $driver) use ($title) { * An expectation for checking current page title matches the given regular expression. * * @param string $titleRegexp The regular expression to test against. - * @return WebDriverExpectedCondition Condition returns whether current page title matches the regular - * expression. + * @return static Condition returns whether current page title matches the regular expression. */ public static function titleMatches($titleRegexp) { @@ -97,7 +96,7 @@ function (WebDriver $driver) use ($titleRegexp) { * An expectation for checking the URL of a page. * * @param string $url The expected URL, which must be an exact match. - * @return WebDriverExpectedCondition Condition returns whether current URL equals given one. + * @return static Condition returns whether current URL equals given one. */ public static function urlIs($url) { @@ -112,7 +111,7 @@ function (WebDriver $driver) use ($url) { * An expectation for checking substring of the URL of a page. * * @param string $url The expected substring of the URL - * @return WebDriverExpectedCondition Condition returns whether current URL contains given string. + * @return static Condition returns whether current URL contains given string. */ public static function urlContains($url) { @@ -127,7 +126,7 @@ function (WebDriver $driver) use ($url) { * An expectation for checking current page URL matches the given regular expression. * * @param string $urlRegexp The regular expression to test against. - * @return WebDriverExpectedCondition Condition returns whether current URL matches the regular expression. + * @return static Condition returns whether current URL matches the regular expression. */ public static function urlMatches($urlRegexp) { @@ -143,7 +142,7 @@ function (WebDriver $driver) use ($urlRegexp) { * This does not necessarily mean that the element is visible. * * @param WebDriverBy $by The locator used to find the element. - * @return WebDriverExpectedCondition Condition returns the element which is located. + * @return static Condition returns the WebDriverElement which is located. */ public static function presenceOfElementLocated(WebDriverBy $by) { @@ -158,7 +157,7 @@ function (WebDriver $driver) use ($by) { * An expectation for checking that there is at least one element present on a web page. * * @param WebDriverBy $by The locator used to find the element. - * @return WebDriverExpectedCondition Condition returns an array of WebDriverElements once they are located. + * @return static Condition return an array of WebDriverElement once they are located. */ public static function presenceOfAllElementsLocatedBy(WebDriverBy $by) { @@ -176,7 +175,7 @@ function (WebDriver $driver) use ($by) { * Visibility means that the element is not only displayed but also has a height and width that is greater than 0. * * @param WebDriverBy $by The locator used to find the element. - * @return WebDriverExpectedCondition Condition returns the element which is located and visible. + * @return static Condition returns the WebDriverElement which is located and visible. */ public static function visibilityOfElementLocated(WebDriverBy $by) { @@ -199,7 +198,7 @@ function (WebDriver $driver) use ($by) { * Visibility means that the element is not only displayed but also has a height and width that is greater than 0. * * @param WebDriverBy $by The located used to find the element. - * @return WebDriverExpectedCondition Condition returns the elements that are located and visible. + * @return static Condition returns the array of WebDriverElement that are located and visible. */ public static function visibilityOfAnyElementLocated(WebDriverBy $by) { @@ -227,8 +226,7 @@ function (WebDriver $driver) use ($by) { * Visibility means that the element is not only displayed but also has a height and width that is greater than 0. * * @param WebDriverElement $element The element to be checked. - * @return WebDriverExpectedCondition Condition returns the same WebDriverElement once it is - * visible. + * @return static Condition returns the same WebDriverElement once it is visible. */ public static function visibilityOf(WebDriverElement $element) { @@ -247,7 +245,7 @@ function () use ($element) { * @deprecated Use WebDriverExpectedCondition::elementTextContains() instead * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element. - * @return WebDriverExpectedCondition Condition returns whether the text is present in the element. + * @return static Condition returns whether the text is present in the element. */ public static function textToBePresentInElement(WebDriverBy $by, $text) { @@ -260,7 +258,7 @@ public static function textToBePresentInElement(WebDriverBy $by, $text) * * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element. - * @return WebDriverExpectedCondition Condition returns whether the partial text is present in the element. + * @return static Condition returns whether the partial text is present in the element. */ public static function elementTextContains(WebDriverBy $by, $text) { @@ -283,7 +281,7 @@ function (WebDriver $driver) use ($by, $text) { * * @param WebDriverBy $by The locator used to find the element. * @param string $text The expected text of the element. - * @return WebDriverExpectedCondition Condition returns whether the element has text value equal to given one. + * @return static Condition returns whether the element has text value equal to given one. */ public static function elementTextIs(WebDriverBy $by, $text) { @@ -303,7 +301,7 @@ function (WebDriver $driver) use ($by, $text) { * * @param WebDriverBy $by The locator used to find the element. * @param string $regexp The regular expression to test against. - * @return WebDriverExpectedCondition Condition returns whether the element has text value equal to given one. + * @return static Condition returns whether the element has text value equal to given one. */ public static function elementTextMatches(WebDriverBy $by, $regexp) { @@ -325,7 +323,7 @@ function (WebDriver $driver) use ($by, $regexp) { * @deprecated Use WebDriverExpectedCondition::elementValueContains() instead * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element value. - * @return WebDriverExpectedCondition Condition returns whether the text is present in value attribute. + * @return static Condition returns whether the text is present in value attribute. */ public static function textToBePresentInElementValue(WebDriverBy $by, $text) { @@ -337,7 +335,7 @@ public static function textToBePresentInElementValue(WebDriverBy $by, $text) * * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element value. - * @return WebDriverExpectedCondition Condition returns whether the text is present in value attribute. + * @return static Condition returns whether the text is present in value attribute. */ public static function elementValueContains(WebDriverBy $by, $text) { @@ -359,8 +357,7 @@ function (WebDriver $driver) use ($by, $text) { * * @param string $frame_locator The locator used to find the iFrame * expected to be either the id or name value of the i/frame - * @return WebDriverExpectedCondition Condition returns object focused on new frame when frame is - * found, false otherwise. + * @return static Condition returns object focused on new frame when frame is found, false otherwise. */ public static function frameToBeAvailableAndSwitchToIt($frame_locator) { @@ -379,7 +376,7 @@ function (WebDriver $driver) use ($frame_locator) { * An expectation for checking that an element is either invisible or not present on the DOM. * * @param WebDriverBy $by The locator used to find the element. - * @return WebDriverExpectedCondition Condition returns whether no visible element located. + * @return static Condition returns whether no visible element located. */ public static function invisibilityOfElementLocated(WebDriverBy $by) { @@ -401,7 +398,7 @@ function (WebDriver $driver) use ($by) { * * @param WebDriverBy $by The locator used to find the element. * @param string $text The text of the element. - * @return WebDriverExpectedCondition Condition returns whether the text is found in the element located. + * @return static Condition returns whether the text is found in the element located. */ public static function invisibilityOfElementWithText(WebDriverBy $by, $text) { @@ -422,8 +419,7 @@ function (WebDriver $driver) use ($by, $text) { * An expectation for checking an element is visible and enabled such that you can click it. * * @param WebDriverBy $by The locator used to find the element - * @return WebDriverExpectedCondition Condition return the WebDriverElement once it is located, - * visible and clickable. + * @return static Condition return the WebDriverElement once it is located, visible and clickable. */ public static function elementToBeClickable(WebDriverBy $by) { @@ -453,7 +449,7 @@ function (WebDriver $driver) use ($visibility_of_element_located) { * Wait until an element is no longer attached to the DOM. * * @param WebDriverElement $element The element to wait for. - * @return WebDriverExpectedCondition Condition returns whether the element is still attached to the DOM. + * @return static Condition returns whether the element is still attached to the DOM. */ public static function stalenessOf(WebDriverElement $element) { @@ -479,8 +475,7 @@ function () use ($element) { * the condition is checked. * * @param WebDriverExpectedCondition $condition The condition wrapped. - * @return WebDriverExpectedCondition Condition returns the return value of the getApply() of the given - * condition. + * @return static Condition returns the return value of the getApply() of the given condition. */ public static function refreshed(self $condition) { @@ -499,7 +494,7 @@ function (WebDriver $driver) use ($condition) { * An expectation for checking if the given element is selected. * * @param mixed $element_or_by Either the element or the locator. - * @return WebDriverExpectedCondition Condition returns whether the element is selected. + * @return static Condition returns whether the element is selected. */ public static function elementToBeSelected($element_or_by) { @@ -514,7 +509,7 @@ public static function elementToBeSelected($element_or_by) * * @param mixed $element_or_by Either the element or the locator. * @param bool $selected The required state. - * @return WebDriverExpectedCondition Condition returns whether the element is selected. + * @return static Condition returns whether the element is selected. */ public static function elementSelectionStateToBe($element_or_by, $selected) { @@ -544,8 +539,7 @@ function (WebDriver $driver) use ($element_or_by, $selected) { /** * An expectation for whether an alert() box is present. * - * @return WebDriverExpectedCondition Condition returns WebDriverAlert if alert() is present, - * null otherwise. + * @return static Condition returns WebDriverAlert if alert() is present, null otherwise. */ public static function alertIsPresent() { From 372fcba2b549a2f7a982e5eacb45614961dcddd5 Mon Sep 17 00:00:00 2001 From: Antonin Rykalsky Date: Mon, 12 Feb 2018 12:58:01 +0100 Subject: [PATCH 099/487] Disable sending Expect header in POST requests --- lib/Remote/HttpCommandExecutor.php | 6 +++++ .../ReportSauceLabsStatusListener.php | 2 ++ tests/unit/Remote/HttpCommandExecutorTest.php | 26 ++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 09bf000d0..6a795080c 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -263,6 +263,12 @@ public function execute(WebDriverCommand $command) curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); } + if (in_array($http_method, ['POST', 'PUT'])) { + // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy + // https://tools.ietf.org/html/rfc7231#section-5.1.1 + curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Expect:']); + } + $encoded_params = null; if ($http_method === 'POST' && $params && is_array($params)) { diff --git a/tests/functional/ReportSauceLabsStatusListener.php b/tests/functional/ReportSauceLabsStatusListener.php index f2a3c9f7d..8d25b76ff 100644 --- a/tests/functional/ReportSauceLabsStatusListener.php +++ b/tests/functional/ReportSauceLabsStatusListener.php @@ -77,6 +77,8 @@ private function submitToSauceLabs($url, array $data) curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); curl_setopt($curl, CURLOPT_USERPWD, getenv('SAUCE_USERNAME') . ':' . getenv('SAUCE_ACCESS_KEY')); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); + // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy + curl_setopt($curl, CURLOPT_HTTPHEADER, ['Expect:']); curl_exec($curl); diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index a3810e731..5519fa0e4 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -34,19 +34,32 @@ public function setUp() * @dataProvider commandProvider * @param int $command * @param array $params + * @param bool $shouldResetExpectHeader * @param string $expectedUrl * @param string $expectedPostData */ - public function testShouldSendRequestToAssembledUrl($command, array $params, $expectedUrl, $expectedPostData) - { + public function testShouldSendRequestToAssembledUrl( + $command, + array $params, + $shouldResetExpectHeader, + $expectedUrl, + $expectedPostData + ) { $command = new WebDriverCommand('foo-123', $command, $params); $curlSetoptMock = $this->getFunctionMock(__NAMESPACE__, 'curl_setopt'); $curlSetoptMock->expects($this->at(0)) ->with($this->anything(), CURLOPT_URL, $expectedUrl); - $curlSetoptMock->expects($this->at(2)) - ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + if ($shouldResetExpectHeader) { + $curlSetoptMock->expects($this->at(2)) + ->with($this->anything(), CURLOPT_HTTPHEADER, ['Expect:']); + $curlSetoptMock->expects($this->at(3)) + ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + } else { + $curlSetoptMock->expects($this->at(2)) + ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); + } $curlExecMock = $this->getFunctionMock(__NAMESPACE__, 'curl_exec'); $curlExecMock->expects($this->once()) @@ -64,30 +77,35 @@ public function commandProvider() 'POST command having :id placeholder in url' => [ DriverCommand::SEND_KEYS_TO_ELEMENT, ['value' => 'submitted-value', ':id' => '1337'], + true, 'http://localhost:4444/session/foo-123/element/1337/value', '{"value":"submitted-value"}', ], 'POST command without :id placeholder in url' => [ DriverCommand::TOUCH_UP, ['x' => 3, 'y' => 6], + true, 'http://localhost:4444/session/foo-123/touch/up', '{"x":3,"y":6}', ], 'Extra useless placeholder parameter should be removed' => [ DriverCommand::TOUCH_UP, ['x' => 3, 'y' => 6, ':useless' => 'foo'], + true, 'http://localhost:4444/session/foo-123/touch/up', '{"x":3,"y":6}', ], 'DELETE command' => [ DriverCommand::DELETE_COOKIE, [':name' => 'cookie-name'], + false, 'http://localhost:4444/session/foo-123/cookie/cookie-name', null, ], 'GET command without session in URL' => [ DriverCommand::GET_ALL_SESSIONS, [], + false, 'http://localhost:4444/sessions', null, ], From 42e7b6554f30689baf43827b791a678b2f7ecc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 17 Feb 2018 01:19:28 +0000 Subject: [PATCH 100/487] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3adb0988..67969e3f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Added - Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID. +### Changed +- Disable sending 'Expect: 100-Continue' header with POST requests, as they may more easily fail when sending via eg. squid proxy. + ## 1.5.0 - 2017-11-15 ### Changed - Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6. From 2d40e31c8911c4e0e9df43ca771623266bb978da Mon Sep 17 00:00:00 2001 From: Drew Budwin Date: Fri, 2 Feb 2018 18:37:19 -0500 Subject: [PATCH 101/487] Update PHPDoc for functions that return static instances of a class Previously, functions that contained functionality similar to `return new static(...)` had their `@return` PHPDoc tag to specify the class name like `@return SomeClass` where the preferred documentation should be `@return static`. Some support for this decision: https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#keyword https://netbeans.org/bugzilla/show_bug.cgi?id=196565 https://blog.madewithlove.be/post/return-types/ --- CHANGELOG.md | 1 + lib/Chrome/ChromeDriver.php | 3 +++ lib/Chrome/ChromeDriverService.php | 3 +++ lib/Remote/DesiredCapabilities.php | 24 ++++++++++---------- lib/Remote/RemoteWebDriver.php | 4 ++-- lib/Remote/RemoteWebElement.php | 2 +- lib/Support/Events/EventFiringWebElement.php | 2 +- lib/WebDriverBy.php | 16 ++++++------- lib/WebDriverExpectedCondition.php | 2 +- 9 files changed, 32 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3adb0988..0b7288a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ### Added - Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID. +- Update PHPDoc for functions that return static instances of a class. ## 1.5.0 - 2017-11-15 ### Changed diff --git a/lib/Chrome/ChromeDriver.php b/lib/Chrome/ChromeDriver.php index 832ca17a8..107854b4a 100644 --- a/lib/Chrome/ChromeDriver.php +++ b/lib/Chrome/ChromeDriver.php @@ -24,6 +24,9 @@ class ChromeDriver extends RemoteWebDriver { + /** + * @return static + */ public static function start(DesiredCapabilities $desired_capabilities = null, ChromeDriverService $service = null) { if ($desired_capabilities === null) { diff --git a/lib/Chrome/ChromeDriverService.php b/lib/Chrome/ChromeDriverService.php index 90b3d24f7..c9525126d 100644 --- a/lib/Chrome/ChromeDriverService.php +++ b/lib/Chrome/ChromeDriverService.php @@ -22,6 +22,9 @@ class ChromeDriverService extends DriverService // The environment variable storing the path to the chrome driver executable. const CHROME_DRIVER_EXE_PROPERTY = 'webdriver.chrome.driver'; + /** + * @return static + */ public static function createDefaultService() { $exe = getenv(self::CHROME_DRIVER_EXE_PROPERTY); diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 940fcf98c..0e1a1ab25 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -178,7 +178,7 @@ public function toArray() } /** - * @return DesiredCapabilities + * @return static */ public static function android() { @@ -189,7 +189,7 @@ public static function android() } /** - * @return DesiredCapabilities + * @return static */ public static function chrome() { @@ -200,7 +200,7 @@ public static function chrome() } /** - * @return DesiredCapabilities + * @return static */ public static function firefox() { @@ -218,7 +218,7 @@ public static function firefox() } /** - * @return DesiredCapabilities + * @return static */ public static function htmlUnit() { @@ -229,7 +229,7 @@ public static function htmlUnit() } /** - * @return DesiredCapabilities + * @return static */ public static function htmlUnitWithJS() { @@ -242,7 +242,7 @@ public static function htmlUnitWithJS() } /** - * @return DesiredCapabilities + * @return static */ public static function internetExplorer() { @@ -253,7 +253,7 @@ public static function internetExplorer() } /** - * @return DesiredCapabilities + * @return static */ public static function microsoftEdge() { @@ -264,7 +264,7 @@ public static function microsoftEdge() } /** - * @return DesiredCapabilities + * @return static */ public static function iphone() { @@ -275,7 +275,7 @@ public static function iphone() } /** - * @return DesiredCapabilities + * @return static */ public static function ipad() { @@ -286,7 +286,7 @@ public static function ipad() } /** - * @return DesiredCapabilities + * @return static */ public static function opera() { @@ -297,7 +297,7 @@ public static function opera() } /** - * @return DesiredCapabilities + * @return static */ public static function safari() { @@ -308,7 +308,7 @@ public static function safari() } /** - * @return DesiredCapabilities + * @return static */ public static function phantomjs() { diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index becab6b98..1487689ec 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -87,7 +87,7 @@ protected function __construct( * @param string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through * @param DesiredCapabilities $required_capabilities The required capabilities - * @return RemoteWebDriver + * @return static */ public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', @@ -141,7 +141,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 - * @return RemoteWebDriver + * @return static */ public static function createBySessionID( $session_id, diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 17fa258c4..26936d8b9 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -408,7 +408,7 @@ public function equals(WebDriverElement $other) * * @param string $id * - * @return RemoteWebElement + * @return static */ protected function newElement($id) { diff --git a/lib/Support/Events/EventFiringWebElement.php b/lib/Support/Events/EventFiringWebElement.php index 9595953cf..8df5cbb78 100644 --- a/lib/Support/Events/EventFiringWebElement.php +++ b/lib/Support/Events/EventFiringWebElement.php @@ -405,7 +405,7 @@ protected function dispatch($method, ...$arguments) /** * @param WebDriverElement $element - * @return EventFiringWebElement + * @return static */ protected function newElement(WebDriverElement $element) { diff --git a/lib/WebDriverBy.php b/lib/WebDriverBy.php index f882683b3..822a5c5fc 100644 --- a/lib/WebDriverBy.php +++ b/lib/WebDriverBy.php @@ -60,7 +60,7 @@ public function getValue() * names are not permitted. * * @param string $class_name - * @return WebDriverBy + * @return static */ public static function className($class_name) { @@ -71,7 +71,7 @@ public static function className($class_name) * Locates elements matching a CSS selector. * * @param string $css_selector - * @return WebDriverBy + * @return static */ public static function cssSelector($css_selector) { @@ -82,7 +82,7 @@ public static function cssSelector($css_selector) * Locates elements whose ID attribute matches the search value. * * @param string $id - * @return WebDriverBy + * @return static */ public static function id($id) { @@ -93,7 +93,7 @@ public static function id($id) * Locates elements whose NAME attribute matches the search value. * * @param string $name - * @return WebDriverBy + * @return static */ public static function name($name) { @@ -104,7 +104,7 @@ public static function name($name) * Locates anchor elements whose visible text matches the search value. * * @param string $link_text - * @return WebDriverBy + * @return static */ public static function linkText($link_text) { @@ -116,7 +116,7 @@ public static function linkText($link_text) * value. * * @param string $partial_link_text - * @return WebDriverBy + * @return static */ public static function partialLinkText($partial_link_text) { @@ -127,7 +127,7 @@ public static function partialLinkText($partial_link_text) * Locates elements whose tag name matches the search value. * * @param string $tag_name - * @return WebDriverBy + * @return static */ public static function tagName($tag_name) { @@ -138,7 +138,7 @@ public static function tagName($tag_name) * Locates elements matching an XPath expression. * * @param string $xpath - * @return WebDriverBy + * @return static */ public static function xpath($xpath) { diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 505474dfd..15c7b3d2d 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -564,7 +564,7 @@ function (WebDriver $driver) { * An expectation checking the number of opened windows. * * @param int $expectedNumberOfWindows - * @return WebDriverExpectedCondition + * @return static */ public static function numberOfWindowsToBe($expectedNumberOfWindows) { From c4dcacf1978e680573042823606a40c1f990dd3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 25 Feb 2018 17:45:57 +0000 Subject: [PATCH 102/487] Describe a bit more how to use php-webdriver with Firefox --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5b331d349..9bbb228c6 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,7 @@ Download and run that file, replacing # with the current server version. Keep in java -jar selenium-server-standalone-#.jar -When using Selenium server 3.5 and newer with some remote end clients (eg. Firefox with Geckodriver), you MUST disable so called "pass-through" mode, so that remote browser's protocol is translated to the protocol supported by php-webdriver (see [issue #469](https://github.com/facebook/php-webdriver/issues/469)): - - java -jar selenium-server-standalone-#.jar -enablePassThrough false +(Please see note below when using Firefox.) Then when you create a session, be sure to pass the url to where your server is running. @@ -58,6 +56,13 @@ $host = 'http://localhost:4444/wd/hub'; // this is the default Make sure to have latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases) installed. +Because Firefox (and Geckodriver) only supports new W3C WebDriver protocol (which is yet to be implemented by php-wedbriver - see [issue #469](https://github.com/facebook/php-webdriver/issues/469)), +the protocols must be translated by Selenium server - this feature is *partially* available in Selenium server version 3.5.0-3.8.1 and you can enable it like this: + + java -jar selenium-server-standalone-3.8.1.jar -enablePassThrough false + +Now you can start Firefox from your code: + ```php $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); ``` From a6d99922c0c9812ee4a663b5796be734b5a40257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 9 Mar 2018 11:10:25 +0100 Subject: [PATCH 103/487] Add missing required extensions to composer.json --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a96b82fe5..5e5fce576 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "symfony/process": "^2.8 || ^3.1 || ^4.0", "ext-curl": "*", "ext-zip": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^5.7", @@ -28,6 +29,9 @@ "symfony/var-dumper": "^3.3 || ^4.0", "jakub-onderka/php-parallel-lint": "^0.9.2" }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, "autoload": { "psr-4": { "Facebook\\WebDriver\\": "lib/" From 17881d2832df05301185b3ae96081f25c484d12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 9 Mar 2018 11:13:31 +0100 Subject: [PATCH 104/487] Prefer Chrome in example script --- example.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/example.php b/example.php index 263d0c1d6..513b918b3 100644 --- a/example.php +++ b/example.php @@ -1,5 +1,6 @@ get('http://www.seleniumhq.org/'); +$driver->get('https://www.seleniumhq.org/'); // adding cookie $driver->manage()->deleteAllCookies(); @@ -57,5 +58,5 @@ ) ); -// close the Firefox +// close the browser $driver->quit(); From d4d655e958658eb0ab0050aea8f4d6c11f471c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 9 Mar 2018 11:28:50 +0100 Subject: [PATCH 105/487] Remove workaround for selenium bug https://github.com/SeleniumHQ/selenium/issues/3398 --- example.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/example.php b/example.php index 513b918b3..2a9d0b298 100644 --- a/example.php +++ b/example.php @@ -45,11 +45,8 @@ // write 'php' in the search box $driver->findElement(WebDriverBy::id('q')) - ->sendKeys('php'); - -// submit the form -$driver->findElement(WebDriverBy::id('submit')) - ->click(); // submit() does not work in Selenium 3 because of bug https://github.com/SeleniumHQ/selenium/issues/3398 + ->sendKeys('php') // fill the search box + ->submit(); // submit the whole form // wait at most 10 seconds until at least one result is shown $driver->wait(10)->until( From bc08cfaddb1bb1a1f25d1981c637b92fa9d3d6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 9 Mar 2018 11:38:17 +0100 Subject: [PATCH 106/487] Suggest Chrome on the first place in readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9bbb228c6..c0bd89d1f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,14 @@ Then when you create a session, be sure to pass the url to where your server is $host = 'http://localhost:4444/wd/hub'; // this is the default ``` +##### Launch Chrome + +Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) installed. + +```php +$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); +``` + ##### Launch Firefox Make sure to have latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases) installed. @@ -67,14 +75,6 @@ Now you can start Firefox from your code: $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); ``` -##### Launch Chrome - -Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) installed. - -```php -$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); -``` - ##### You can also customize the desired capabilities ```php From 50be1fdd1d69119f7b6ad95f983a27b2b61f387b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Mar 2018 16:34:11 +0100 Subject: [PATCH 107/487] PHPDoc: WebDriverElement::getAttribute may return null --- lib/WebDriverElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebDriverElement.php b/lib/WebDriverElement.php index 2505368b3..7253cb238 100644 --- a/lib/WebDriverElement.php +++ b/lib/WebDriverElement.php @@ -38,7 +38,7 @@ public function click(); * Get the value of a the given attribute of the element. * * @param string $attribute_name The name of the attribute. - * @return string The value of the attribute. + * @return string|null The value of the attribute. */ public function getAttribute($attribute_name); From cb4696490461d95c80784eb8e45008a296663092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 13 Mar 2018 10:21:01 +0100 Subject: [PATCH 108/487] Remove useless parenthesis in WebDriverSelect (minor) --- lib/WebDriverSelect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebDriverSelect.php b/lib/WebDriverSelect.php index abe6b54fe..1dfbe268b 100644 --- a/lib/WebDriverSelect.php +++ b/lib/WebDriverSelect.php @@ -39,7 +39,7 @@ public function __construct(WebDriverElement $element) } $this->element = $element; $value = $element->getAttribute('multiple'); - $this->isMulti = ($value === 'true'); + $this->isMulti = $value === 'true'; } public function isMultiple() From cbcc53cd9c53a1e3ea91947fac8a730b7dd39e8f Mon Sep 17 00:00:00 2001 From: Antonin Rykalsky Date: Tue, 13 Mar 2018 15:07:32 +0100 Subject: [PATCH 109/487] Fix other http headers being not sent in case of adding Except header --- lib/Remote/HttpCommandExecutor.php | 18 +++++++++--------- tests/unit/Remote/HttpCommandExecutorTest.php | 12 +++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 6a795080c..f584324b7 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -26,6 +26,11 @@ */ class HttpCommandExecutor implements WebDriverCommandExecutor { + const DEFAULT_HTTP_HEADERS = [ + 'Content-Type: application/json;charset=UTF-8', + 'Accept: application/json', + ]; + /** * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#command-reference */ @@ -169,14 +174,7 @@ public function __construct($url, $http_proxy = null, $http_proxy_port = null) curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); - curl_setopt( - $this->curl, - CURLOPT_HTTPHEADER, - [ - 'Content-Type: application/json;charset=UTF-8', - 'Accept: application/json', - ] - ); + curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); $this->setRequestTimeout(30000); $this->setConnectionTimeout(30000); } @@ -266,7 +264,9 @@ public function execute(WebDriverCommand $command) if (in_array($http_method, ['POST', 'PUT'])) { // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy // https://tools.ietf.org/html/rfc7231#section-5.1.1 - curl_setopt($this->curl, CURLOPT_HTTPHEADER, ['Expect:']); + curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_merge(static::DEFAULT_HTTP_HEADERS, ['Expect:'])); + } else { + curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); } $encoded_params = null; diff --git a/tests/unit/Remote/HttpCommandExecutorTest.php b/tests/unit/Remote/HttpCommandExecutorTest.php index 5519fa0e4..d6aae04b2 100644 --- a/tests/unit/Remote/HttpCommandExecutorTest.php +++ b/tests/unit/Remote/HttpCommandExecutorTest.php @@ -53,11 +53,21 @@ public function testShouldSendRequestToAssembledUrl( if ($shouldResetExpectHeader) { $curlSetoptMock->expects($this->at(2)) - ->with($this->anything(), CURLOPT_HTTPHEADER, ['Expect:']); + ->with( + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json', 'Expect:'] + ); $curlSetoptMock->expects($this->at(3)) ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); } else { $curlSetoptMock->expects($this->at(2)) + ->with( + $this->anything(), + CURLOPT_HTTPHEADER, + ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json'] + ); + $curlSetoptMock->expects($this->at(3)) ->with($this->anything(), CURLOPT_POSTFIELDS, $expectedPostData); } From 8d368c65e6503aef11c545b1ec0cfb883b42b65d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 14 Mar 2018 22:44:11 +0100 Subject: [PATCH 110/487] Add more php-cs-fixer rules --- .php_cs.dist | 5 +++++ tests/unit/Remote/DesiredCapabilitiesTest.php | 7 +++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.php_cs.dist b/.php_cs.dist index d95f9d0fc..48aa29de7 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -12,6 +12,7 @@ return PhpCsFixer\Config::create() 'cast_spaces' => true, 'concat_space' => ['spacing' => 'one'], 'function_typehint_space' => true, + 'general_phpdoc_annotation_remove' => ['author'], 'linebreak_after_opening_tag' => true, 'lowercase_cast' => true, 'mb_str_functions' => true, @@ -50,6 +51,9 @@ return PhpCsFixer\Config::create() 'ordered_imports' => true, 'php_unit_construct' => true, 'php_unit_dedicate_assert' => true, + 'php_unit_expectation' => true, + 'php_unit_mock' => true, + 'php_unit_no_expectation_annotation' => true, 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, @@ -73,6 +77,7 @@ return PhpCsFixer\Config::create() 'unary_operator_spaces' => true, 'visibility_required' => true, 'whitespace_after_comma_in_array' => true, + 'yoda_style' => false, ]) ->setRiskyAllowed(true) ->setFinder($finder); diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index 7d39c3675..6e14ef044 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -62,12 +62,11 @@ public function testShouldProvideAccessToCapabilitiesUsingSettersAndGetters() $this->assertSame(333, $capabilities->getVersion()); } - /** - * @expectedException \Exception - * @expectedExceptionMessage isJavascriptEnabled() is a htmlunit-only option - */ public function testShouldNotAllowToDisableJavascriptForNonHtmlUnitBrowser() { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('isJavascriptEnabled() is a htmlunit-only option'); + $capabilities = new DesiredCapabilities(); $capabilities->setBrowserName(WebDriverBrowserType::FIREFOX); $capabilities->setJavascriptEnabled(false); From 316dd0b7c056694a056bfc4e5217896eec2d1fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 14 Mar 2018 23:32:35 +0100 Subject: [PATCH 111/487] Use udiff format in php-cs-fixer for more readable diffs --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5e5fce576..2a9929725 100644 --- a/composer.json +++ b/composer.json @@ -45,11 +45,11 @@ }, "scripts": { "codestyle:check": [ - "vendor/bin/php-cs-fixer fix --diff --dry-run", + "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff --dry-run -vvv --ansi", "vendor/bin/phpcs --standard=PSR2 ./lib/ ./tests/" ], "codestyle:fix": [ - "vendor/bin/php-cs-fixer fix --diff || exit 0", + "vendor/bin/php-cs-fixer fix --diff --diff-format=udiff -vvv || exit 0", "vendor/bin/phpcbf --standard=PSR2 ./lib/ ./tests/" ], "analyze": [ From 8a2df15749421bd6685b87c4574ad3c283d9d057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Mar 2018 10:28:07 +0100 Subject: [PATCH 112/487] Fix @see in WebDriverOptions::addCookie --- lib/WebDriverOptions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index 9791b9f1c..a610a9e6a 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -37,7 +37,7 @@ public function __construct(ExecuteMethod $executor) /** * Add a specific cookie. * - * @see Facebook\WebDriver\Cookie for description of possible cookie properties + * @see Cookie for description of possible cookie properties * @param Cookie|array $cookie Cookie object. May be also created from array for compatibility reasons. * @return WebDriverOptions The current instance. */ From c95385adff0858e21446be5b49474056c1c06164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 16 Mar 2018 15:54:52 +0100 Subject: [PATCH 113/487] Fix the PHPDoc's types of Cookie.php --- lib/Cookie.php | 10 +++++----- lib/WebDriverOptions.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Cookie.php b/lib/Cookie.php index 0434a128b..57ec7e13a 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -104,7 +104,7 @@ public function setPath($path) } /** - * @return string + * @return string|null */ public function getPath() { @@ -126,7 +126,7 @@ public function setDomain($domain) } /** - * @return string + * @return string|null */ public function getDomain() { @@ -144,7 +144,7 @@ public function setExpiry($expiry) } /** - * @return int + * @return int|null */ public function getExpiry() { @@ -162,7 +162,7 @@ public function setSecure($secure) } /** - * @return bool + * @return bool|null */ public function isSecure() { @@ -180,7 +180,7 @@ public function setHttpOnly($httpOnly) } /** - * @return bool + * @return bool|null */ public function isHttpOnly() { diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index a610a9e6a..bec7fd18b 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -90,7 +90,7 @@ public function deleteCookieNamed($name) * Get the cookie with a given name. * * @param string $name - * @return Cookie The cookie, or null if no cookie with the given name is presented. + * @return Cookie|null The cookie, or null if no cookie with the given name is presented. */ public function getCookieNamed($name) { From 933a114472777c72f781166ce4bccd29d61abe7b Mon Sep 17 00:00:00 2001 From: agentFinland Date: Tue, 10 Apr 2018 12:13:13 -0700 Subject: [PATCH 114/487] Clarified README (#557) * Clarified README - simplified some word choice for those new to the project and non-primary English speakers - corrected grammar - removed unnecessary words (clutter) - corrected sentence fragments - corrected improper verb choices - added extra headers to clarify purpose of each set of steps - rearranged some sentences so the focus of the sentence came earlier and/or became the subject --- README.md | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c0bd89d1f..b25bee453 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ by the Selenium server and will also implement the [W3C WebDriver](https://w3c.g The concepts of this library are very similar to the "official" Java, .NET, Python and Ruby bindings from the [Selenium project](https://github.com/SeleniumHQ/selenium/). -**This is new version of PHP client, rewritten from scratch starting 2013.** +**As of 2013, this PHP client has been rewritten from scratch.** Using the old version? Check out [Adam Goucher's fork](https://github.com/Element-34/php-webdriver) of it. Looking for API documentation of php-webdriver? See [https://facebook.github.io/php-webdriver/](https://facebook.github.io/php-webdriver/latest/) -Any complaint, question, idea? You can post it on the user group https://www.facebook.com/groups/phpwebdriver/. +Any complaints, questions, or ideas? Post them in the user group https://www.facebook.com/groups/phpwebdriver/. ## Installation @@ -37,15 +37,19 @@ Then install the library: ## Getting started -All you need as the server for this client is the `selenium-server-standalone-#.jar` file provided here: http://selenium-release.storage.googleapis.com/index.html +### Start Server -Download and run that file, replacing # with the current server version. Keep in mind you must have Java 8+ installed to start this command. +The required server is the `selenium-server-standalone-#.jar` file provided here: http://selenium-release.storage.googleapis.com/index.html + +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**. java -jar selenium-server-standalone-#.jar -(Please see note below when using Firefox.) +**NOTE:** If using Firefox, see alternate command below. + +### Create a Browser Session -Then when you create a session, be sure to pass the url to where your server is running. +When creating a browser session, be sure to pass the url of your running server. ```php // This would be the url of the host running the server-standalone.jar @@ -54,7 +58,7 @@ $host = 'http://localhost:4444/wd/hub'; // this is the default ##### Launch Chrome -Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) installed. +Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) versions installed. ```php $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); @@ -64,8 +68,8 @@ $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); Make sure to have latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases) installed. -Because Firefox (and Geckodriver) only supports new W3C WebDriver protocol (which is yet to be implemented by php-wedbriver - see [issue #469](https://github.com/facebook/php-webdriver/issues/469)), -the protocols must be translated by Selenium server - this feature is *partially* available in Selenium server version 3.5.0-3.8.1 and you can enable it like this: +Because Firefox (and Geckodriver) only support the new W3C WebDriver protocol (which is yet to be implemented by php-webdriver - see [issue #469](https://github.com/facebook/php-webdriver/issues/469)), +the protocols must be translated by Selenium Server - this feature is *partially* available in Selenium Server versions 3.5.0-3.8.1 and you can enable it like this: java -jar selenium-server-standalone-3.8.1.jar -enablePassThrough false @@ -75,7 +79,7 @@ Now you can start Firefox from your code: $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); ``` -##### You can also customize the desired capabilities +### Customize Desired Capabilities ```php $desired_capabilities = DesiredCapabilities::firefox(); @@ -85,7 +89,7 @@ $driver = RemoteWebDriver::create($host, $desired_capabilities); * See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities for more details. -* Above snippets are not intended to be a working example by simply copy pasting. See [example.php](example.php) for working example. +**NOTE:** Above snippets are not intended to be a working example by simply copy-pasting. See [example.php](example.php) for working example. ## Changelog For latest changes see [CHANGELOG.md](CHANGELOG.md) file. @@ -98,22 +102,21 @@ You may also want to check out the Selenium [docs](http://docs.seleniumhq.org/do ## Testing framework integration -To take advantage of automatized testing you will most probably want to integrate php-webdriver to your testing framework. -There are some project already providing this: +To take advantage of automatized testing you may want to integrate php-webdriver to your testing framework. +There are some projects already providing this: -- [Steward](https://github.com/lmc-eu/steward) integrates php-webdriver directly to [PHPUnit](https://phpunit.de/), also providers 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. +- [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 ## Support -We have a great community willing to try and help you! +We have a great community willing to help you! -- **Via our Facebook Group** - If you have questions or are an active contributor consider joining our [facebook group](https://www.facebook.com/groups/phpwebdriver/) and contributing to the communal discussion and support. -- **Via StackOverflow** - You can also [ask a question](https://stackoverflow.com/questions/ask?tags=php+selenium-webdriver) or find many already answered question on StackOverflow. -- **Via GitHub** - Another option if you have a question (or bug report) is to [submit it here](https://github.com/facebook/php-webdriver/issues/new) as an new issue. +- **Via our Facebook Group** - If you have questions or are an active contributor consider joining our [facebook group](https://www.facebook.com/groups/phpwebdriver/) and contribute to communal discussion and support +- **Via StackOverflow** - You can also [ask a question](https://stackoverflow.com/questions/ask?tags=php+selenium-webdriver) or find many already answered question on StackOverflow +- **Via GitHub** - Another option if you have a question (or bug report) is to [submit it here](https://github.com/facebook/php-webdriver/issues/new) as an new issue ## 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](CONTRIBUTING.md) for more information about contributing and developing php-webdriver. From f9a1a6ba4f57be77cc92122ae13df3dad6d3191e Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Wed, 16 May 2018 10:34:50 -0700 Subject: [PATCH 115/487] Update changelog for release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b76cce76d..0f3088da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +--- + +## 1.6.0 - 2018-05-16 ### Added - Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID. - Update PHPDoc for functions that return static instances of a class. From a1784b20ea64afdb0613e5164b3cb42773df4578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 28 May 2018 16:32:37 +0200 Subject: [PATCH 116/487] Update Selenium to 3.8.1 and Chromedriver to 2.38 --- .travis.yml | 6 +++--- tests/functional/RemoteWebDriverTest.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c9c5c1a3..4a2cada83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ env: global: - DISPLAY=:99.0 - BROWSER_NAME="htmlunit" - - CHROMEDRIVER_VERSION="2.35" + - CHROMEDRIVER_VERSION="2.38" matrix: include: @@ -93,8 +93,8 @@ before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - sh -e /etc/init.d/xvfb start - - if [ ! -f jar/selenium-server-standalone-3.4.0.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar; fi - - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.4.0.jar -log ./logs/selenium.log & + - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; fi + - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.8.1.jar -enablePassThrough false -log ./logs/selenium.log & - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - 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" diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 440776ad5..818a9da5a 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -84,7 +84,6 @@ public function testShouldGetAllSessions() $this->assertArrayHasKey('capabilities', $sessions[0]); $this->assertArrayHasKey('id', $sessions[0]); - $this->assertArrayHasKey('class', $sessions[0]); } /** From 6c885357678c692ee76d97d21ba03fbfbd19b46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 29 May 2018 15:03:45 +0200 Subject: [PATCH 117/487] Add utility classes to interact with checkboxes and radio buttons (#545) * Add an utility class to manipulate checkboxes and radio buttons --- lib/AbstractWebDriverCheckboxOrRadio.php | 248 ++++++++++++++++++ lib/WebDriverCheckboxes.php | 66 +++++ lib/WebDriverRadios.php | 65 +++++ tests/functional/WebDriverCheckboxesTest.php | 197 ++++++++++++++ tests/functional/WebDriverRadiosTest.php | 184 +++++++++++++ tests/functional/web/form_checkbox_radio.html | 43 +++ 6 files changed, 803 insertions(+) create mode 100644 lib/AbstractWebDriverCheckboxOrRadio.php create mode 100644 lib/WebDriverCheckboxes.php create mode 100644 lib/WebDriverRadios.php create mode 100644 tests/functional/WebDriverCheckboxesTest.php create mode 100644 tests/functional/WebDriverRadiosTest.php create mode 100644 tests/functional/web/form_checkbox_radio.html diff --git a/lib/AbstractWebDriverCheckboxOrRadio.php b/lib/AbstractWebDriverCheckboxOrRadio.php new file mode 100644 index 000000000..46288592b --- /dev/null +++ b/lib/AbstractWebDriverCheckboxOrRadio.php @@ -0,0 +1,248 @@ +getTagName(); + if ($tagName !== 'input') { + throw new UnexpectedTagNameException('input', $tagName); + } + + $this->name = $element->getAttribute('name'); + if ($this->name === null) { + throw new WebDriverException('The input does not have a "name" attribute.'); + } + + $this->element = $element; + } + + public function getOptions() + { + return $this->getRelatedElements(); + } + + public function getAllSelectedOptions() + { + $selectedElement = []; + foreach ($this->getRelatedElements() as $element) { + if ($element->isSelected()) { + $selectedElement[] = $element; + + if (!$this->isMultiple()) { + return $selectedElement; + } + } + } + + return $selectedElement; + } + + public function getFirstSelectedOption() + { + foreach ($this->getRelatedElements() as $element) { + if ($element->isSelected()) { + return $element; + } + } + + throw new NoSuchElementException( + sprintf('No %s are selected', 'radio' === $this->type ? 'radio buttons' : 'checkboxes') + ); + } + + public function selectByIndex($index) + { + $this->byIndex($index); + } + + public function selectByValue($value) + { + $this->byValue($value); + } + + public function selectByVisibleText($text) + { + $this->byVisibleText($text); + } + + public function selectByVisiblePartialText($text) + { + $this->byVisibleText($text, true); + } + + /** + * Selects or deselects a checkbox or a radio button by its value. + * + * @param string $value + * @param bool $select + * @throws NoSuchElementException + */ + protected function byValue($value, $select = true) + { + $matched = false; + foreach ($this->getRelatedElements($value) as $element) { + $select ? $this->selectOption($element) : $this->deselectOption($element); + if (!$this->isMultiple()) { + return; + } + + $matched = true; + } + + if (!$matched) { + throw new NoSuchElementException( + sprintf('Cannot locate %s with value: %s', $this->type, $value) + ); + } + } + + /** + * Selects or deselects a checkbox or a radio button by its index. + * + * @param int $index + * @param bool $select + * @throws NoSuchElementException + */ + protected function byIndex($index, $select = true) + { + $elements = $this->getRelatedElements(); + if (!isset($elements[$index])) { + throw new NoSuchElementException(sprintf('Cannot locate %s with index: %d', $this->type, $index)); + } + + $select ? $this->selectOption($elements[$index]) : $this->deselectOption($elements[$index]); + } + + /** + * Selects or deselects a checkbox or a radio button by its visible text. + * + * @param string $text + * @param bool $partial + * @param bool $select + */ + protected function byVisibleText($text, $partial = false, $select = true) + { + foreach ($this->getRelatedElements() as $element) { + $normalizeFilter = sprintf( + $partial ? 'contains(normalize-space(.), %s)' : 'normalize-space(.) = %s', + XPathEscaper::escapeQuotes($text) + ); + + $xpath = 'ancestor::label'; + $xpathNormalize = sprintf('%s[%s]', $xpath, $normalizeFilter); + + $id = $element->getAttribute('id'); + if ($id !== null) { + $idFilter = sprintf('@for = %s', XPathEscaper::escapeQuotes($id)); + + $xpath .= sprintf(' | //label[%s]', $idFilter); + $xpathNormalize .= sprintf(' | //label[%s and %s]', $idFilter, $normalizeFilter); + } + + try { + $element->findElement(WebDriverBy::xpath($xpathNormalize)); + } catch (NoSuchElementException $e) { + if ($partial) { + continue; + } + + try { + // Since the mechanism of getting the text in xpath is not the same as + // webdriver, use the expensive getText() to check if nothing is matched. + if ($text !== $element->findElement(WebDriverBy::xpath($xpath))->getText()) { + continue; + } + } catch (NoSuchElementException $e) { + continue; + } + } + + $select ? $this->selectOption($element) : $this->deselectOption($element); + if (!$this->isMultiple()) { + return; + } + } + } + + /** + * Gets checkboxes or radio buttons with the same name. + * + * @param string|null $value + * @return WebDriverElement[] + */ + protected function getRelatedElements($value = null) + { + $valueSelector = $value ? sprintf(' and @value = %s', XPathEscaper::escapeQuotes($value)) : ''; + $formId = $this->element->getAttribute('form'); + if ($formId === null) { + $form = $this->element->findElement(WebDriverBy::xpath('ancestor::form')); + + $formId = $form->getAttribute('id'); + if ($formId === '') { + return $form->findElements(WebDriverBy::xpath( + sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector) + )); + } + } + + return $this->element->findElements(WebDriverBy::xpath(sprintf( + '//form[@id = %1$s]//input[@name = %2$s%3$s] | //input[@form = %1$s and @name = %2$s%3$s]', + XPathEscaper::escapeQuotes($formId), + XPathEscaper::escapeQuotes($this->name), + $valueSelector + ))); + } + + /** + * Selects a checkbox or a radio button. + */ + protected function selectOption(WebDriverElement $element) + { + if (!$element->isSelected()) { + $element->click(); + } + } + + /** + * Deselects a checkbox or a radio button. + */ + protected function deselectOption(WebDriverElement $element) + { + if ($element->isSelected()) { + $element->click(); + } + } +} diff --git a/lib/WebDriverCheckboxes.php b/lib/WebDriverCheckboxes.php new file mode 100644 index 000000000..1ac00093d --- /dev/null +++ b/lib/WebDriverCheckboxes.php @@ -0,0 +1,66 @@ +type = $element->getAttribute('type'); + if ($this->type !== 'checkbox') { + throw new WebDriverException('The input must be of type "checkbox".'); + } + } + + public function isMultiple() + { + return true; + } + + public function deselectAll() + { + foreach ($this->getRelatedElements() as $checkbox) { + $this->deselectOption($checkbox); + } + } + + public function deselectByIndex($index) + { + $this->byIndex($index, false); + } + + public function deselectByValue($value) + { + $this->byValue($value, false); + } + + public function deselectByVisibleText($text) + { + $this->byVisibleText($text, false, false); + } + + public function deselectByVisiblePartialText($text) + { + $this->byVisibleText($text, true, false); + } +} diff --git a/lib/WebDriverRadios.php b/lib/WebDriverRadios.php new file mode 100644 index 000000000..d6a4d2ac7 --- /dev/null +++ b/lib/WebDriverRadios.php @@ -0,0 +1,65 @@ +type = $element->getAttribute('type'); + if ($this->type !== 'radio') { + throw new WebDriverException('The input must be of type "radio".'); + } + } + + public function isMultiple() + { + return false; + } + + public function deselectAll() + { + throw new UnsupportedOperationException('You cannot deselect radio buttons'); + } + + public function deselectByIndex($index) + { + throw new UnsupportedOperationException('You cannot deselect radio buttons'); + } + + public function deselectByValue($value) + { + throw new UnsupportedOperationException('You cannot deselect radio buttons'); + } + + public function deselectByVisibleText($text) + { + throw new UnsupportedOperationException('You cannot deselect radio buttons'); + } + + public function deselectByVisiblePartialText($text) + { + throw new UnsupportedOperationException('You cannot deselect radio buttons'); + } +} diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php new file mode 100644 index 000000000..3bf8ccffd --- /dev/null +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -0,0 +1,197 @@ +driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + } + + public function testIsMultiple() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $this->assertTrue($c->isMultiple()); + } + + public function testGetOptions() + { + $c = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//form[2]//input[@type="checkbox"]')) + ); + $this->assertNotEmpty($c->getOptions()); + } + + public function testGetFirstSelectedOption() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $c->selectByValue('j2a'); + $this->assertSame('j2a', $c->getFirstSelectedOption()->getAttribute('value')); + } + + public function testSelectByValue() + { + $selectedOptions = ['j2b', 'j2c']; + + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + foreach ($selectedOptions as $index => $selectedOption) { + $c->selectByValue($selectedOption); + } + + $selectedValues = []; + foreach ($c->getAllSelectedOptions() as $option) { + $selectedValues[] = $option->getAttribute('value'); + } + $this->assertSame($selectedOptions, $selectedValues); + } + + public function testSelectByValueInvalid() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate checkbox with value: notexist'); + $c->selectByValue('notexist'); + } + + public function testSelectByIndex() + { + $selectedOptions = [1 => 'j2b', 2 => 'j2c']; + + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + foreach ($selectedOptions as $index => $selectedOption) { + $c->selectByIndex($index); + } + + $selectedValues = []; + foreach ($c->getAllSelectedOptions() as $option) { + $selectedValues[] = $option->getAttribute('value'); + } + $this->assertSame(array_values($selectedOptions), $selectedValues); + } + + public function testSelectByIndexInvalid() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate checkbox with index: ' . PHP_INT_MAX); + $c->selectByIndex(PHP_INT_MAX); + } + + /** + * @dataProvider selectByVisibleTextDataProvider + * + * @param string $text + * @param string $value + */ + public function testSelectByVisibleText($text, $value) + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $c->selectByVisibleText($text); + $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + } + + /** + * @return array + */ + public function selectByVisibleTextDataProvider() + { + return [ + ['J 2 B', 'j2b'], + ['J2C', 'j2c'], + ]; + } + + /** + * @dataProvider selectByVisiblePartialTextDataProvider + * + * @param string $text + * @param string $value + */ + public function testSelectByVisiblePartialText($text, $value) + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $c->selectByVisiblePartialText($text); + $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + } + + /** + * @return array + */ + public function selectByVisiblePartialTextDataProvider() + { + return [ + ['2 B', 'j2b'], + ['2C', 'j2c'], + ]; + } + + public function testDeselectAll() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $c->selectByIndex(0); + $this->assertCount(1, $c->getAllSelectedOptions()); + $c->deselectAll(); + $this->assertEmpty($c->getAllSelectedOptions()); + } + + public function testDeselectByIndex() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $c->selectByIndex(0); + $this->assertCount(1, $c->getAllSelectedOptions()); + $c->deselectByIndex(0); + $this->assertEmpty($c->getAllSelectedOptions()); + } + + public function testDeselectByValue() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $c->selectByValue('j2a'); + $this->assertCount(1, $c->getAllSelectedOptions()); + $c->deselectByValue('j2a'); + $this->assertEmpty($c->getAllSelectedOptions()); + } + + public function testDeselectByVisibleText() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $c->selectByVisibleText('J 2 B'); + $this->assertCount(1, $c->getAllSelectedOptions()); + $c->deselectByVisibleText('J 2 B'); + $this->assertEmpty($c->getAllSelectedOptions()); + } + + public function testDeselectByVisiblePartialText() + { + $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + + $c->selectByVisiblePartialText('2C'); + $this->assertCount(1, $c->getAllSelectedOptions()); + $c->deselectByVisiblePartialText('2C'); + $this->assertEmpty($c->getAllSelectedOptions()); + } +} diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php new file mode 100644 index 000000000..f28417ac4 --- /dev/null +++ b/tests/functional/WebDriverRadiosTest.php @@ -0,0 +1,184 @@ +driver->get($this->getTestPageUrl('form_checkbox_radio.html')); + } + + public function testIsMultiple() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $this->assertFalse($c->isMultiple()); + } + + public function testGetOptions() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $values = []; + foreach ($c->getOptions() as $option) { + $values[] = $option->getAttribute('value'); + } + + $this->assertSame(['j3a', 'j3b', 'j3c'], $values); + } + + public function testGetFirstSelectedOption() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $c->selectByValue('j3a'); + $this->assertSame('j3a', $c->getFirstSelectedOption()->getAttribute('value')); + } + + public function testSelectByValue() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $c->selectByValue('j3b'); + + $selectedOptions = $c->getAllSelectedOptions(); + $this->assertCount(1, $selectedOptions); + $this->assertSame('j3b', $selectedOptions[0]->getAttribute('value')); + } + + public function testSelectByValueInvalid() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate radio with value: notexist'); + $c->selectByValue('notexist'); + } + + public function testSelectByIndex() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $c->selectByIndex(1); + + $allSelectedOptions = $c->getAllSelectedOptions(); + $this->assertCount(1, $allSelectedOptions); + $this->assertSame('j3b', $allSelectedOptions[0]->getAttribute('value')); + } + + public function testSelectByIndexInvalid() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(NoSuchElementException::class); + $this->expectExceptionMessage('Cannot locate radio with index: ' . PHP_INT_MAX); + $c->selectByIndex(PHP_INT_MAX); + } + + /** + * @dataProvider selectByVisibleTextDataProvider + * + * @param string $text + * @param string $value + */ + public function testSelectByVisibleText($text, $value) + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $c->selectByVisibleText($text); + $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + } + + /** + * @return array + */ + public function selectByVisibleTextDataProvider() + { + return [ + ['J 3 B', 'j3b'], + ['J3C', 'j3c'], + ]; + } + + /** + * @dataProvider selectByVisiblePartialTextDataProvider + * + * @param string $text + * @param string $value + */ + public function testSelectByVisiblePartialText($text, $value) + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $c->selectByVisiblePartialText($text); + $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + } + + /** + * @return array + */ + public function selectByVisiblePartialTextDataProvider() + { + return [ + ['3 B', 'j3b'], + ['3C', 'j3c'], + ]; + } + + public function testDeselectAllRadio() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You cannot deselect radio buttons'); + $c->deselectAll(); + } + + public function testDeselectByIndexRadio() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You cannot deselect radio buttons'); + $c->deselectByIndex(0); + } + + public function testDeselectByValueRadio() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You cannot deselect radio buttons'); + $c->deselectByValue('val'); + } + + public function testDeselectByVisibleTextRadio() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You cannot deselect radio buttons'); + $c->deselectByVisibleText('AB'); + } + + public function testDeselectByVisiblePartialTextRadio() + { + $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->expectException(UnsupportedOperationException::class); + $this->expectExceptionMessage('You cannot deselect radio buttons'); + $c->deselectByVisiblePartialText('AB'); + } +} diff --git a/tests/functional/web/form_checkbox_radio.html b/tests/functional/web/form_checkbox_radio.html new file mode 100644 index 000000000..b51dcfe47 --- /dev/null +++ b/tests/functional/web/form_checkbox_radio.html @@ -0,0 +1,43 @@ + + + + + Form + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + From ce44465b74b1707a7e76b4f70f7d2fee6014ce94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 29 May 2018 15:17:26 +0200 Subject: [PATCH 118/487] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3088da9..95a4c6c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased - ---- +### Added +- `WebDriverCheckboxes` and `WebDriverRadios` helper classes to simplify interaction with checkboxes and radio buttons. ## 1.6.0 - 2018-05-16 ### Added From 37c7b04a453d88d79e41e8ac4425603a04d17fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 16 Jun 2018 14:26:38 +0200 Subject: [PATCH 119/487] Fix the name of a env var in CONTRIBUTING.md (#583) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7b93736c..1a01c7adb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ test suite: ./vendor/bin/phpunit --testsuite functional The functional tests will be started in HtmlUnit headless browser by default. If you want to run them in eg. Firefox, -simply set the `BROWSER` environment variable: +simply set the `BROWSER_NAME` environment variable: ... export BROWSER_NAME="firefox" From be2985897851adca3e941d8c07c28512e24ee4a1 Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Thu, 18 Oct 2018 13:53:38 -0700 Subject: [PATCH 120/487] Renamed license file. --- LICENCE.md => LICENSE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LICENCE.md => LICENSE.md (100%) diff --git a/LICENCE.md b/LICENSE.md similarity index 100% rename from LICENCE.md rename to LICENSE.md From e020ae2811098d55cce705c66c52f1a4fbf2f9df Mon Sep 17 00:00:00 2001 From: Fosco Marotto Date: Thu, 18 Oct 2018 13:58:23 -0700 Subject: [PATCH 121/487] Added copyright headers to 2 codefiles --- example.php | 14 ++++++++++++++ tests/bootstrap.php | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/example.php b/example.php index 2a9d0b298..f1109ed54 100644 --- a/example.php +++ b/example.php @@ -1,4 +1,18 @@ Date: Thu, 18 Oct 2018 14:14:22 -0700 Subject: [PATCH 122/487] Added a few more copyright headers. --- lib/Exception/IndexOutOfBoundsException.php | 13 +++++++++++++ lib/Exception/NoCollectionException.php | 13 +++++++++++++ lib/Exception/NoStringException.php | 13 +++++++++++++ lib/WebDriverSelectInterface.php | 13 +++++++++++++ tests/functional/web/slow_pixel.png.php | 13 +++++++++++++ tests/functional/web/submit.php | 16 +++++++++++++++- tests/functional/web/upload.php | 16 +++++++++++++++- 7 files changed, 95 insertions(+), 2 deletions(-) diff --git a/lib/Exception/IndexOutOfBoundsException.php b/lib/Exception/IndexOutOfBoundsException.php index ad52d21e9..de166b61e 100644 --- a/lib/Exception/IndexOutOfBoundsException.php +++ b/lib/Exception/IndexOutOfBoundsException.php @@ -1,4 +1,17 @@ + diff --git a/tests/functional/web/upload.php b/tests/functional/web/upload.php index 91a61bbcf..0bac36c3a 100644 --- a/tests/functional/web/upload.php +++ b/tests/functional/web/upload.php @@ -1,4 +1,18 @@ - + From 799fc83fb9bb0f61bfcb725db336a955774c22c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 6 Jun 2019 18:10:13 +0200 Subject: [PATCH 123/487] Improve test stability on SauceLabs; simplify doubleClick test to make it temporarily, as this action is not part of W3C WebDriver --- .travis.yml | 2 +- tests/functional/RemoteWebDriverTest.php | 26 +++++++++++++++------- tests/functional/WebDriverActionsTest.php | 5 +---- tests/functional/WebDriverTestCase.php | 4 ++-- tests/functional/WebDriverTimeoutsTest.php | 4 ++-- tests/functional/web/delayed_element.html | 2 +- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a2cada83..a268f0b4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -58,7 +58,7 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.2 - env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="15.15063" PLATFORM="Windows 10" + env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="16.16299" 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" diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 818a9da5a..d996d304a 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -145,6 +145,7 @@ public function testShouldCloseWindow() /** * @covers ::executeScript + * @group exclude-saucelabs */ public function testShouldExecuteScriptAndDoNotBlockExecution() { @@ -153,17 +154,18 @@ public function testShouldExecuteScriptAndDoNotBlockExecution() $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); + $start = microtime(true); $this->driver->executeScript(' setTimeout( - function(){document.getElementById("id_test").innerHTML = "Text changed by script"}, - 500 + function(){document.getElementById("id_test").innerHTML = "Text changed by script";}, + 250 )'); + $end = microtime(true); - // Make sure the script don't block the test execution - $this->assertSame('Test by ID', $element->getText()); + $this->assertLessThan(250, $end - $start, 'executeScript() should not block execution'); - // If we wait, the script should be executed - usleep(1000000); // wait 1000 ms + // If we wait, the script should be executed and its value changed + usleep(300000); // wait 300 ms $this->assertSame('Text changed by script', $element->getText()); } @@ -180,6 +182,7 @@ public function testShouldExecuteAsyncScriptAndWaitUntilItIsFinished() $element = $this->driver->findElement(WebDriverBy::id('id_test')); $this->assertSame('Test by ID', $element->getText()); + $start = microtime(true); $this->driver->executeAsyncScript( 'var callback = arguments[arguments.length - 1]; setTimeout( @@ -190,6 +193,13 @@ function(){ 250 );' ); + $end = microtime(true); + + $this->assertGreaterThan( + 0.250, + $end - $start, + 'executeAsyncScript() should block execution until callback() is called' + ); // The result must be immediately available, as the executeAsyncScript should block the execution until the // callback is called. @@ -204,7 +214,7 @@ public function testShouldTakeScreenshot() if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Screenshots are not supported by HtmlUnit browser'); } @@ -227,7 +237,7 @@ public function testShouldSaveScreenshotToFile() if (!extension_loaded('gd')) { $this->markTestSkipped('GD extension must be enabled'); } - if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Screenshots are not supported by HtmlUnit browser'); } diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 188c76cd9..3df984810 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -122,10 +122,7 @@ public function testShouldDoubleClickOnElement() ->doubleClick($element) ->perform(); - $this->assertSame( - ['mouseover item-3', 'mousedown item-3', 'mouseup item-3', 'click item-3', 'dblclick item-3'], - $this->retrieveLoggedEvents() - ); + $this->assertContains('dblclick item-3', $this->retrieveLoggedEvents()); } /** diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index e9714a87c..5fe6a7e8a 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -44,7 +44,7 @@ protected function setUp() { $this->desiredCapabilities = new DesiredCapabilities(); - if ($this->isSauceLabsBuild()) { + if (static::isSauceLabsBuild()) { $this->setUpSauceLabs(); } else { if (getenv('BROWSER_NAME')) { @@ -87,7 +87,7 @@ protected function tearDown() /** * @return bool */ - public function isSauceLabsBuild() + public static function isSauceLabsBuild() { return getenv('SAUCELABS') ? true : false; } diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 69d143cc4..733be545c 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -42,7 +42,7 @@ public function testShouldGetDelayedElementWithImplicitWait() { $this->driver->get($this->getTestPageUrl('delayed_element.html')); - $this->driver->manage()->timeouts()->implicitlyWait(1); + $this->driver->manage()->timeouts()->implicitlyWait(2); $element = $this->driver->findElement(WebDriverBy::id('delayed')); $this->assertInstanceOf(RemoteWebElement::class, $element); @@ -54,7 +54,7 @@ public function testShouldGetDelayedElementWithImplicitWait() */ public function testShouldFailIfPageIsLoadingLongerThanPageLoadTimeout() { - if ($this->desiredCapabilities->getBrowserName() == WebDriverBrowserType::HTMLUNIT) { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { $this->markTestSkipped('Not supported by HtmlUnit browser'); } diff --git a/tests/functional/web/delayed_element.html b/tests/functional/web/delayed_element.html index b0f057a9a..4572cd342 100644 --- a/tests/functional/web/delayed_element.html +++ b/tests/functional/web/delayed_element.html @@ -12,7 +12,7 @@ setTimeout(function () { var wrapper = document.getElementById("wrapper"); wrapper.innerHTML = '
    Element appearing after 500ms
    '; - }, 500); + }, 1500); From 6d488eacd23db77822a748725a1c9dafa6d0221e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 8 Jun 2019 12:33:18 +0200 Subject: [PATCH 124/487] Force Chrome 74 on saucelabs, as 75 uses W3C protocol by default --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a268f0b4d..c97b22525 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - php: 7.2 - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="latest" PLATFORM="Windows 10" + env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" 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" From 5f7a311bde888283615e8b3767a92302c5e29515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 7 Jun 2019 17:29:22 +0200 Subject: [PATCH 125/487] Use PHP 7.3 as the main PHP version for travis builds --- .travis.yml | 55 +++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/.travis.yml b/.travis.yml index c97b22525..082a6efb8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,11 @@ sudo: false dist: trusty php: - - 5.6 - - 7.0 - - 7.1 - - 7.2 + - '5.6' + - '7.0' + - '7.1' + - '7.2' + - '7.3' env: global: @@ -16,30 +17,43 @@ env: matrix: include: + # Codestyle check build + - php: '7.3' + env: CHECK_CODESTYLE=1 + before_install: + - phpenv config-rm xdebug.ini + before_script: ~ + script: + - composer require phpstan/phpstan-shim # Not part of require-dev, because it won't install on PHP 5.6 + - composer analyze + - composer codestyle:check + after_script: ~ + after_success: ~ + + # Build with lowest possible dependencies on lowest possible PHP + - php: '5.6' + env: DEPENDENCIES="--prefer-lowest" + # Add build to run tests against Firefox inside Travis environment - - php: 7.2 + - php: '7.3' env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" # Add build to run tests against Chrome inside Travis environment - - php: 7.2 + - php: '7.3' env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" addons: chrome: stable - # Build with lowest possible dependencies - - php: 7.2 - env: DEPENDENCIES="--prefer-lowest" - # Chrome on Travis build with lowest possible dependencies - - php: 7.2 + - php: '7.3' env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" DEPENDENCIES="--prefer-lowest" addons: chrome: stable # Saucelabs builds - - php: 7.2 + - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -48,7 +62,7 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.2 + - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -57,7 +71,7 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: 7.2 + - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="16.16299" PLATFORM="Windows 10" before_script: - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & @@ -67,19 +81,6 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - # Codestyle check build - - php: 7.2 - env: CHECK_CODESTYLE=1 - before_install: - - phpenv config-rm xdebug.ini - before_script: ~ - script: - - composer require phpstan/phpstan-shim # Not part of require-dev, because it won't install on PHP 5.6 - - composer analyze - - composer codestyle:check - after_script: ~ - after_success: ~ - cache: directories: - $HOME/.composer/cache From 242fffeb4caf279a87f75a01b29a87e66a3e1a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 8 Jun 2019 15:02:51 +0200 Subject: [PATCH 126/487] Disable WebDriverSelectTest on saucelabs - it is too slow and has minimal value --- tests/functional/WebDriverCheckboxesTest.php | 4 ++++ tests/functional/WebDriverRadiosTest.php | 4 ++++ tests/functional/WebDriverSelectTest.php | 1 + 3 files changed, 9 insertions(+) diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index 3bf8ccffd..128d298d5 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -17,6 +17,10 @@ use Facebook\WebDriver\Exception\NoSuchElementException; +/** + * @covers \Facebook\WebDriver\WebDriverCheckboxes + * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + */ class WebDriverCheckboxesTest extends WebDriverTestCase { protected function setUp() diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index f28417ac4..2a1a9869d 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -18,6 +18,10 @@ use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnsupportedOperationException; +/** + * @covers \Facebook\WebDriver\WebDriverRadios + * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + */ class WebDriverRadiosTest extends WebDriverTestCase { protected function setUp() diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index 37ad416af..b25dd03af 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -20,6 +20,7 @@ use Facebook\WebDriver\Exception\UnsupportedOperationException; /** + * @group exclude-saucelabs * @covers \Facebook\WebDriver\WebDriverSelect * @covers \Facebook\WebDriver\Exception\UnexpectedTagNameException */ From 9be6cbbd8006a24fb180df75cb4b9796a2aad770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 8 Jun 2019 15:42:49 +0200 Subject: [PATCH 127/487] Remove mostly redundant second lowest-deps build --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 082a6efb8..b3ee784e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,12 +46,6 @@ matrix: addons: chrome: stable - # Chrome on Travis build with lowest possible dependencies - - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" DEPENDENCIES="--prefer-lowest" - addons: - chrome: stable - # Saucelabs builds - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" From 0f3933c41606fd076d79ff064bc0def2b774e67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 8 Jun 2019 22:50:56 +0200 Subject: [PATCH 128/487] Disable default W3C protocol in Chrome 75+ --- .travis.yml | 27 ++++++++++++++++++++++----- lib/Remote/HttpCommandExecutor.php | 5 +++++ lib/Remote/RemoteWebDriver.php | 14 ++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b3ee784e8..b6850baf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ env: global: - DISPLAY=:99.0 - BROWSER_NAME="htmlunit" - - CHROMEDRIVER_VERSION="2.38" matrix: include: @@ -34,15 +33,21 @@ matrix: - php: '5.6' env: DEPENDENCIES="--prefer-lowest" - # Add build to run tests against Firefox inside Travis environment + # Firefox inside Travis environment - php: '7.3' env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" - # Add build to run tests against Chrome inside Travis environment + # Stable Chrome + Chromedriver 74 inside Travis environment - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="74.0.3729.6" + addons: + chrome: stable + + # Stable Chrome + Chromedriver 75+ inside Travis environment + - php: '7.3' + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="75.0.3770.8" addons: chrome: stable @@ -56,8 +61,9 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= + - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" PLATFORM="Windows 10" + env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" PLATFORM="Windows 10" # 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" @@ -65,6 +71,17 @@ matrix: sauce_connect: true jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= + + - php: '7.3' + env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="75.0" 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: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= + - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="16.16299" PLATFORM="Windows 10" before_script: diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index f584324b7..1891fdb35 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -273,6 +273,11 @@ public function execute(WebDriverCommand $command) if ($http_method === 'POST' && $params && is_array($params)) { $encoded_params = json_encode($params); + } elseif ($http_method === 'POST' && $encoded_params === null) { + // Workaround for bug https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943 in Chrome 75. + // Chromedriver now erroneously does not allow POST body to be empty even for the JsonWire protocol. + // If the command POST is empty, here we send some dummy data as a workaround: + $encoded_params = json_encode(['_' => '_']); } curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params); diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 1487689ec..538ae282f 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -15,6 +15,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\WebDriver; @@ -102,6 +103,19 @@ public static function create( $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); + // Hotfix: W3C WebDriver protocol is not yet supported by php-webdriver, so we must force Chromedriver to + // not use the W3C protocol by default (which is what Chromedriver does starting with version 75). + if ($desired_capabilities->getBrowserName() === WebDriverBrowserType::CHROME) { + $currentChromeOptions = $desired_capabilities->getCapability(ChromeOptions::CAPABILITY); + $chromeOptions = !empty($currentChromeOptions) ? $currentChromeOptions : new ChromeOptions(); + + if (!isset($chromeOptions->toArray()['w3c'])) { + $chromeOptions->setExperimentalOption('w3c', false); + } + + $desired_capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); + } + $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); From 550edf5a815694f60654a0a82d0bbe818eeb1514 Mon Sep 17 00:00:00 2001 From: JorisVanEijden Date: Mon, 25 Feb 2019 12:38:04 +0100 Subject: [PATCH 129/487] Do not send null values in cookie array (fixes #626) --- lib/Cookie.php | 50 ++++++++++++++++++++------------------- tests/unit/CookieTest.php | 22 +++++++++++++++++ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/lib/Cookie.php b/lib/Cookie.php index 57ec7e13a..216ac3033 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -27,15 +27,7 @@ class Cookie implements \ArrayAccess { /** @var array */ - protected $cookie = [ - 'name' => null, - 'value' => null, - 'path' => null, - 'domain' => null, - 'expiry' => null, - 'secure' => null, - 'httpOnly' => null, - ]; + protected $cookie = []; /** * @param string $name The name of the cookie; may not be null or an empty string. @@ -51,11 +43,17 @@ public function __construct($name, $value) } /** - * @param array $cookieArray + * @param array $cookieArray The cookie fields; must contain name and value. * @return Cookie */ public static function createFromArray(array $cookieArray) { + if (!isset($cookieArray['name'])) { + throw new InvalidArgumentException('Cookie name should be set'); + } + if (!isset($cookieArray['value'])) { + throw new InvalidArgumentException('Cookie value should be set'); + } $cookie = new self($cookieArray['name'], $cookieArray['value']); if (isset($cookieArray['path'])) { @@ -82,7 +80,7 @@ public static function createFromArray(array $cookieArray) */ public function getName() { - return $this->cookie['name']; + return $this->offsetGet('name'); } /** @@ -90,7 +88,7 @@ public function getName() */ public function getValue() { - return $this->cookie['value']; + return $this->offsetGet('value'); } /** @@ -100,7 +98,7 @@ public function getValue() */ public function setPath($path) { - $this->cookie['path'] = $path; + $this->offsetSet('path', $path); } /** @@ -108,7 +106,7 @@ public function setPath($path) */ public function getPath() { - return $this->cookie['path']; + return $this->offsetGet('path'); } /** @@ -122,7 +120,7 @@ public function setDomain($domain) throw new InvalidArgumentException(sprintf('Cookie domain "%s" should not contain a port', $domain)); } - $this->cookie['domain'] = $domain; + $this->offsetSet('domain', $domain); } /** @@ -130,7 +128,7 @@ public function setDomain($domain) */ public function getDomain() { - return $this->cookie['domain']; + return $this->offsetGet('domain'); } /** @@ -140,7 +138,7 @@ public function getDomain() */ public function setExpiry($expiry) { - $this->cookie['expiry'] = (int) $expiry; + $this->offsetSet('expiry', (int) $expiry); } /** @@ -148,7 +146,7 @@ public function setExpiry($expiry) */ public function getExpiry() { - return $this->cookie['expiry']; + return $this->offsetGet('expiry'); } /** @@ -158,7 +156,7 @@ public function getExpiry() */ public function setSecure($secure) { - $this->cookie['secure'] = $secure; + $this->offsetSet('secure', $secure); } /** @@ -166,7 +164,7 @@ public function setSecure($secure) */ public function isSecure() { - return $this->cookie['secure']; + return $this->offsetGet('secure'); } /** @@ -176,7 +174,7 @@ public function isSecure() */ public function setHttpOnly($httpOnly) { - $this->cookie['httpOnly'] = $httpOnly; + $this->offsetSet('httpOnly', $httpOnly); } /** @@ -184,7 +182,7 @@ public function setHttpOnly($httpOnly) */ public function isHttpOnly() { - return $this->cookie['httpOnly']; + return $this->offsetGet('httpOnly'); } /** @@ -202,12 +200,16 @@ public function offsetExists($offset) public function offsetGet($offset) { - return $this->cookie[$offset]; + return $this->offsetExists($offset) ? $this->cookie[$offset] : null; } public function offsetSet($offset, $value) { - $this->cookie[$offset] = $value; + if ($value === null) { + unset($this->cookie[$offset]); + } else { + $this->cookie[$offset] = $value; + } } public function offsetUnset($offset) diff --git a/tests/unit/CookieTest.php b/tests/unit/CookieTest.php index 3a0679d2c..5fdb43038 100644 --- a/tests/unit/CookieTest.php +++ b/tests/unit/CookieTest.php @@ -62,6 +62,28 @@ public function testShouldBeConvertibleToArray(Cookie $cookie) ); } + /** + * Test that there are no null values in the cookie array. + * + * Both JsonWireProtocol and w3c protocol say to leave an entry off + * rather than having a null value. + * + * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol + * https://w3c.github.io/webdriver/#add-cookie + */ + public function testShouldNotContainNullValues() + { + $cookie = new Cookie('cookieName', 'someValue'); + + $cookie->setHttpOnly(null); + $cookie->setPath(null); + $cookieArray = $cookie->toArray(); + + foreach ($cookieArray as $key => $value) { + $this->assertNotNull($value, $key . ' should not be null'); + } + } + /** * @depends testShouldSetAllProperties * @param Cookie $cookie From d1ad0b14b4f34f3d3eca10c1587b991f612e6746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Jun 2019 13:28:48 +0200 Subject: [PATCH 130/487] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a4c6c73..e27bd1b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Added - `WebDriverCheckboxes` and `WebDriverRadios` helper classes to simplify interaction with checkboxes and radio buttons. +### Fixed +- Stop sending null values in Cookie object, which is against the protocol and may cause request to remote ends to fail. + +### Changed +- Force Chrome to not use W3C WebDriver protocol. +- Add workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943) which breaks the protocol in Chromedriver 75. + ## 1.6.0 - 2018-05-16 ### Added - Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID. From 12ac107aba8af2cc08c549884f5a5844899509e9 Mon Sep 17 00:00:00 2001 From: Oleg Andreyev Date: Mon, 31 Dec 2018 02:23:12 +0200 Subject: [PATCH 131/487] Improved xpath matching related elements to handle input associated to different
    Added @form attribute check, because it's possible to put into a form which is not related to it, see MDN https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form --- lib/AbstractWebDriverCheckboxOrRadio.php | 17 +++++++++++------ tests/functional/WebDriverCheckboxesTest.php | 6 ++++++ tests/functional/WebDriverRadiosTest.php | 6 ++++++ tests/functional/web/form_checkbox_radio.html | 14 +++++++++++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/AbstractWebDriverCheckboxOrRadio.php b/lib/AbstractWebDriverCheckboxOrRadio.php index 46288592b..f70a2ef04 100644 --- a/lib/AbstractWebDriverCheckboxOrRadio.php +++ b/lib/AbstractWebDriverCheckboxOrRadio.php @@ -218,12 +218,17 @@ protected function getRelatedElements($value = null) } } - return $this->element->findElements(WebDriverBy::xpath(sprintf( - '//form[@id = %1$s]//input[@name = %2$s%3$s] | //input[@form = %1$s and @name = %2$s%3$s]', - XPathEscaper::escapeQuotes($formId), - XPathEscaper::escapeQuotes($this->name), - $valueSelector - ))); + // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form + return $this->element->findElements( + WebDriverBy::xpath(sprintf( + '//form[@id = %1$s]//input[@name = %2$s%3$s' + . ' and ((boolean(@form) = true() and @form = %1$s) or boolean(@form) = false())]' + . ' | //input[@form = %1$s and @name = %2$s%3$s]', + XPathEscaper::escapeQuotes($formId), + XPathEscaper::escapeQuotes($this->name), + $valueSelector + )) + ); } /** diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index 128d298d5..f7e1407b6 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -51,6 +51,12 @@ public function testGetFirstSelectedOption() $this->assertSame('j2a', $c->getFirstSelectedOption()->getAttribute('value')); } + public function testGetFirstSelectedOptionWithSameNameDifferentForm() + { + $radio = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@id="j5b"]'))); + $this->assertEquals('j5b', $radio->getFirstSelectedOption()->getAttribute('value')); + } + public function testSelectByValue() { $selectedOptions = ['j2b', 'j2c']; diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index 2a1a9869d..369717eb2 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -55,6 +55,12 @@ public function testGetFirstSelectedOption() $this->assertSame('j3a', $c->getFirstSelectedOption()->getAttribute('value')); } + public function testGetFirstSelectedOptionWithSameNameDifferentForm() + { + $radio = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); + $this->assertEquals('j4b', $radio->getFirstSelectedOption()->getAttribute('value')); + } + public function testSelectByValue() { $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); diff --git a/tests/functional/web/form_checkbox_radio.html b/tests/functional/web/form_checkbox_radio.html index b51dcfe47..1e13d74e4 100644 --- a/tests/functional/web/form_checkbox_radio.html +++ b/tests/functional/web/form_checkbox_radio.html @@ -27,6 +27,12 @@ + + + + + +
    @@ -36,7 +42,13 @@ -
    + + + + + + +
    From 87f9d0f4dbaf8ecf7c87415cccd3f00ea9e0311c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Jun 2019 13:06:00 +0200 Subject: [PATCH 132/487] Improve readability of tests by structuring theme more in arrange-act-assert way --- tests/functional/WebDriverCheckboxesTest.php | 139 +++++++++++------- tests/functional/WebDriverRadiosTest.php | 73 ++++----- tests/functional/web/form_checkbox_radio.html | 18 ++- 3 files changed, 137 insertions(+), 93 deletions(-) diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index f7e1407b6..a37fde342 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -32,42 +32,55 @@ protected function setUp() public function testIsMultiple() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); - $this->assertTrue($c->isMultiple()); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); + + $this->assertTrue($checkboxes->isMultiple()); } public function testGetOptions() { - $c = new WebDriverCheckboxes( + $checkboxes = new WebDriverCheckboxes( $this->driver->findElement(WebDriverBy::xpath('//form[2]//input[@type="checkbox"]')) ); - $this->assertNotEmpty($c->getOptions()); + + $this->assertNotEmpty($checkboxes->getOptions()); } public function testGetFirstSelectedOption() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); - $c->selectByValue('j2a'); - $this->assertSame('j2a', $c->getFirstSelectedOption()->getAttribute('value')); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); + + $checkboxes->selectByValue('j2a'); + + $this->assertSame('j2a', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } - public function testGetFirstSelectedOptionWithSameNameDifferentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() { - $radio = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@id="j5b"]'))); - $this->assertEquals('j5b', $radio->getFirstSelectedOption()->getAttribute('value')); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5b"]')) + ); + + $this->assertEquals('j5b', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } public function testSelectByValue() { $selectedOptions = ['j2b', 'j2c']; - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); foreach ($selectedOptions as $index => $selectedOption) { - $c->selectByValue($selectedOption); + $checkboxes->selectByValue($selectedOption); } $selectedValues = []; - foreach ($c->getAllSelectedOptions() as $option) { + foreach ($checkboxes->getAllSelectedOptions() as $option) { $selectedValues[] = $option->getAttribute('value'); } $this->assertSame($selectedOptions, $selectedValues); @@ -75,24 +88,28 @@ public function testSelectByValue() public function testSelectByValueInvalid() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); $this->expectException(NoSuchElementException::class); $this->expectExceptionMessage('Cannot locate checkbox with value: notexist'); - $c->selectByValue('notexist'); + $checkboxes->selectByValue('notexist'); } public function testSelectByIndex() { $selectedOptions = [1 => 'j2b', 2 => 'j2c']; - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); foreach ($selectedOptions as $index => $selectedOption) { - $c->selectByIndex($index); + $checkboxes->selectByIndex($index); } $selectedValues = []; - foreach ($c->getAllSelectedOptions() as $option) { + foreach ($checkboxes->getAllSelectedOptions() as $option) { $selectedValues[] = $option->getAttribute('value'); } $this->assertSame(array_values($selectedOptions), $selectedValues); @@ -100,11 +117,13 @@ public function testSelectByIndex() public function testSelectByIndexInvalid() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); $this->expectException(NoSuchElementException::class); $this->expectExceptionMessage('Cannot locate checkbox with index: ' . PHP_INT_MAX); - $c->selectByIndex(PHP_INT_MAX); + $checkboxes->selectByIndex(PHP_INT_MAX); } /** @@ -115,9 +134,13 @@ public function testSelectByIndexInvalid() */ public function testSelectByVisibleText($text, $value) { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); - $c->selectByVisibleText($text); - $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); + + $checkboxes->selectByVisibleText($text); + + $this->assertSame($value, $checkboxes->getFirstSelectedOption()->getAttribute('value')); } /** @@ -139,9 +162,13 @@ public function selectByVisibleTextDataProvider() */ public function testSelectByVisiblePartialText($text, $value) { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); - $c->selectByVisiblePartialText($text); - $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); + + $checkboxes->selectByVisiblePartialText($text); + + $this->assertSame($value, $checkboxes->getFirstSelectedOption()->getAttribute('value')); } /** @@ -157,51 +184,61 @@ public function selectByVisiblePartialTextDataProvider() public function testDeselectAll() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); - $c->selectByIndex(0); - $this->assertCount(1, $c->getAllSelectedOptions()); - $c->deselectAll(); - $this->assertEmpty($c->getAllSelectedOptions()); + $checkboxes->selectByIndex(0); + $this->assertCount(1, $checkboxes->getAllSelectedOptions()); + $checkboxes->deselectAll(); + $this->assertEmpty($checkboxes->getAllSelectedOptions()); } public function testDeselectByIndex() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); - $c->selectByIndex(0); - $this->assertCount(1, $c->getAllSelectedOptions()); - $c->deselectByIndex(0); - $this->assertEmpty($c->getAllSelectedOptions()); + $checkboxes->selectByIndex(0); + $this->assertCount(1, $checkboxes->getAllSelectedOptions()); + $checkboxes->deselectByIndex(0); + $this->assertEmpty($checkboxes->getAllSelectedOptions()); } public function testDeselectByValue() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); - $c->selectByValue('j2a'); - $this->assertCount(1, $c->getAllSelectedOptions()); - $c->deselectByValue('j2a'); - $this->assertEmpty($c->getAllSelectedOptions()); + $checkboxes->selectByValue('j2a'); + $this->assertCount(1, $checkboxes->getAllSelectedOptions()); + $checkboxes->deselectByValue('j2a'); + $this->assertEmpty($checkboxes->getAllSelectedOptions()); } public function testDeselectByVisibleText() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); - $c->selectByVisibleText('J 2 B'); - $this->assertCount(1, $c->getAllSelectedOptions()); - $c->deselectByVisibleText('J 2 B'); - $this->assertEmpty($c->getAllSelectedOptions()); + $checkboxes->selectByVisibleText('J 2 B'); + $this->assertCount(1, $checkboxes->getAllSelectedOptions()); + $checkboxes->deselectByVisibleText('J 2 B'); + $this->assertEmpty($checkboxes->getAllSelectedOptions()); } public function testDeselectByVisiblePartialText() { - $c = new WebDriverCheckboxes($this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]'))); + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@type="checkbox"]')) + ); - $c->selectByVisiblePartialText('2C'); - $this->assertCount(1, $c->getAllSelectedOptions()); - $c->deselectByVisiblePartialText('2C'); - $this->assertEmpty($c->getAllSelectedOptions()); + $checkboxes->selectByVisiblePartialText('2C'); + $this->assertCount(1, $checkboxes->getAllSelectedOptions()); + $checkboxes->deselectByVisiblePartialText('2C'); + $this->assertEmpty($checkboxes->getAllSelectedOptions()); } } diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index 369717eb2..2350a401d 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -33,15 +33,16 @@ protected function setUp() public function testIsMultiple() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $this->assertFalse($c->isMultiple()); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $this->assertFalse($radios->isMultiple()); } public function testGetOptions() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $values = []; - foreach ($c->getOptions() as $option) { + foreach ($radios->getOptions() as $option) { $values[] = $option->getAttribute('value'); } @@ -50,53 +51,57 @@ public function testGetOptions() public function testGetFirstSelectedOption() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $c->selectByValue('j3a'); - $this->assertSame('j3a', $c->getFirstSelectedOption()->getAttribute('value')); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + + $radios->selectByValue('j3a'); + + $this->assertSame('j3a', $radios->getFirstSelectedOption()->getAttribute('value')); } - public function testGetFirstSelectedOptionWithSameNameDifferentForm() + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() { $radio = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); + $this->assertEquals('j4b', $radio->getFirstSelectedOption()->getAttribute('value')); } public function testSelectByValue() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $c->selectByValue('j3b'); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios->selectByValue('j3b'); + + $selectedOptions = $radios->getAllSelectedOptions(); - $selectedOptions = $c->getAllSelectedOptions(); $this->assertCount(1, $selectedOptions); $this->assertSame('j3b', $selectedOptions[0]->getAttribute('value')); } public function testSelectByValueInvalid() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(NoSuchElementException::class); $this->expectExceptionMessage('Cannot locate radio with value: notexist'); - $c->selectByValue('notexist'); + $radios->selectByValue('notexist'); } public function testSelectByIndex() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $c->selectByIndex(1); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios->selectByIndex(1); - $allSelectedOptions = $c->getAllSelectedOptions(); + $allSelectedOptions = $radios->getAllSelectedOptions(); $this->assertCount(1, $allSelectedOptions); $this->assertSame('j3b', $allSelectedOptions[0]->getAttribute('value')); } public function testSelectByIndexInvalid() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(NoSuchElementException::class); $this->expectExceptionMessage('Cannot locate radio with index: ' . PHP_INT_MAX); - $c->selectByIndex(PHP_INT_MAX); + $radios->selectByIndex(PHP_INT_MAX); } /** @@ -107,9 +112,9 @@ public function testSelectByIndexInvalid() */ public function testSelectByVisibleText($text, $value) { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $c->selectByVisibleText($text); - $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios->selectByVisibleText($text); + $this->assertSame($value, $radios->getFirstSelectedOption()->getAttribute('value')); } /** @@ -131,9 +136,9 @@ public function selectByVisibleTextDataProvider() */ public function testSelectByVisiblePartialText($text, $value) { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); - $c->selectByVisiblePartialText($text); - $this->assertSame($value, $c->getFirstSelectedOption()->getAttribute('value')); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios->selectByVisiblePartialText($text); + $this->assertSame($value, $radios->getFirstSelectedOption()->getAttribute('value')); } /** @@ -149,46 +154,46 @@ public function selectByVisiblePartialTextDataProvider() public function testDeselectAllRadio() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('You cannot deselect radio buttons'); - $c->deselectAll(); + $radios->deselectAll(); } public function testDeselectByIndexRadio() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('You cannot deselect radio buttons'); - $c->deselectByIndex(0); + $radios->deselectByIndex(0); } public function testDeselectByValueRadio() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('You cannot deselect radio buttons'); - $c->deselectByValue('val'); + $radios->deselectByValue('val'); } public function testDeselectByVisibleTextRadio() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('You cannot deselect radio buttons'); - $c->deselectByVisibleText('AB'); + $radios->deselectByVisibleText('AB'); } public function testDeselectByVisiblePartialTextRadio() { - $c = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@type="radio"]'))); $this->expectException(UnsupportedOperationException::class); $this->expectExceptionMessage('You cannot deselect radio buttons'); - $c->deselectByVisiblePartialText('AB'); + $radios->deselectByVisiblePartialText('AB'); } } diff --git a/tests/functional/web/form_checkbox_radio.html b/tests/functional/web/form_checkbox_radio.html index 1e13d74e4..3e7c2a19a 100644 --- a/tests/functional/web/form_checkbox_radio.html +++ b/tests/functional/web/form_checkbox_radio.html @@ -5,7 +5,7 @@ Form -
    + @@ -27,28 +27,30 @@ + - + - +
    + - + - + - + - + -
    +
    From e8accbdea92c617e0bfea7bcd5aea522e0c19c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Jun 2019 13:20:45 +0200 Subject: [PATCH 133/487] Extend testcases to cover also form without id --- tests/functional/WebDriverCheckboxesTest.php | 9 +++++++++ tests/functional/WebDriverRadiosTest.php | 13 +++++++++++-- tests/functional/web/form_checkbox_radio.html | 12 ++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index a37fde342..48b72a53c 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -68,6 +68,15 @@ public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociate $this->assertEquals('j5b', $checkboxes->getFirstSelectedOption()->getAttribute('value')); } + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + { + $checkboxes = new WebDriverCheckboxes( + $this->driver->findElement(WebDriverBy::xpath('//input[@id="j5d"]')) + ); + + $this->assertEquals('j5c', $checkboxes->getFirstSelectedOption()->getAttribute('value')); + } + public function testSelectByValue() { $selectedOptions = ['j2b', 'j2c']; diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index 2350a401d..4d659772b 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -60,9 +60,18 @@ public function testGetFirstSelectedOption() public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentForm() { - $radio = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); + $radios = new WebDriverRadios($this->driver->findElement(WebDriverBy::xpath('//input[@id="j4b"]'))); - $this->assertEquals('j4b', $radio->getFirstSelectedOption()->getAttribute('value')); + $this->assertEquals('j4b', $radios->getFirstSelectedOption()->getAttribute('value')); + } + + public function testShouldGetFirstSelectedOptionConsideringOnlyElementsAssociatedWithCurrentFormWithoutId() + { + $radios = new WebDriverRadios( + $this->driver->findElement(WebDriverBy::xpath('//input[@id="j4c"]')) + ); + + $this->assertEquals('j4c', $radios->getFirstSelectedOption()->getAttribute('value')); } public function testSelectByValue() diff --git a/tests/functional/web/form_checkbox_radio.html b/tests/functional/web/form_checkbox_radio.html index 3e7c2a19a..fe59896d9 100644 --- a/tests/functional/web/form_checkbox_radio.html +++ b/tests/functional/web/form_checkbox_radio.html @@ -53,5 +53,17 @@
    + + +
    + + + + + + + +
    + From 9a52b1ac036743c31bc127fd7f2f4710e2bc968a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 10 Jun 2019 16:32:25 +0200 Subject: [PATCH 134/487] Release version 1.7.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e27bd1b89..9b5487916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased + +## 1.7.0 - 2019-06-10 ### Added - `WebDriverCheckboxes` and `WebDriverRadios` helper classes to simplify interaction with checkboxes and radio buttons. From ec7ce5b611e3a73eb789c783760487c89b4999a1 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Tue, 11 Jun 2019 11:47:33 +0200 Subject: [PATCH 135/487] Do not fail if ChromeOptions is already an array DesiredCapabilities::toArray() is a mutable operation which can lead to undesirable side effect of converting ChromeOptions to an array. --- lib/Remote/DesiredCapabilities.php | 1 + lib/Remote/RemoteWebDriver.php | 4 +++- tests/functional/RemoteWebDriverCreateTest.php | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 0e1a1ab25..533c8873a 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -156,6 +156,7 @@ public function setJavascriptEnabled($enabled) } /** + * @todo Remove side-effects - not change ie. ChromeOptions::CAPABILITY from instance of ChromeOptions to an array * @return array */ public function toArray() diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 538ae282f..af729f16e 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -109,8 +109,10 @@ public static function create( $currentChromeOptions = $desired_capabilities->getCapability(ChromeOptions::CAPABILITY); $chromeOptions = !empty($currentChromeOptions) ? $currentChromeOptions : new ChromeOptions(); - if (!isset($chromeOptions->toArray()['w3c'])) { + if ($chromeOptions instanceof ChromeOptions && !isset($chromeOptions->toArray()['w3c'])) { $chromeOptions->setExperimentalOption('w3c', false); + } elseif (is_array($chromeOptions) && !isset($chromeOptions['w3c'])) { + $chromeOptions['w3c'] = false; } $desired_capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index f9d7e809e..90c072922 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -49,6 +49,19 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->assertSame($this->desiredCapabilities->getBrowserName(), $returnedCapabilities->getBrowserName()); } + public function testShouldAcceprCapabilitiesAsAnArray() + { + // Method has a side-effect of converting whole content of desiredCapabilities to an array + $this->desiredCapabilities->toArray(); + + $this->driver = RemoteWebDriver::create( + $this->serverUrl, + $this->desiredCapabilities, + $this->connectionTimeout, + $this->requestTimeout + ); + } + public function testShouldCreateWebDriverWithRequiredCapabilities() { $requiredCapabilities = new DesiredCapabilities(); From f650ee73645a3e95fb0567b25d4f33c13664cbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Wed, 12 Jun 2019 11:32:08 +0200 Subject: [PATCH 136/487] Do not send w3c capability as a workaround for browsestack schema validation (fixes #644) --- lib/Remote/RemoteWebDriver.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index af729f16e..71f45704e 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -105,7 +105,9 @@ public static function create( // Hotfix: W3C WebDriver protocol is not yet supported by php-webdriver, so we must force Chromedriver to // not use the W3C protocol by default (which is what Chromedriver does starting with version 75). - if ($desired_capabilities->getBrowserName() === WebDriverBrowserType::CHROME) { + if ($desired_capabilities->getBrowserName() === WebDriverBrowserType::CHROME + && mb_strpos($selenium_server_url, 'browserstack') === false // see https://github.com/facebook/php-webdriver/issues/644 + ) { $currentChromeOptions = $desired_capabilities->getCapability(ChromeOptions::CAPABILITY); $chromeOptions = !empty($currentChromeOptions) ? $currentChromeOptions : new ChromeOptions(); From e43de70f3c7166169d0f14a374505392734160e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 13 Jun 2019 10:02:18 +0200 Subject: [PATCH 137/487] Release version 1.7.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5487916..a5e4ca748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## 1.7.1 - 2019-06-13 +### Fixed +- Error `Call to a member function toArray()` if capabilities were already converted to an array. +- Temporarily do not send capabilities to disable W3C WebDriver protocol when BrowserStack hub is used. + ## 1.7.0 - 2019-06-10 ### Added - `WebDriverCheckboxes` and `WebDriverRadios` helper classes to simplify interaction with checkboxes and radio buttons. From 3f73de8364e7285115750e7e47b9a27e08c730d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 14 Jun 2019 11:33:44 +0200 Subject: [PATCH 138/487] Revert Chromedriver bug workaround, which is fixed in Chromedriver 75.0.3770.90 --- .travis.yml | 2 +- CHANGELOG.md | 3 +++ lib/Remote/HttpCommandExecutor.php | 5 ----- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index b6850baf1..ac6cc1e24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ matrix: # Stable Chrome + Chromedriver 75+ inside Travis environment - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="75.0.3770.8" + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="75.0.3770.90" addons: chrome: stable diff --git a/CHANGELOG.md b/CHANGELOG.md index a5e4ca748..f1e733516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Changed +- Revert no longer needed workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943). + ## 1.7.1 - 2019-06-13 ### Fixed - Error `Call to a member function toArray()` if capabilities were already converted to an array. diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 1891fdb35..f584324b7 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -273,11 +273,6 @@ public function execute(WebDriverCommand $command) if ($http_method === 'POST' && $params && is_array($params)) { $encoded_params = json_encode($params); - } elseif ($http_method === 'POST' && $encoded_params === null) { - // Workaround for bug https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943 in Chrome 75. - // Chromedriver now erroneously does not allow POST body to be empty even for the JsonWire protocol. - // If the command POST is empty, here we send some dummy data as a workaround: - $encoded_params = json_encode(['_' => '_']); } curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params); From 4f8b32289daa41c9c96b6a9dbfcb352a24e5af1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 1 Nov 2019 19:16:13 +0000 Subject: [PATCH 139/487] Download latest chromedriver without a need of manual version bumping --- .travis.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index ac6cc1e24..37dfc2cf1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,15 +39,9 @@ matrix: addons: firefox: "45.8.0esr" - # Stable Chrome + Chromedriver 74 inside Travis environment + # Stable Chrome + Chromedriver inside Travis environment - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="74.0.3729.6" - addons: - chrome: stable - - # Stable Chrome + Chromedriver 75+ inside Travis environment - - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" CHROMEDRIVER_VERSION="75.0.3770.90" + env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" addons: chrome: stable @@ -102,7 +96,7 @@ install: - travis_retry composer update --no-interaction $DEPENDENCIES before_script: - - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi + - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROMEDRIVER_VERSION=$(wget -qO- "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; fi From 87be63361d6670b8110b7d084887746b1adc685b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 9 Apr 2018 16:25:57 +0200 Subject: [PATCH 140/487] W3C-compatible implementation --- .travis.yml | 12 +- CONTRIBUTING.md | 10 +- lib/AbstractWebDriverCheckboxOrRadio.php | 2 +- lib/Cookie.php | 8 +- lib/Exception/WebDriverException.php | 50 ++++- lib/Interactions/WebDriverActions.php | 1 - lib/Remote/DriverCommand.php | 4 + lib/Remote/HttpCommandExecutor.php | 63 ++++++- lib/Remote/JsonWireCompat.php | 102 ++++++++++ lib/Remote/RemoteMouse.php | 174 +++++++++++++++++- lib/Remote/RemoteTargetLocator.php | 4 +- lib/Remote/RemoteWebDriver.php | 82 ++++++--- lib/Remote/RemoteWebElement.php | 112 ++++++++--- lib/WebDriverDimension.php | 12 +- lib/WebDriverOptions.php | 9 +- lib/WebDriverPoint.php | 4 +- lib/WebDriverTimeouts.php | 34 +++- .../functional/RemoteWebDriverCreateTest.php | 5 +- .../RemoteWebDriverFindElementTest.php | 20 ++ tests/functional/RemoteWebDriverTest.php | 23 ++- tests/functional/RemoteWebElementTest.php | 4 + tests/functional/WebDriverActionsTest.php | 24 ++- tests/functional/WebDriverTestCase.php | 7 +- tests/functional/web/escape_css.html | 14 ++ tests/functional/web/upload.html | 2 +- 25 files changed, 699 insertions(+), 83 deletions(-) create mode 100644 lib/Remote/JsonWireCompat.php create mode 100644 tests/functional/web/escape_css.html diff --git a/.travis.yml b/.travis.yml index 37dfc2cf1..47ddb461e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,11 +34,21 @@ matrix: env: DEPENDENCIES="--prefer-lowest" # Firefox inside Travis environment - - php: '7.3' + - name: 'Firefox 45 on Travis (OSS protocol)' + php: '7.3' env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" + # Firefox with Geckodriver (W3C mode) inside Travis environment + - name: 'Firefox latest on Travis (W3C protocol)' + php: 7.3 + env: + - BROWSER_NAME="firefox" + - GECKODRIVER="1" + addons: + firefox: latest + # Stable Chrome + Chromedriver inside Travis environment - php: '7.3' env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a01c7adb..741a140a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,10 +37,10 @@ For the functional tests you must first [download](http://selenium-release.stora the selenium standalone server, start the local PHP server which will serve the test pages and then run the `functional` test suite: - java -jar selenium-server-standalone-2.53.1.jar -log selenium.log & + java -jar selenium-server-standalone-3.9.1.jar -log selenium.log & php -S localhost:8000 -t tests/functional/web/ & ./vendor/bin/phpunit --testsuite functional - + The functional tests will be started in HtmlUnit headless browser by default. If you want to run them in eg. Firefox, simply set the `BROWSER_NAME` environment variable: @@ -48,6 +48,12 @@ simply set the `BROWSER_NAME` environment variable: export BROWSER_NAME="firefox" ./vendor/bin/phpunit --testsuite functional +To test with Geckodriver, [download](https://github.com/mozilla/geckodriver/releases) and start the server, then run: + + export GECKODRIVER=1 + export BROWSER_NAME=firefox + ./vendor/bin/phpunit --testsuite functional + ### Check coding style Your code-style should comply with [PSR-2](http://www.php-fig.org/psr/psr-2/). To make sure your code matches this requirement run: diff --git a/lib/AbstractWebDriverCheckboxOrRadio.php b/lib/AbstractWebDriverCheckboxOrRadio.php index f70a2ef04..1268ce7c0 100644 --- a/lib/AbstractWebDriverCheckboxOrRadio.php +++ b/lib/AbstractWebDriverCheckboxOrRadio.php @@ -211,7 +211,7 @@ protected function getRelatedElements($value = null) $form = $this->element->findElement(WebDriverBy::xpath('ancestor::form')); $formId = $form->getAttribute('id'); - if ($formId === '') { + if (!$formId) { return $form->findElements(WebDriverBy::xpath( sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector) )); diff --git a/lib/Cookie.php b/lib/Cookie.php index 216ac3033..170b46486 100644 --- a/lib/Cookie.php +++ b/lib/Cookie.php @@ -190,7 +190,13 @@ public function isHttpOnly() */ public function toArray() { - return $this->cookie; + $cookie = $this->cookie; + if (!isset($cookie['secure'])) { + // Passing a boolean value for the "secure" flag is mandatory when using geckodriver + $cookie['secure'] = false; + } + + return $cookie; } public function offsetExists($offset) diff --git a/lib/Exception/WebDriverException.php b/lib/Exception/WebDriverException.php index 8545729ad..b17b2f70f 100644 --- a/lib/Exception/WebDriverException.php +++ b/lib/Exception/WebDriverException.php @@ -42,7 +42,7 @@ public function getResults() /** * Throw WebDriverExceptions based on WebDriver status code. * - * @param int $status_code + * @param int|string $status_code * @param string $message * @param mixed $results * @@ -85,6 +85,54 @@ public function getResults() */ public static function throwException($status_code, $message, $results) { + if (is_string($status_code)) { + // see https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors + switch ($status_code) { + case 'no such element': + throw new NoSuchElementException($message, $results); + case 'no such frame': + throw new NoSuchFrameException($message, $results); + case 'unknown command': + throw new UnknownCommandException($message, $results); + case 'stale element reference': + throw new StaleElementReferenceException($message, $results); + case 'invalid element state': + throw new InvalidElementStateException($message, $results); + case 'unknown error': + throw new UnknownServerException($message, $results); + case 'unsupported operation': + throw new ExpectedException($message, $results); + case 'element not interactable': + throw new ElementNotSelectableException($message, $results); + case 'no such window': + throw new NoSuchDocumentException($message, $results); + case 'javascript error': + throw new UnexpectedJavascriptException($message, $results); + case 'timeout': + throw new TimeOutException($message, $results); + case 'no such window': + throw new NoSuchWindowException($message, $results); + case 'invalid cookie domain': + throw new InvalidCookieDomainException($message, $results); + case 'unable to set cookie': + throw new UnableToSetCookieException($message, $results); + case 'unexpected alert open': + throw new UnexpectedAlertOpenException($message, $results); + case 'no such alert': + throw new NoAlertOpenException($message, $results); + case 'script timeout': + throw new ScriptTimeoutException($message, $results); + case 'invalid selector': + throw new InvalidSelectorException($message, $results); + case 'session not created': + throw new SessionNotCreatedException($message, $results); + case 'move target out of bounds': + throw new MoveTargetOutOfBoundsException($message, $results); + default: + throw new UnrecognizedExceptionException($message, $results); + } + } + switch ($status_code) { case 1: throw new IndexOutOfBoundsException($message, $results); diff --git a/lib/Interactions/WebDriverActions.php b/lib/Interactions/WebDriverActions.php index e97796854..66ac7c1ab 100644 --- a/lib/Interactions/WebDriverActions.php +++ b/lib/Interactions/WebDriverActions.php @@ -25,7 +25,6 @@ use Facebook\WebDriver\Interactions\Internal\WebDriverMouseMoveAction; use Facebook\WebDriver\Interactions\Internal\WebDriverMoveToOffsetAction; use Facebook\WebDriver\Interactions\Internal\WebDriverSendKeysAction; -use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverHasInputDevices; diff --git a/lib/Remote/DriverCommand.php b/lib/Remote/DriverCommand.php index c69052030..fd24a691c 100644 --- a/lib/Remote/DriverCommand.php +++ b/lib/Remote/DriverCommand.php @@ -146,6 +146,10 @@ class DriverCommand const GET_NETWORK_CONNECTION = 'getNetworkConnection'; const SET_NETWORK_CONNECTION = 'setNetworkConnection'; + // W3C specific + const ACTIONS = 'actions'; + const GET_ELEMENT_PROPERTY = 'getElementProperty'; + private function __construct() { } diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index f584324b7..c783b2892 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -137,6 +137,29 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], ]; + /** + * @var array Will be merged with $commands + */ + protected static $w3cCompliantCommands = [ + DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/accept'], + DriverCommand::ACTIONS => ['method' => 'POST', 'url' => '/session/:sessionId/actions'], + DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'], + DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], + DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], + DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window'], + DriverCommand::GET_ELEMENT_LOCATION => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], + DriverCommand::GET_ELEMENT_PROPERTY => [ + 'method' => 'GET', + 'url' => '/session/:sessionId/element/:id/property/:name', + ], + DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], + DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'], + DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], + DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], + DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert/text'], + DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], + DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], + ]; /** * @var string */ @@ -145,6 +168,10 @@ class HttpCommandExecutor implements WebDriverCommandExecutor * @var resource */ protected $curl; + /** + * @var bool + */ + protected $w3cCompliant = true; /** * @param string $url @@ -153,6 +180,8 @@ class HttpCommandExecutor implements WebDriverCommandExecutor */ public function __construct($url, $http_proxy = null, $http_proxy_port = null) { + self::$w3cCompliantCommands = array_merge(self::$commands, self::$w3cCompliantCommands); + $this->url = $url; $this->curl = curl_init(); @@ -179,6 +208,11 @@ public function __construct($url, $http_proxy = null, $http_proxy_port = null) $this->setConnectionTimeout(30000); } + public function disableW3CCompliance() + { + $this->w3cCompliant = false; + } + /** * Set timeout for the connect phase * @@ -226,11 +260,19 @@ public function setRequestTimeout($timeout_in_ms) */ public function execute(WebDriverCommand $command) { - if (!isset(self::$commands[$command->getName()])) { - throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); + $commandName = $command->getName(); + if (!isset(self::$commands[$commandName])) { + if ($this->w3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { + throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); + } + } + + if ($this->w3cCompliant) { + $raw = self::$w3cCompliantCommands[$command->getName()]; + } else { + $raw = self::$commands[$command->getName()]; } - $raw = self::$commands[$command->getName()]; $http_method = $raw['method']; $url = $raw['url']; $url = str_replace(':sessionId', $command->getSessionID(), $url); @@ -317,12 +359,23 @@ public function execute(WebDriverCommand $command) } $sessionId = null; - if (is_array($results) && array_key_exists('sessionId', $results)) { + if (is_array($value) && array_key_exists('sessionId', $value)) { + // W3C's WebDriver + $sessionId = $value['sessionId']; + } elseif (is_array($results) && array_key_exists('sessionId', $results)) { + // Legacy JsonWire $sessionId = $results['sessionId']; } + // @see https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors + if (isset($value['error'])) { + // W3C's WebDriver + WebDriverException::throwException($value['error'], $message, $results); + } + $status = isset($results['status']) ? $results['status'] : 0; - if ($status != 0) { + if ($status !== 0) { + // Legacy JsonWire WebDriverException::throwException($status, $message, $results); } diff --git a/lib/Remote/JsonWireCompat.php b/lib/Remote/JsonWireCompat.php new file mode 100644 index 000000000..78b9cba0e --- /dev/null +++ b/lib/Remote/JsonWireCompat.php @@ -0,0 +1,102 @@ +getMechanism(); + $value = $by->getValue(); + + if ($w3cCompliant) { + switch ($mechanism) { + // Convert to CSS selectors + case 'class name': + $mechanism = 'css selector'; + $value = sprintf('.%s', self::escapeSelector($value)); + break; + case 'id': + $mechanism = 'css selector'; + $value = sprintf('#%s', self::escapeSelector($value)); + break; + case 'name': + $mechanism = 'css selector'; + $value = sprintf('[name=\'%s\']', self::escapeSelector($value)); + break; + } + } + + return ['using' => $mechanism, 'value' => $value]; + } + + /** + * Escapes a CSS selector. + * + * Code adapted from the Zend Escaper project. + * + * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) + * @see https://github.com/zendframework/zend-escaper/blob/master/src/Escaper.php + * + * @param string $selector + * @return string + */ + private static function escapeSelector($selector) + { + return preg_replace_callback('/[^a-z0-9]/iSu', function ($matches) { + $chr = $matches[0]; + if (mb_strlen($chr) == 1) { + $ord = ord($chr); + } else { + $chr = mb_convert_encoding($chr, 'UTF-32BE', 'UTF-8'); + $ord = hexdec(bin2hex($chr)); + } + + return sprintf('\\%X ', $ord); + }, $selector); + } +} diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index 48c578df7..aa0d59829 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -27,13 +27,19 @@ class RemoteMouse implements WebDriverMouse * @var RemoteExecuteMethod */ private $executor; + /** + * @var bool + */ + private $w3cCompliant; /** * @param RemoteExecuteMethod $executor + * @param bool $w3cCompliant */ - public function __construct(RemoteExecuteMethod $executor) + public function __construct(RemoteExecuteMethod $executor, $w3cCompliant = false) { $this->executor = $executor; + $this->w3cCompliant = $w3cCompliant; } /** @@ -43,6 +49,22 @@ public function __construct(RemoteExecuteMethod $executor) */ public function click(WebDriverCoordinates $where = null) { + if ($this->w3cCompliant) { + $moveAction = $where ? [$this->createMoveAction($where)] : []; + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => array_merge($moveAction, $this->createClickActions()), + ], + ], + ]); + + return $this; + } + $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => 0, @@ -58,6 +80,33 @@ public function click(WebDriverCoordinates $where = null) */ public function contextClick(WebDriverCoordinates $where = null) { + if ($this->w3cCompliant) { + $moveAction = $where ? [$this->createMoveAction($where)] : []; + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => array_merge($moveAction, [ + [ + 'type' => 'pointerDown', + 'duration' => 0, + 'button' => 2, + ], + [ + 'type' => 'pointerUp', + 'duration' => 0, + 'button' => 2, + ], + ]), + ], + ], + ]); + + return $this; + } + $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => 2, @@ -73,6 +122,23 @@ public function contextClick(WebDriverCoordinates $where = null) */ public function doubleClick(WebDriverCoordinates $where = null) { + if ($this->w3cCompliant) { + $clickActions = $this->createClickActions(); + $moveAction = null === $where ? [] : [$this->createMoveAction($where)]; + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => array_merge($moveAction, $clickActions, $clickActions), + ], + ], + ]); + + return $this; + } + $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::DOUBLE_CLICK); @@ -86,6 +152,28 @@ public function doubleClick(WebDriverCoordinates $where = null) */ public function mouseDown(WebDriverCoordinates $where = null) { + if ($this->w3cCompliant) { + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [ + $this->createMoveAction($where), + [ + 'type' => 'pointerDown', + 'duration' => 0, + 'button' => 0, + ], + ], + ], + ], + ]); + + return $this; + } + $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_DOWN); @@ -104,6 +192,21 @@ public function mouseMove( $x_offset = null, $y_offset = null ) { + if ($this->w3cCompliant) { + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [$this->createMoveAction($where, $x_offset, $y_offset)], + ], + ], + ]); + + return $this; + } + $params = []; if ($where !== null) { $params['element'] = $where->getAuxiliary(); @@ -114,6 +217,7 @@ public function mouseMove( if ($y_offset !== null) { $params['yoffset'] = $y_offset; } + $this->executor->execute(DriverCommand::MOVE_TO, $params); return $this; @@ -126,6 +230,29 @@ public function mouseMove( */ public function mouseUp(WebDriverCoordinates $where = null) { + if ($this->w3cCompliant) { + $moveAction = $where ? [$this->createMoveAction($where)] : []; + + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => array_merge($moveAction, [ + [ + 'type' => 'pointerDown', + 'duration' => 0, + 'button' => 0, + ], + ]), + ], + ], + ]); + + return $this; + } + $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_UP); @@ -141,4 +268,49 @@ protected function moveIfNeeded(WebDriverCoordinates $where = null) $this->mouseMove($where); } } + + /** + * @param WebDriverCoordinates $where + * @param int|null $x_offset + * @param int|null $y_offset + * + * @return array + */ + private function createMoveAction( + WebDriverCoordinates $where = null, + $x_offset = null, + $y_offset = null + ) { + $move_action = [ + 'type' => 'pointerMove', + 'duration' => 0, + '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()]; + } + + return $move_action; + } + + /** + * @return array + */ + private function createClickActions() + { + return [ + [ + 'type' => 'pointerDown', + 'duration' => 0, + 'button' => 0, + ], + [ + 'type' => 'pointerUp', + 'duration' => 0, + 'button' => 0, + ], + ]; + } } diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index 5c218075d..55a879c0d 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -112,6 +112,8 @@ public function activeElement() $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); - return new RemoteWebElement($method, $response['ELEMENT']); + $w3cCompliant = $this->driver instanceof RemoteWebDriver ? $this->driver->isW3cCompliant() : false; + + return new RemoteWebElement($method, JsonWireCompat::getElement($response), $w3cCompliant); } } diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 71f45704e..6d3a7d833 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -59,19 +59,26 @@ class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInpu * @var RemoteExecuteMethod */ protected $executeMethod; + /** + * @var bool + */ + protected $w3cCompliant; /** * @param HttpCommandExecutor $commandExecutor * @param string $sessionId * @param WebDriverCapabilities|null $capabilities + * @param bool $w3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec */ protected function __construct( HttpCommandExecutor $commandExecutor, $sessionId, - WebDriverCapabilities $capabilities = null + WebDriverCapabilities $capabilities = null, + $w3cCompliant = false ) { $this->executor = $commandExecutor; $this->sessionID = $sessionId; + $this->w3cCompliant = $w3cCompliant; if ($capabilities !== null) { $this->capabilities = $capabilities; @@ -88,6 +95,7 @@ protected function __construct( * @param string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through * @param DesiredCapabilities $required_capabilities The required capabilities + * * @return static */ public static function create( @@ -99,6 +107,7 @@ 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); @@ -128,23 +137,43 @@ public static function create( $executor->setRequestTimeout($request_timeout_in_ms); } + // W3C + $parameters = [ + 'capabilities' => [ + 'firstMatch' => [$desired_capabilities->toArray()], + ], + ]; + + // Legacy protocol + if (null !== $required_capabilities && $required_capabilities_array = $required_capabilities->toArray()) { + $parameters['capabilities']['alwaysMatch'] = $required_capabilities_array; + } + if ($required_capabilities !== null) { // TODO: Selenium (as of v3.0.1) does accept requiredCapabilities only as a property of desiredCapabilities. - // This will probably change in future with the W3C WebDriver spec, but is the only way how to pass these - // values now. + // 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()); } + $parameters['desiredCapabilities'] = $desired_capabilities->toArray(); + $command = new WebDriverCommand( null, DriverCommand::NEW_SESSION, - ['desiredCapabilities' => $desired_capabilities->toArray()] + $parameters ); $response = $executor->execute($command); - $returnedCapabilities = new DesiredCapabilities($response->getValue()); + $value = $response->getValue(); + + if (!$w3c_compliant = isset($value['capabilities'])) { + $executor->disableW3CCompliance(); + } - $driver = new static($executor, $response->getSessionID(), $returnedCapabilities); + $returnedCapabilities = new DesiredCapabilities($w3c_compliant ? $value['capabilities'] : $value); + + $driver = new static($executor, $response->getSessionID(), $returnedCapabilities, $w3c_compliant); return $driver; } @@ -167,7 +196,9 @@ public static function createBySessionID( $connection_timeout_in_ms = null, $request_timeout_in_ms = null ) { - $executor = new HttpCommandExecutor($selenium_server_url); + // BC layer to not break the method signature + $w3c_compliant = func_num_args() > 3 ? func_get_arg(3) : false; + $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } @@ -175,7 +206,7 @@ public static function createBySessionID( $executor->setRequestTimeout($request_timeout_in_ms); } - return new static($executor, $session_id); + return new static($executor, $session_id, null, $w3c_compliant); } /** @@ -199,13 +230,12 @@ public function close() */ public function findElement(WebDriverBy $by) { - $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()]; $raw_element = $this->execute( DriverCommand::FIND_ELEMENT, - $params + JsonWireCompat::getUsing($by, $this->w3cCompliant) ); - return $this->newElement($raw_element['ELEMENT']); + return $this->newElement(JsonWireCompat::getElement($raw_element)); } /** @@ -217,15 +247,14 @@ public function findElement(WebDriverBy $by) */ public function findElements(WebDriverBy $by) { - $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()]; $raw_elements = $this->execute( DriverCommand::FIND_ELEMENTS, - $params + JsonWireCompat::getUsing($by, $this->w3cCompliant) ); $elements = []; foreach ($raw_elements as $raw_element) { - $elements[] = $this->newElement($raw_element['ELEMENT']); + $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; @@ -399,7 +428,7 @@ public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) */ public function manage() { - return new WebDriverOptions($this->getExecuteMethod()); + return new WebDriverOptions($this->getExecuteMethod(), $this->w3cCompliant); } /** @@ -430,7 +459,7 @@ public function switchTo() public function getMouse() { if (!$this->mouse) { - $this->mouse = new RemoteMouse($this->getExecuteMethod()); + $this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->w3cCompliant); } return $this->mouse; @@ -541,7 +570,7 @@ public function getCapabilities() */ public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000) { - $executor = new HttpCommandExecutor($selenium_server_url); + $executor = new HttpCommandExecutor($selenium_server_url, null, null); $executor->setConnectionTimeout($timeout_in_ms); $command = new WebDriverCommand( @@ -570,6 +599,15 @@ public function execute($command_name, $params = []) return null; } + /** + * @internal + * @return bool + */ + public function isW3cCompliant() + { + return $this->w3cCompliant; + } + /** * Prepare arguments for JavaScript injection * @@ -581,9 +619,11 @@ protected function prepareScriptArguments(array $arguments) $args = []; foreach ($arguments as $key => $value) { if ($value instanceof WebDriverElement) { - $args[$key] = ['ELEMENT' => $value->getID()]; + $args[$key] = [ + $this->w3cCompliant ? JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER : 'ELEMENT' => $value->getID(), + ]; } else { - if (is_array($value)) { + if (\is_array($value)) { $value = $this->prepareScriptArguments($value); } $args[$key] = $value; @@ -613,7 +653,7 @@ protected function getExecuteMethod() */ protected function newElement($id) { - return new RemoteWebElement($this->getExecuteMethod(), $id); + return new RemoteWebElement($this->getExecuteMethod(), $id, $this->w3cCompliant); } /** @@ -629,7 +669,7 @@ protected static function castToDesiredCapabilitiesObject($desired_capabilities return new DesiredCapabilities(); } - if (is_array($desired_capabilities)) { + if (\is_array($desired_capabilities)) { return new DesiredCapabilities($desired_capabilities); } diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 26936d8b9..b07b18227 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -15,6 +15,7 @@ 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; @@ -42,16 +43,22 @@ class RemoteWebElement implements WebDriverElement, WebDriverLocatable * @var UselessFileDetector */ protected $fileDetector; + /** + * @var bool + */ + protected $w3cCompliant; /** * @param RemoteExecuteMethod $executor * @param string $id + * @param bool $w3cCompliant */ - public function __construct(RemoteExecuteMethod $executor, $id) + public function __construct(RemoteExecuteMethod $executor, $id, $w3cCompliant = false) { $this->executor = $executor; $this->id = $id; $this->fileDetector = new UselessFileDetector(); + $this->w3cCompliant = $w3cCompliant; } /** @@ -66,6 +73,23 @@ public function clear() [':id' => $this->id] ); + if ($this->w3cCompliant) { + $this->executor->execute(DriverCommand::ACTIONS, [ + 'actions' => [[ + 'type' => 'key', + 'id' => 'keyboard', + 'actions' => [ + ['type' => 'keyDown' , 'value' => WebDriverKeys::CONTROL], + ['type' => 'keyDown' , 'value' => 'a'], + ['type' => 'keyUp' , 'value' => WebDriverKeys::CONTROL], + ['type' => 'keyUp' , 'value' => 'a'], + ['type' => 'keyDown' , 'value' => WebDriverKeys::BACKSPACE], + ['type' => 'keyUp' , 'value' => WebDriverKeys::BACKSPACE], + ], + ]], + ]); + } + return $this; } @@ -94,17 +118,15 @@ public function click() */ public function findElement(WebDriverBy $by) { - $params = [ - 'using' => $by->getMechanism(), - 'value' => $by->getValue(), - ':id' => $this->id, - ]; + $params = JsonWireCompat::getUsing($by, $this->w3cCompliant); + $params[':id'] = $this->id; + $raw_element = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENT, $params ); - return $this->newElement($raw_element['ELEMENT']); + return $this->newElement(JsonWireCompat::getElement($raw_element)); } /** @@ -117,11 +139,8 @@ public function findElement(WebDriverBy $by) */ public function findElements(WebDriverBy $by) { - $params = [ - 'using' => $by->getMechanism(), - 'value' => $by->getValue(), - ':id' => $this->id, - ]; + $params = JsonWireCompat::getUsing($by, $this->w3cCompliant); + $params[':id'] = $this->id; $raw_elements = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENTS, $params @@ -129,7 +148,7 @@ public function findElements(WebDriverBy $by) $elements = []; foreach ($raw_elements as $raw_element) { - $elements[] = $this->newElement($raw_element['ELEMENT']); + $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; @@ -148,10 +167,23 @@ public function getAttribute($attribute_name) ':id' => $this->id, ]; - return $this->executor->execute( - DriverCommand::GET_ELEMENT_ATTRIBUTE, - $params - ); + if ($this->w3cCompliant && ($attribute_name === 'value' || $attribute_name === 'index')) { + $value = $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); + + if ($value === true) { + return 'true'; + } + + if ($value === false) { + return 'false'; + } + + if ($value !== null) { + return (string) $value; + } + } + + return $this->executor->execute(DriverCommand::GET_ELEMENT_ATTRIBUTE, $params); } /** @@ -325,20 +357,37 @@ public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); if ($local_file === null) { + if ($this->w3cCompliant) { + $params = [ + 'text' => (string) $value, + ':id' => $this->id, + ]; + } else { + $params = [ + 'value' => WebDriverKeys::encode($value), + ':id' => $this->id, + ]; + } + + $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); + + return $this; + } + + if ($this->w3cCompliant) { $params = [ - 'value' => WebDriverKeys::encode($value), + 'text' => $local_file, ':id' => $this->id, ]; - $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); } else { - $remote_path = $this->upload($local_file); $params = [ - 'value' => WebDriverKeys::encode($remote_path), + 'value' => WebDriverKeys::encode($this->upload($local_file)), ':id' => $this->id, ]; - $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); } + $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); + return $this; } @@ -371,6 +420,19 @@ public function setFileDetector(FileDetector $detector) */ public function submit() { + if ($this->w3cCompliant) { + $this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [ + // 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' + ), + 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], + ]); + + return $this; + } + $this->executor->execute( DriverCommand::SUBMIT_ELEMENT, [':id' => $this->id] @@ -397,6 +459,10 @@ public function getID() */ public function equals(WebDriverElement $other) { + if ($this->w3cCompliant) { + throw new UnsupportedOperationException('"elementEquals" is not supported by the W3C specification'); + } + return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [ ':id' => $this->id, ':other' => $other->getID(), @@ -412,7 +478,7 @@ public function equals(WebDriverElement $other) */ protected function newElement($id) { - return new static($this->executor, $id); + return new static($this->executor, $id, $this->w3cCompliant); } /** diff --git a/lib/WebDriverDimension.php b/lib/WebDriverDimension.php index 308c1990f..81a223989 100644 --- a/lib/WebDriverDimension.php +++ b/lib/WebDriverDimension.php @@ -21,17 +21,17 @@ class WebDriverDimension { /** - * @var int + * @var int|float */ private $height; /** - * @var int + * @var int|float */ private $width; /** - * @param int $width - * @param int $height + * @param int|float $width + * @param int|float $height */ public function __construct($width, $height) { @@ -46,7 +46,7 @@ public function __construct($width, $height) */ public function getHeight() { - return $this->height; + return (int) $this->height; } /** @@ -56,7 +56,7 @@ public function getHeight() */ public function getWidth() { - return $this->width; + return (int) $this->width; } /** diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index bec7fd18b..4c584bd07 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -28,10 +28,15 @@ class WebDriverOptions * @var ExecuteMethod */ protected $executor; + /** + * @var bool + */ + protected $w3cCompliant; - public function __construct(ExecuteMethod $executor) + public function __construct(ExecuteMethod $executor, $w3cCompliant = false) { $this->executor = $executor; + $this->w3cCompliant = $w3cCompliant; } /** @@ -128,7 +133,7 @@ public function getCookies() */ public function timeouts() { - return new WebDriverTimeouts($this->executor); + return new WebDriverTimeouts($this->executor, $this->w3cCompliant); } /** diff --git a/lib/WebDriverPoint.php b/lib/WebDriverPoint.php index 4e2dbd211..9600af9d8 100644 --- a/lib/WebDriverPoint.php +++ b/lib/WebDriverPoint.php @@ -36,7 +36,7 @@ public function __construct($x, $y) */ public function getX() { - return $this->x; + return (int) $this->x; } /** @@ -46,7 +46,7 @@ public function getX() */ public function getY() { - return $this->y; + return (int) $this->y; } /** diff --git a/lib/WebDriverTimeouts.php b/lib/WebDriverTimeouts.php index 6903f7080..eeba9a10e 100644 --- a/lib/WebDriverTimeouts.php +++ b/lib/WebDriverTimeouts.php @@ -27,10 +27,15 @@ class WebDriverTimeouts * @var ExecuteMethod */ protected $executor; + /** + * @var bool + */ + protected $w3cCompliant; - public function __construct(ExecuteMethod $executor) + public function __construct(ExecuteMethod $executor, $w3cCompliant = false) { $this->executor = $executor; + $this->w3cCompliant = $w3cCompliant; } /** @@ -41,6 +46,15 @@ public function __construct(ExecuteMethod $executor) */ public function implicitlyWait($seconds) { + if ($this->w3cCompliant) { + $this->executor->execute( + DriverCommand::IMPLICITLY_WAIT, + ['implicit' => $seconds * 1000] + ); + + return $this; + } + $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['ms' => $seconds * 1000] @@ -57,6 +71,15 @@ public function implicitlyWait($seconds) */ public function setScriptTimeout($seconds) { + if ($this->w3cCompliant) { + $this->executor->execute( + DriverCommand::SET_SCRIPT_TIMEOUT, + ['script' => $seconds * 1000] + ); + + return $this; + } + $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['ms' => $seconds * 1000] @@ -73,6 +96,15 @@ public function setScriptTimeout($seconds) */ public function pageLoadTimeout($seconds) { + if ($this->w3cCompliant) { + $this->executor->execute( + DriverCommand::SET_SCRIPT_TIMEOUT, + ['pageLoad' => $seconds * 1000] + ); + + return $this; + } + $this->executor->execute(DriverCommand::SET_TIMEOUT, [ 'type' => 'page load', 'ms' => $seconds * 1000, diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index 90c072922..a2f6e9d0f 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -33,7 +33,10 @@ public function testShouldStartBrowserAndCreateInstanceOfRemoteWebDriver() $this->serverUrl, $this->desiredCapabilities, $this->connectionTimeout, - $this->requestTimeout + $this->requestTimeout, + null, + null, + null ); $this->assertInstanceOf(RemoteWebDriver::class, $this->driver); diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index eea14b6e5..066179f1f 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -61,4 +61,24 @@ public function testShouldFindMultipleElements() $this->assertCount(5, $elements); $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } + + public function testEscapeCssSelector() + { + if (getenv('GECKODRIVER') !== '1') { + $this->markTestSkipped( + 'CSS selectors containing special characters are not supported by the legacy protocol' + ); + } + + $this->driver->get($this->getTestPageUrl('escape_css.html')); + + $element = $this->driver->findElement(WebDriverBy::id('.fo\'oo')); + $this->assertSame('Foo', $element->getText()); + + $element = $this->driver->findElement(WebDriverBy::className('#ba\'r')); + $this->assertSame('Bar', $element->getText()); + + $element = $this->driver->findElement(WebDriverBy::name('.#ba\'z')); + $this->assertSame('Baz', $element->getText()); + } } diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index d996d304a..9105aa93f 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -77,7 +77,11 @@ public function testShouldGetSessionId() */ public function testShouldGetAllSessions() { - $sessions = RemoteWebDriver::getAllSessions($this->serverUrl); + if (getenv('GECKODRIVER') === '1') { + $this->markTestSkipped('"getAllSessions" is not supported by the W3C specification'); + } + + $sessions = RemoteWebDriver::getAllSessions($this->serverUrl, 30000); $this->assertInternalType('array', $sessions); $this->assertCount(1, $sessions); @@ -94,12 +98,22 @@ public function testShouldGetAllSessions() */ public function testShouldQuitAndUnsetExecutor() { - $this->assertCount(1, RemoteWebDriver::getAllSessions($this->serverUrl)); + if (getenv('GECKODRIVER') === '1') { + $this->markTestSkipped('"getAllSessions" is not supported by the W3C specification'); + } + + $this->assertCount( + 1, + RemoteWebDriver::getAllSessions($this->serverUrl, 30000) + ); $this->assertInstanceOf(HttpCommandExecutor::class, $this->driver->getCommandExecutor()); $this->driver->quit(); - $this->assertCount(0, RemoteWebDriver::getAllSessions($this->serverUrl)); + $this->assertCount( + 0, + RemoteWebDriver::getAllSessions($this->serverUrl, 30000) + ); $this->assertNull($this->driver->getCommandExecutor()); } @@ -136,6 +150,9 @@ public function testShouldCloseWindow() $this->driver->get($this->getTestPageUrl('open_new_window.html')); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); + // Mandatory for Geckodriver + $this->driver->wait()->until(WebDriverExpectedCondition::numberOfWindowsToBe(2)); + $this->assertCount(2, $this->driver->getWindowHandles()); $this->driver->close(); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index d4614a431..a4684ace3 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -272,6 +272,10 @@ public function testShouldSubmitFormByClickOnSubmitInput() */ public function testShouldCompareEqualsElement() { + if (getenv('GECKODRIVER') === '1') { + $this->markTestSkipped('"equals" is not supported by the W3C specification'); + } + $this->driver->get($this->getTestPageUrl('index.html')); $firstElement = $this->driver->findElement(WebDriverBy::cssSelector('ul.list')); diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 3df984810..b2bd51835 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -46,10 +46,16 @@ public function testShouldClickOnElement() ->click($element) ->perform(); - $this->assertSame( - ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], - $this->retrieveLoggedEvents() - ); + $logs = ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1']; + $loggedEvents = $this->retrieveLoggedEvents(); + + if ('1' === getenv('GECKODRIVER')) { + $loggedEvents = array_slice($loggedEvents, 0, count($logs)); + // Firefox sometimes triggers some extra events + // it's not related to Geckodriver, it's Firefox's own behavior + } + + $this->assertSame($logs, $loggedEvents); } /** @@ -71,10 +77,12 @@ public function testShouldClickAndHoldOnElementAndRelease() ->release() ->perform(); - $this->assertSame( - ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], - $this->retrieveLoggedEvents() - ); + if ('1' === getenv('GECKODRIVER')) { + $logs = ['mouseover item-1', 'mousedown item-1', 'dragstart item-1']; + } else { + $logs = ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1']; + } + $this->assertSame($logs, $this->retrieveLoggedEvents()); } /** diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 5fe6a7e8a..7a0f8af23 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -58,6 +58,8 @@ protected function setUp() // --no-sandbox is a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 $chromeOptions->addArguments(['--headless', 'window-size=1024,768', '--no-sandbox']); $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); + } elseif (getenv('GECKODRIVER') === '1') { + $this->serverUrl = 'http://localhost:4444/'; } $this->desiredCapabilities->setBrowserName($browserName); @@ -68,7 +70,10 @@ protected function setUp() $this->serverUrl, $this->desiredCapabilities, $this->connectionTimeout, - $this->requestTimeout + $this->requestTimeout, + null, + null, + null ); } } diff --git a/tests/functional/web/escape_css.html b/tests/functional/web/escape_css.html new file mode 100644 index 000000000..48213084a --- /dev/null +++ b/tests/functional/web/escape_css.html @@ -0,0 +1,14 @@ + + + + + Test CSS selector escaping + + + +
    Foo
    +
    Bar
    +
    Baz
    + + + diff --git a/tests/functional/web/upload.html b/tests/functional/web/upload.html index 65a622c50..6762873c3 100644 --- a/tests/functional/web/upload.html +++ b/tests/functional/web/upload.html @@ -8,7 +8,7 @@

    - +

    From 4a65bed00dd2bdbbfa81206590df29d2060221c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 20 Aug 2019 16:18:50 +0200 Subject: [PATCH 141/487] Add back Geckodriver to the Travis matrix --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 47ddb461e..906951171 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,9 +108,14 @@ install: before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROMEDRIVER_VERSION=$(wget -qO- "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi + - if [ "$GECKODRIVER" = "1" ]; then mkdir geckodriver; wget -q -t 3 https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz; tar xzf geckodriver-v0.24.0-linux64.tar.gz -C geckodriver; fi - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; fi - - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.8.1.jar -enablePassThrough false -log ./logs/selenium.log & + - if [ "$GECKODRIVER" = "1" ]; then + geckodriver/geckodriver &> ./logs/geckodriver.log & + else + java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.8.1.jar -enablePassThrough false -log ./logs/selenium.log & + fi - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - 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" @@ -124,6 +129,7 @@ script: after_script: - if [ -f ./logs/selenium.log ]; then cat ./logs/selenium.log; fi - if [ -f ./logs/php-server.log ]; then cat ./logs/php-server.log; fi + - if [ -f ./logs/geckodriver.log ]; then cat ./logs/geckodriver.log; fi after_success: - travis_retry php vendor/bin/php-coveralls -v From 5729355c5d37953d6a1a5a2f55afc7ba1c23bf57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 20 Aug 2019 17:06:26 +0200 Subject: [PATCH 142/487] Improve tests stability --- tests/functional/WebDriverActionsTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index b2bd51835..1f03c8899 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -78,11 +78,13 @@ public function testShouldClickAndHoldOnElementAndRelease() ->perform(); if ('1' === getenv('GECKODRIVER')) { - $logs = ['mouseover item-1', 'mousedown item-1', 'dragstart item-1']; + $this->assertArraySubset(['mouseover item-1', 'mousedown item-1'], $this->retrieveLoggedEvents()); } else { - $logs = ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1']; + $this->assertSame( + ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1'], + $this->retrieveLoggedEvents() + ); } - $this->assertSame($logs, $this->retrieveLoggedEvents()); } /** From 721a7c2737ea8353c01e76ec7752aa0751e667eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 4 Nov 2019 19:36:50 +0000 Subject: [PATCH 143/487] Use latest geckodriver --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 906951171..950e8fe64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,7 +108,7 @@ install: before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROMEDRIVER_VERSION=$(wget -qO- "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - - if [ "$GECKODRIVER" = "1" ]; then mkdir geckodriver; wget -q -t 3 https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz; tar xzf geckodriver-v0.24.0-linux64.tar.gz -C geckodriver; fi + - if [ "$GECKODRIVER" = "1" ]; then mkdir 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 - sh -e /etc/init.d/xvfb start - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; fi - if [ "$GECKODRIVER" = "1" ]; then From 71a6698227485b368d4212980ed9efcde15b292f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 5 Nov 2019 15:35:40 +0000 Subject: [PATCH 144/487] Make POST body always valid JSON in W3C mode --- lib/Remote/HttpCommandExecutor.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index c783b2892..8114d7817 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -313,8 +313,13 @@ public function execute(WebDriverCommand $command) $encoded_params = null; - if ($http_method === 'POST' && $params && is_array($params)) { - $encoded_params = json_encode($params); + if ($http_method === 'POST') { + if ($params && is_array($params)) { + $encoded_params = json_encode($params); + } elseif ($this->w3cCompliant) { + // POST body must be valid JSON in W3C, even if empty: https://www.w3.org/TR/webdriver/#processing-model + $encoded_params = '{}'; + } } curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params); From 7119d7fc97c5291768cd8632a15bfa7d75bff145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 5 Nov 2019 15:37:36 +0000 Subject: [PATCH 145/487] Codestyle: unify casing in w3c methods --- lib/Remote/HttpCommandExecutor.php | 12 +++---- lib/Remote/JsonWireCompat.php | 8 ++--- lib/Remote/RemoteMouse.php | 20 ++++++------ lib/Remote/RemoteTargetLocator.php | 4 +-- lib/Remote/RemoteWebDriver.php | 30 +++++++++--------- lib/Remote/RemoteWebElement.php | 50 ++++++++++++++++-------------- lib/WebDriverOptions.php | 8 ++--- lib/WebDriverTimeouts.php | 12 +++---- 8 files changed, 74 insertions(+), 70 deletions(-) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index 8114d7817..b87c94069 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -171,7 +171,7 @@ class HttpCommandExecutor implements WebDriverCommandExecutor /** * @var bool */ - protected $w3cCompliant = true; + protected $isW3cCompliant = true; /** * @param string $url @@ -208,9 +208,9 @@ public function __construct($url, $http_proxy = null, $http_proxy_port = null) $this->setConnectionTimeout(30000); } - public function disableW3CCompliance() + public function disableW3cCompliance() { - $this->w3cCompliant = false; + $this->isW3cCompliant = false; } /** @@ -262,12 +262,12 @@ public function execute(WebDriverCommand $command) { $commandName = $command->getName(); if (!isset(self::$commands[$commandName])) { - if ($this->w3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { + if ($this->isW3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { throw new InvalidArgumentException($command->getName() . ' is not a valid command.'); } } - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $raw = self::$w3cCompliantCommands[$command->getName()]; } else { $raw = self::$commands[$command->getName()]; @@ -316,7 +316,7 @@ public function execute(WebDriverCommand $command) if ($http_method === 'POST') { if ($params && is_array($params)) { $encoded_params = json_encode($params); - } elseif ($this->w3cCompliant) { + } elseif ($this->isW3cCompliant) { // POST body must be valid JSON in W3C, even if empty: https://www.w3.org/TR/webdriver/#processing-model $encoded_params = '{}'; } diff --git a/lib/Remote/JsonWireCompat.php b/lib/Remote/JsonWireCompat.php index 78b9cba0e..3488b8604 100644 --- a/lib/Remote/JsonWireCompat.php +++ b/lib/Remote/JsonWireCompat.php @@ -44,16 +44,16 @@ public static function getElement(array $rawElement) /** * @param WebDriverBy $by - * @param bool $w3cCompliant + * @param bool $isW3cCompliant * * @return array */ - public static function getUsing(WebDriverBy $by, $w3cCompliant) + public static function getUsing(WebDriverBy $by, $isW3cCompliant) { $mechanism = $by->getMechanism(); $value = $by->getValue(); - if ($w3cCompliant) { + if ($isW3cCompliant) { switch ($mechanism) { // Convert to CSS selectors case 'class name': @@ -89,7 +89,7 @@ private static function escapeSelector($selector) { return preg_replace_callback('/[^a-z0-9]/iSu', function ($matches) { $chr = $matches[0]; - if (mb_strlen($chr) == 1) { + if (mb_strlen($chr) === 1) { $ord = ord($chr); } else { $chr = mb_convert_encoding($chr, 'UTF-32BE', 'UTF-8'); diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index aa0d59829..4bd4a849b 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -30,16 +30,16 @@ class RemoteMouse implements WebDriverMouse /** * @var bool */ - private $w3cCompliant; + private $isW3cCompliant; /** * @param RemoteExecuteMethod $executor - * @param bool $w3cCompliant + * @param bool $isW3cCompliant */ - public function __construct(RemoteExecuteMethod $executor, $w3cCompliant = false) + public function __construct(RemoteExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; - $this->w3cCompliant = $w3cCompliant; + $this->isW3cCompliant = $isW3cCompliant; } /** @@ -49,7 +49,7 @@ public function __construct(RemoteExecuteMethod $executor, $w3cCompliant = false */ public function click(WebDriverCoordinates $where = null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ @@ -80,7 +80,7 @@ public function click(WebDriverCoordinates $where = null) */ public function contextClick(WebDriverCoordinates $where = null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ @@ -122,7 +122,7 @@ public function contextClick(WebDriverCoordinates $where = null) */ public function doubleClick(WebDriverCoordinates $where = null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $clickActions = $this->createClickActions(); $moveAction = null === $where ? [] : [$this->createMoveAction($where)]; $this->executor->execute(DriverCommand::ACTIONS, [ @@ -152,7 +152,7 @@ public function doubleClick(WebDriverCoordinates $where = null) */ public function mouseDown(WebDriverCoordinates $where = null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ @@ -192,7 +192,7 @@ public function mouseMove( $x_offset = null, $y_offset = null ) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ @@ -230,7 +230,7 @@ public function mouseMove( */ public function mouseUp(WebDriverCoordinates $where = null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index 55a879c0d..8bd89ef44 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -112,8 +112,8 @@ public function activeElement() $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); - $w3cCompliant = $this->driver instanceof RemoteWebDriver ? $this->driver->isW3cCompliant() : false; + $isW3cCompliant = ($this->driver instanceof RemoteWebDriver) ? $this->driver->isW3cCompliant() : false; - return new RemoteWebElement($method, JsonWireCompat::getElement($response), $w3cCompliant); + return new RemoteWebElement($method, JsonWireCompat::getElement($response), $isW3cCompliant); } } diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 6d3a7d833..4d3573c0c 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -62,23 +62,23 @@ class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInpu /** * @var bool */ - protected $w3cCompliant; + protected $isW3cCompliant; /** * @param HttpCommandExecutor $commandExecutor * @param string $sessionId * @param WebDriverCapabilities|null $capabilities - * @param bool $w3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec + * @param bool $isW3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec */ protected function __construct( HttpCommandExecutor $commandExecutor, $sessionId, WebDriverCapabilities $capabilities = null, - $w3cCompliant = false + $isW3cCompliant = false ) { $this->executor = $commandExecutor; $this->sessionID = $sessionId; - $this->w3cCompliant = $w3cCompliant; + $this->isW3cCompliant = $isW3cCompliant; if ($capabilities !== null) { $this->capabilities = $capabilities; @@ -168,7 +168,7 @@ public static function create( $value = $response->getValue(); if (!$w3c_compliant = isset($value['capabilities'])) { - $executor->disableW3CCompliance(); + $executor->disableW3cCompliance(); } $returnedCapabilities = new DesiredCapabilities($w3c_compliant ? $value['capabilities'] : $value); @@ -232,7 +232,7 @@ public function findElement(WebDriverBy $by) { $raw_element = $this->execute( DriverCommand::FIND_ELEMENT, - JsonWireCompat::getUsing($by, $this->w3cCompliant) + JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); return $this->newElement(JsonWireCompat::getElement($raw_element)); @@ -249,7 +249,7 @@ public function findElements(WebDriverBy $by) { $raw_elements = $this->execute( DriverCommand::FIND_ELEMENTS, - JsonWireCompat::getUsing($by, $this->w3cCompliant) + JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); $elements = []; @@ -428,7 +428,7 @@ public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) */ public function manage() { - return new WebDriverOptions($this->getExecuteMethod(), $this->w3cCompliant); + return new WebDriverOptions($this->getExecuteMethod(), $this->isW3cCompliant); } /** @@ -459,7 +459,7 @@ public function switchTo() public function getMouse() { if (!$this->mouse) { - $this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->w3cCompliant); + $this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->isW3cCompliant); } return $this->mouse; @@ -605,7 +605,7 @@ public function execute($command_name, $params = []) */ public function isW3cCompliant() { - return $this->w3cCompliant; + return $this->isW3cCompliant; } /** @@ -620,10 +620,12 @@ protected function prepareScriptArguments(array $arguments) foreach ($arguments as $key => $value) { if ($value instanceof WebDriverElement) { $args[$key] = [ - $this->w3cCompliant ? JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER : 'ELEMENT' => $value->getID(), + $this->isW3cCompliant ? + JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER + : 'ELEMENT' => $value->getID(), ]; } else { - if (\is_array($value)) { + if (is_array($value)) { $value = $this->prepareScriptArguments($value); } $args[$key] = $value; @@ -653,7 +655,7 @@ protected function getExecuteMethod() */ protected function newElement($id) { - return new RemoteWebElement($this->getExecuteMethod(), $id, $this->w3cCompliant); + return new RemoteWebElement($this->getExecuteMethod(), $id, $this->isW3cCompliant); } /** @@ -669,7 +671,7 @@ protected static function castToDesiredCapabilitiesObject($desired_capabilities return new DesiredCapabilities(); } - if (\is_array($desired_capabilities)) { + if (is_array($desired_capabilities)) { return new DesiredCapabilities($desired_capabilities); } diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index b07b18227..75dfa3ba2 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -46,19 +46,19 @@ class RemoteWebElement implements WebDriverElement, WebDriverLocatable /** * @var bool */ - protected $w3cCompliant; + protected $isW3cCompliant; /** * @param RemoteExecuteMethod $executor * @param string $id - * @param bool $w3cCompliant + * @param bool $isW3cCompliant */ - public function __construct(RemoteExecuteMethod $executor, $id, $w3cCompliant = false) + public function __construct(RemoteExecuteMethod $executor, $id, $isW3cCompliant = false) { $this->executor = $executor; $this->id = $id; $this->fileDetector = new UselessFileDetector(); - $this->w3cCompliant = $w3cCompliant; + $this->isW3cCompliant = $isW3cCompliant; } /** @@ -73,20 +73,22 @@ public function clear() [':id' => $this->id] ); - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ - 'actions' => [[ - 'type' => 'key', - 'id' => 'keyboard', - 'actions' => [ - ['type' => 'keyDown' , 'value' => WebDriverKeys::CONTROL], - ['type' => 'keyDown' , 'value' => 'a'], - ['type' => 'keyUp' , 'value' => WebDriverKeys::CONTROL], - ['type' => 'keyUp' , 'value' => 'a'], - ['type' => 'keyDown' , 'value' => WebDriverKeys::BACKSPACE], - ['type' => 'keyUp' , 'value' => WebDriverKeys::BACKSPACE], + 'actions' => [ + [ + 'type' => 'key', + 'id' => 'keyboard', + 'actions' => [ + ['type' => 'keyDown', 'value' => WebDriverKeys::CONTROL], + ['type' => 'keyDown', 'value' => 'a'], + ['type' => 'keyUp', 'value' => WebDriverKeys::CONTROL], + ['type' => 'keyUp', 'value' => 'a'], + ['type' => 'keyDown', 'value' => WebDriverKeys::BACKSPACE], + ['type' => 'keyUp', 'value' => WebDriverKeys::BACKSPACE], + ], ], - ]], + ], ]); } @@ -118,7 +120,7 @@ public function click() */ public function findElement(WebDriverBy $by) { - $params = JsonWireCompat::getUsing($by, $this->w3cCompliant); + $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_element = $this->executor->execute( @@ -139,7 +141,7 @@ public function findElement(WebDriverBy $by) */ public function findElements(WebDriverBy $by) { - $params = JsonWireCompat::getUsing($by, $this->w3cCompliant); + $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_elements = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENTS, @@ -167,7 +169,7 @@ public function getAttribute($attribute_name) ':id' => $this->id, ]; - if ($this->w3cCompliant && ($attribute_name === 'value' || $attribute_name === 'index')) { + if ($this->isW3cCompliant && ($attribute_name === 'value' || $attribute_name === 'index')) { $value = $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); if ($value === true) { @@ -357,7 +359,7 @@ public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); if ($local_file === null) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $params = [ 'text' => (string) $value, ':id' => $this->id, @@ -374,7 +376,7 @@ public function sendKeys($value) return $this; } - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $params = [ 'text' => $local_file, ':id' => $this->id, @@ -420,7 +422,7 @@ public function setFileDetector(FileDetector $detector) */ public function submit() { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [ // cannot call the submit method directly in case an input of this form is named "submit" 'script' => sprintf( @@ -459,7 +461,7 @@ public function getID() */ public function equals(WebDriverElement $other) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { throw new UnsupportedOperationException('"elementEquals" is not supported by the W3C specification'); } @@ -478,7 +480,7 @@ public function equals(WebDriverElement $other) */ protected function newElement($id) { - return new static($this->executor, $id, $this->w3cCompliant); + return new static($this->executor, $id, $this->isW3cCompliant); } /** diff --git a/lib/WebDriverOptions.php b/lib/WebDriverOptions.php index 4c584bd07..682918a17 100644 --- a/lib/WebDriverOptions.php +++ b/lib/WebDriverOptions.php @@ -31,12 +31,12 @@ class WebDriverOptions /** * @var bool */ - protected $w3cCompliant; + protected $isW3cCompliant; - public function __construct(ExecuteMethod $executor, $w3cCompliant = false) + public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; - $this->w3cCompliant = $w3cCompliant; + $this->isW3cCompliant = $isW3cCompliant; } /** @@ -133,7 +133,7 @@ public function getCookies() */ public function timeouts() { - return new WebDriverTimeouts($this->executor, $this->w3cCompliant); + return new WebDriverTimeouts($this->executor, $this->isW3cCompliant); } /** diff --git a/lib/WebDriverTimeouts.php b/lib/WebDriverTimeouts.php index eeba9a10e..5735cb48d 100644 --- a/lib/WebDriverTimeouts.php +++ b/lib/WebDriverTimeouts.php @@ -30,12 +30,12 @@ class WebDriverTimeouts /** * @var bool */ - protected $w3cCompliant; + protected $isW3cCompliant; - public function __construct(ExecuteMethod $executor, $w3cCompliant = false) + public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; - $this->w3cCompliant = $w3cCompliant; + $this->isW3cCompliant = $isW3cCompliant; } /** @@ -46,7 +46,7 @@ public function __construct(ExecuteMethod $executor, $w3cCompliant = false) */ public function implicitlyWait($seconds) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['implicit' => $seconds * 1000] @@ -71,7 +71,7 @@ public function implicitlyWait($seconds) */ public function setScriptTimeout($seconds) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['script' => $seconds * 1000] @@ -96,7 +96,7 @@ public function setScriptTimeout($seconds) */ public function pageLoadTimeout($seconds) { - if ($this->w3cCompliant) { + if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['pageLoad' => $seconds * 1000] From bd34170cb3c17322dbc785fede30b6c88ffd651c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 28 Oct 2019 18:21:02 +0100 Subject: [PATCH 146/487] Compatibility with Symfony 5 --- CHANGELOG.md | 1 + composer.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e733516..e2662351d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ### Changed - Revert no longer needed workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943). +- Allow installation of Symfony 5 components. ## 1.7.1 - 2019-06-13 ### Fixed diff --git a/composer.json b/composer.json index 2a9929725..3a841a504 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "minimum-stability": "beta", "require": { "php": "^5.6 || ~7.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0", "ext-curl": "*", "ext-zip": "*", "ext-mbstring": "*", @@ -26,7 +26,7 @@ "squizlabs/php_codesniffer": "^2.6", "php-mock/php-mock-phpunit": "^1.1", "php-coveralls/php-coveralls": "^2.0", - "symfony/var-dumper": "^3.3 || ^4.0", + "symfony/var-dumper": "^3.3 || ^4.0 || ^5.0", "jakub-onderka/php-parallel-lint": "^0.9.2" }, "suggest": { From e9277912d553971c2b73bf19df3fb44323f1f8b7 Mon Sep 17 00:00:00 2001 From: Mohammadreza Yektamaram Date: Tue, 1 Oct 2019 14:51:24 +0330 Subject: [PATCH 147/487] Minor grammar fixes --- README.md | 2 +- example.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b25bee453..0fa9db31c 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ We have a great community willing to help you! - **Via our Facebook Group** - If you have questions or are an active contributor consider joining our [facebook group](https://www.facebook.com/groups/phpwebdriver/) and contribute to communal discussion and support - **Via StackOverflow** - You can also [ask a question](https://stackoverflow.com/questions/ask?tags=php+selenium-webdriver) or find many already answered question on StackOverflow -- **Via GitHub** - Another option if you have a question (or bug report) is to [submit it here](https://github.com/facebook/php-webdriver/issues/new) as an new issue +- **Via GitHub** - Another option if you have a question (or bug report) is to [submit it here](https://github.com/facebook/php-webdriver/issues/new) as a new issue ## Contributing diff --git a/example.php b/example.php index f1109ed54..4dbb93900 100644 --- a/example.php +++ b/example.php @@ -23,7 +23,7 @@ require_once('vendor/autoload.php'); -// start Chrome with 5 second timeout +// start Chrome with 5 seconds timeout $host = 'http://localhost:4444/wd/hub'; // this is the default $capabilities = DesiredCapabilities::chrome(); $driver = RemoteWebDriver::create($host, $capabilities, 5000); From a21b3c82dabb8a777e81aed5a8239cba67efc115 Mon Sep 17 00:00:00 2001 From: "Benjamin R. White" Date: Tue, 27 Feb 2018 17:21:16 +0000 Subject: [PATCH 148/487] Allow combined not() and presenceOfElementLocated() functionality --- CHANGELOG.md | 3 +++ lib/WebDriverExpectedCondition.php | 9 ++++--- tests/unit/WebDriverExpectedConditionTest.php | 24 ++++++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2662351d..bbd2f4d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). - Revert no longer needed workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943). - Allow installation of Symfony 5 components. +### Fixed +- `WebDriverExpectedCondition::presenceOfElementLocated()` works correctly when used within `WebDriverExpectedCondition::not()`. + ## 1.7.1 - 2019-06-13 ### Fixed - Error `Call to a member function toArray()` if capabilities were already converted to an array. diff --git a/lib/WebDriverExpectedCondition.php b/lib/WebDriverExpectedCondition.php index 15c7b3d2d..4bab4707b 100644 --- a/lib/WebDriverExpectedCondition.php +++ b/lib/WebDriverExpectedCondition.php @@ -148,7 +148,11 @@ public static function presenceOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { - return $driver->findElement($by); + try { + return $driver->findElement($by); + } catch (NoSuchElementException $e) { + return false; + } } ); } @@ -423,8 +427,7 @@ function (WebDriver $driver) use ($by, $text) { */ public static function elementToBeClickable(WebDriverBy $by) { - $visibility_of_element_located = - self::visibilityOfElementLocated($by); + $visibility_of_element_located = self::visibilityOfElementLocated($by); return new static( function (WebDriver $driver) use ($visibility_of_element_located) { diff --git a/tests/unit/WebDriverExpectedConditionTest.php b/tests/unit/WebDriverExpectedConditionTest.php index 8cd4203a9..6dde484ef 100644 --- a/tests/unit/WebDriverExpectedConditionTest.php +++ b/tests/unit/WebDriverExpectedConditionTest.php @@ -135,6 +135,28 @@ public function testShouldDetectPresenceOfElementLocatedCondition() $this->assertSame($element, $this->wait->until($condition)); } + public function testShouldDetectNotPresenceOfElementLocatedCondition() + { + $element = new RemoteWebElement(new RemoteExecuteMethod($this->driverMock), 'id'); + + $this->driverMock->expects($this->at(0)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willReturn($element); + + $this->driverMock->expects($this->at(1)) + ->method('findElement') + ->with($this->isInstanceOf(WebDriverBy::class)) + ->willThrowException(new NoSuchElementException('')); + + $condition = WebDriverExpectedCondition::not( + WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('.foo')) + ); + + $this->assertFalse(call_user_func($condition->getApply(), $this->driverMock)); + $this->assertTrue(call_user_func($condition->getApply(), $this->driverMock)); + } + public function testShouldDetectPresenceOfAllElementsLocatedByCondition() { $element = $this->createMock(RemoteWebElement::class); @@ -160,7 +182,7 @@ public function testShouldDetectVisibilityOfElementLocatedCondition() // Call #1: throws NoSuchElementException // Call #2: return Element, but isDisplayed will throw StaleElementReferenceException // Call #3: return Element, but isDisplayed will return false - // Call #4: return Element, isDisplayed will true and condition will match + // Call #4: return Element, isDisplayed will return true and condition will match $element = $this->createMock(RemoteWebElement::class); $element->expects($this->at(0)) From 1f8d242f98310b12d8dbfc39859aad6b13f35708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 1 Oct 2018 12:16:55 +0200 Subject: [PATCH 149/487] Use polyfill-mbstring when ext-mbstring isn't available --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3a841a504..e5aef5a03 100644 --- a/composer.json +++ b/composer.json @@ -14,9 +14,9 @@ "require": { "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0", + "symfony/polyfill-mbstring": "^1.12", "ext-curl": "*", "ext-zip": "*", - "ext-mbstring": "*", "ext-json": "*" }, "require-dev": { From dc50f3f0fd5506230f4fcc48670bf1edcf1d65ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 00:04:33 +0100 Subject: [PATCH 150/487] Skipped tests must be completely excluded from Saucelabs to avoid starting the driver there, otherwise the build is not marked passed on Saucelabs dashboard --- tests/functional/RemoteWebDriverFindElementTest.php | 3 +++ tests/functional/WebDriverActionsTest.php | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 066179f1f..2eb3e38c7 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -62,6 +62,9 @@ public function testShouldFindMultipleElements() $this->assertContainsOnlyInstancesOf(RemoteWebElement::class, $elements); } + /** + * @group exclude-saucelabs + */ public function testEscapeCssSelector() { if (getenv('GECKODRIVER') !== '1') { diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 1f03c8899..07af88085 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -88,6 +88,7 @@ public function testShouldClickAndHoldOnElementAndRelease() } /** + * @group exclude-saucelabs * @covers ::__construct * @covers ::contextClick * @covers ::perform From a29494a551b8efb8afef51dc4f7af2cb7af98d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 10 Nov 2019 23:01:03 +0100 Subject: [PATCH 151/487] Add Chromedriver Travis build (without Selenium server proxy) --- .travis.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 950e8fe64..986df3e31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,8 @@ env: matrix: include: # Codestyle check build - - php: '7.3' + - name: 'Code style and static analysis' + php: '7.3' env: CHECK_CODESTYLE=1 before_install: - phpenv config-rm xdebug.ini @@ -30,18 +31,19 @@ matrix: after_success: ~ # Build with lowest possible dependencies on lowest possible PHP - - php: '5.6' + - name: 'Lowest dependencies build' + php: '5.6' env: DEPENDENCIES="--prefer-lowest" # Firefox inside Travis environment - - name: 'Firefox 45 on Travis (OSS protocol)' + - name: 'Firefox 45 on Travis (OSS protocol); via Selenium server' php: '7.3' env: BROWSER_NAME="firefox" addons: firefox: "45.8.0esr" # Firefox with Geckodriver (W3C mode) inside Travis environment - - name: 'Firefox latest on Travis (W3C protocol)' + - name: 'Firefox latest on Travis (W3C protocol); no Selenium server' php: 7.3 env: - BROWSER_NAME="firefox" @@ -49,9 +51,22 @@ matrix: addons: firefox: latest - # Stable Chrome + Chromedriver inside Travis environment - - php: '7.3' - env: BROWSER_NAME="chrome" CHROME_HEADLESS="1" + # Stable Chrome + Chromedriver inside Travis environment via Selenium server proxy + - name: 'Chrome stable on Travis; via Selenium server' + php: '7.3' + env: + - BROWSER_NAME="chrome" + - CHROME_HEADLESS="1" + addons: + chrome: stable + + # Stable Chrome + Chromedriver inside Travis environment directly via Chromedriver + - name: 'Chrome stable on Travis; no Selenium server' + php: '7.3' + env: + - BROWSER_NAME="chrome" + - CHROME_HEADLESS="1" + - CHROMEDRIVER="1" addons: chrome: stable @@ -113,6 +128,8 @@ before_script: - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; fi - if [ "$GECKODRIVER" = "1" ]; then geckodriver/geckodriver &> ./logs/geckodriver.log & + elif [ "$CHROMEDRIVER" = "1" ]; then + chromedriver/chromedriver --port=4444 --url-base=/wd/hub &> ./logs/chromedriver.log & else java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.8.1.jar -enablePassThrough false -log ./logs/selenium.log & fi @@ -130,6 +147,7 @@ after_script: - if [ -f ./logs/selenium.log ]; then cat ./logs/selenium.log; fi - if [ -f ./logs/php-server.log ]; then cat ./logs/php-server.log; fi - if [ -f ./logs/geckodriver.log ]; then cat ./logs/geckodriver.log; fi + - if [ -f ./logs/chromedriver.log ]; then cat ./logs/chromedriver.log; fi after_success: - travis_retry php vendor/bin/php-coveralls -v From 61ab881143846201c314bc9f9988a8831a75f26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 10 Nov 2019 23:51:18 +0100 Subject: [PATCH 152/487] Use newer Selenium server where possible But we can easily update only to 3.14.0 (latest version which includes HtmlUnit). However update to 3.14.159 should be done soon (but HtmlUnit needs to be sideloaded). --- .travis.yml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 986df3e31..fc7368965 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ env: global: - 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 matrix: include: @@ -36,9 +37,11 @@ matrix: env: DEPENDENCIES="--prefer-lowest" # Firefox inside Travis environment - - name: 'Firefox 45 on Travis (OSS protocol); via Selenium server' + - name: 'Firefox 45 on Travis (OSS protocol); via legacy Selenium server' php: '7.3' - env: BROWSER_NAME="firefox" + env: + - BROWSER_NAME="firefox" + - SELENIUM_SERVER="legacy" addons: firefox: "45.8.0esr" @@ -114,7 +117,6 @@ matrix: cache: directories: - $HOME/.composer/cache - - jar install: - travis_retry composer self-update @@ -123,15 +125,24 @@ install: before_script: - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROMEDRIVER_VERSION=$(wget -qO- "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi - if [ "$BROWSER_NAME" = "chrome" ]; then export CHROMEDRIVER_PATH=$PWD/chromedriver/chromedriver; fi - - if [ "$GECKODRIVER" = "1" ]; then mkdir 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 - - sh -e /etc/init.d/xvfb start - - if [ ! -f jar/selenium-server-standalone-3.8.1.jar ]; then wget -q -t 3 -P jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; 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 + - sh -e /etc/init.d/xvfb start # TODO: start only when needed (ie. not in headless mode) + - if [ ! -f jar/selenium-server-standalone.jar ] && [ -n "$SELENIUM_SERVER" ]; then + mkdir -p jar; + if [ "$SELENIUM_SERVER" = "legacy" ]; then + wget -q -t 3 -O jar/selenium-server-standalone.jar https://selenium-release.storage.googleapis.com/3.8/selenium-server-standalone-3.8.1.jar; + else + wget -q -t 3 -O jar/selenium-server-standalone.jar $SELENIUM_SERVER; + fi + fi - if [ "$GECKODRIVER" = "1" ]; then geckodriver/geckodriver &> ./logs/geckodriver.log & elif [ "$CHROMEDRIVER" = "1" ]; then chromedriver/chromedriver --port=4444 --url-base=/wd/hub &> ./logs/chromedriver.log & + elif [ "$SELENIUM_SERVER" = "legacy" ]; then + java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$PWD/chromedriver/chromedriver" -jar jar/selenium-server-standalone.jar -enablePassThrough false -log ./logs/selenium.log & else - java -Dwebdriver.firefox.marionette=false -Dwebdriver.chrome.driver="$CHROMEDRIVER_PATH" -jar jar/selenium-server-standalone-3.8.1.jar -enablePassThrough false -log ./logs/selenium.log & + java -Dwebdriver.chrome.driver="$PWD/chromedriver/chromedriver" -Dwebdriver.gecko.driver="$PWD/geckodriver/geckodriver" -jar jar/selenium-server-standalone.jar -log ./logs/selenium.log & fi - until $(echo | nc localhost 4444); do sleep 1; echo Waiting for Selenium server on port 4444...; done; echo "Selenium server started" - php -S 127.0.0.1:8000 -t tests/functional/web/ &>>./logs/php-server.log & From a4b0d7130ed35c2083f62ae0bb135c563df68364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 10:21:11 +0100 Subject: [PATCH 153/487] Convert W3C invalid capabilities to corresponding ones This avoids `Facebook\WebDriver\Exception\UnknownServerException: Illegal key values seen in w3c capabilities: [chromeOptions]` error with new Selenium server. This conversion is necessary to maintain BC - simply renaming chromeOptions to goog:chromeOptions will break capabilities with Chromedriver <2.31. --- lib/Chrome/ChromeOptions.php | 7 ++++++- lib/Remote/RemoteWebDriver.php | 32 ++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/Chrome/ChromeOptions.php b/lib/Chrome/ChromeOptions.php index 901dc5388..949d6708b 100644 --- a/lib/Chrome/ChromeOptions.php +++ b/lib/Chrome/ChromeOptions.php @@ -25,9 +25,14 @@ class ChromeOptions { /** - * The key of chrome options in desired capabilities. + * The key of chrome options desired capabilities (in legacy OSS JsonWire protocol) + * @deprecated */ const CAPABILITY = 'chromeOptions'; + /** + * The key of chrome options desired capabilities (in W3C compatible protocol) + */ + const CAPABILITY_W3C = 'goog:chromeOptions'; /** * @var array */ diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 4d3573c0c..c87a572f9 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -140,15 +140,15 @@ public static function create( // W3C $parameters = [ 'capabilities' => [ - 'firstMatch' => [$desired_capabilities->toArray()], + 'firstMatch' => [static::convertCapabilitiesToW3c($desired_capabilities->toArray())], ], ]; - // Legacy protocol - if (null !== $required_capabilities && $required_capabilities_array = $required_capabilities->toArray()) { - $parameters['capabilities']['alwaysMatch'] = $required_capabilities_array; + if ($required_capabilities !== null && $required_capabilities_array = $required_capabilities->toArray()) { + $parameters['capabilities']['alwaysMatch'] = static::convertCapabilitiesToW3c($required_capabilities_array); } + // Legacy protocol if ($required_capabilities !== null) { // 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 @@ -677,4 +677,28 @@ protected static function castToDesiredCapabilitiesObject($desired_capabilities return $desired_capabilities; } + + /** + * Convert keys invalid in W3C capabilities to corresponding ones for W3C. + * + * @param array $capabilitiesArray + * @return array + */ + protected static function convertCapabilitiesToW3c(array $capabilitiesArray) + { + if (array_key_exists(ChromeOptions::CAPABILITY, $capabilitiesArray)) { + if (array_key_exists(ChromeOptions::CAPABILITY_W3C, $capabilitiesArray)) { + $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] = array_merge( + $capabilitiesArray[ChromeOptions::CAPABILITY], + $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] + ); + } else { + $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] = $capabilitiesArray[ChromeOptions::CAPABILITY]; + } + + unset($capabilitiesArray[ChromeOptions::CAPABILITY]); + } + + return $capabilitiesArray; + } } From 3597a6bcaddb2a462d71cf81620346892991ad47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 10:37:25 +0100 Subject: [PATCH 154/487] =?UTF-8?q?Enable=20W3C=20protocol=20for=20Chromed?= =?UTF-8?q?river=20=F0=9F=8E=89=20#469?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now, W3C was forcily disabled for Chromedriver using Capabilities on startup. As we now experimentaly support W3C protocol, this is no longer needed. If W3C is detected during initial handshake, the browser will be started using W3C dialect (otherwise it will use legacy OSS JsonWire protocol). --- lib/Remote/RemoteWebDriver.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index c87a572f9..d2e302f44 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -112,23 +112,6 @@ public static function create( $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); - // Hotfix: W3C WebDriver protocol is not yet supported by php-webdriver, so we must force Chromedriver to - // not use the W3C protocol by default (which is what Chromedriver does starting with version 75). - if ($desired_capabilities->getBrowserName() === WebDriverBrowserType::CHROME - && mb_strpos($selenium_server_url, 'browserstack') === false // see https://github.com/facebook/php-webdriver/issues/644 - ) { - $currentChromeOptions = $desired_capabilities->getCapability(ChromeOptions::CAPABILITY); - $chromeOptions = !empty($currentChromeOptions) ? $currentChromeOptions : new ChromeOptions(); - - if ($chromeOptions instanceof ChromeOptions && !isset($chromeOptions->toArray()['w3c'])) { - $chromeOptions->setExperimentalOption('w3c', false); - } elseif (is_array($chromeOptions) && !isset($chromeOptions['w3c'])) { - $chromeOptions['w3c'] = false; - } - - $desired_capabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); - } - $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); From a9685bf2aa22de07d97ca93ff452e5016ee3141d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 12:41:45 +0100 Subject: [PATCH 155/487] Add JsonWire travis build on Chromedriver to make sure we won't break the old protocol --- .travis.yml | 19 +++++++++++++++---- tests/functional/WebDriverTestCase.php | 5 +++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc7368965..4a8a268a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,8 +54,8 @@ matrix: addons: firefox: latest - # Stable Chrome + Chromedriver inside Travis environment via Selenium server proxy - - name: 'Chrome stable on Travis; via Selenium server' + # Stable Chrome + Chromedriver (W3C mode) inside Travis environment via Selenium server proxy + - name: 'Chrome stable on Travis (W3C protocol); via Selenium server' php: '7.3' env: - BROWSER_NAME="chrome" @@ -63,8 +63,8 @@ matrix: addons: chrome: stable - # Stable Chrome + Chromedriver inside Travis environment directly via Chromedriver - - name: 'Chrome stable on Travis; no Selenium server' + # Stable Chrome + Chromedriver (W3C mode) inside Travis environment directly via Chromedriver + - name: 'Chrome stable on Travis (W3C protocol); no Selenium server' php: '7.3' env: - BROWSER_NAME="chrome" @@ -73,6 +73,17 @@ matrix: addons: chrome: stable + # Stable Chrome + Chromedriver (JsonWire OSS mode) inside Travis environment directly via Chromedriver + - name: 'Chrome stable on Travis (OSS protocol); no Selenium server' + php: '7.3' + env: + - BROWSER_NAME="chrome" + - CHROME_HEADLESS="1" + - CHROMEDRIVER="1" + - DISABLE_W3C_PROTOCOL="1" + addons: + chrome: stable + # Saucelabs builds - php: '7.3' env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 7a0f8af23..920eb86a7 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -57,6 +57,11 @@ protected function setUp() $chromeOptions = new ChromeOptions(); // --no-sandbox is a workaround for Chrome crashing: https://github.com/SeleniumHQ/selenium/issues/4961 $chromeOptions->addArguments(['--headless', 'window-size=1024,768', '--no-sandbox']); + + if (getenv('DISABLE_W3C_PROTOCOL')) { + $chromeOptions->setExperimentalOption('w3c', false); + } + $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); } elseif (getenv('GECKODRIVER') === '1') { $this->serverUrl = 'http://localhost:4444/'; From 041658be9e1284578f5fba1198b10dc8922ceb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 13:25:45 +0100 Subject: [PATCH 156/487] Improve W3C builds detection in conditionally-run tests --- .../RemoteWebDriverFindElementTest.php | 8 +++--- tests/functional/RemoteWebDriverTest.php | 1 - tests/functional/RemoteWebElementTest.php | 4 +-- tests/functional/WebDriverActionsTest.php | 4 +-- tests/functional/WebDriverTestCase.php | 26 +++++++++++++++++++ 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/tests/functional/RemoteWebDriverFindElementTest.php b/tests/functional/RemoteWebDriverFindElementTest.php index 2eb3e38c7..c5671b497 100644 --- a/tests/functional/RemoteWebDriverFindElementTest.php +++ b/tests/functional/RemoteWebDriverFindElementTest.php @@ -67,11 +67,9 @@ public function testShouldFindMultipleElements() */ public function testEscapeCssSelector() { - if (getenv('GECKODRIVER') !== '1') { - $this->markTestSkipped( - 'CSS selectors containing special characters are not supported by the legacy protocol' - ); - } + self::skipForJsonWireProtocol( + 'CSS selectors containing special characters are not supported by the legacy protocol' + ); $this->driver->get($this->getTestPageUrl('escape_css.html')); diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 9105aa93f..72fc30505 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -150,7 +150,6 @@ public function testShouldCloseWindow() $this->driver->get($this->getTestPageUrl('open_new_window.html')); $this->driver->findElement(WebDriverBy::cssSelector('a'))->click(); - // Mandatory for Geckodriver $this->driver->wait()->until(WebDriverExpectedCondition::numberOfWindowsToBe(2)); $this->assertCount(2, $this->driver->getWindowHandles()); diff --git a/tests/functional/RemoteWebElementTest.php b/tests/functional/RemoteWebElementTest.php index a4684ace3..52877dcda 100644 --- a/tests/functional/RemoteWebElementTest.php +++ b/tests/functional/RemoteWebElementTest.php @@ -272,9 +272,7 @@ public function testShouldSubmitFormByClickOnSubmitInput() */ public function testShouldCompareEqualsElement() { - if (getenv('GECKODRIVER') === '1') { - $this->markTestSkipped('"equals" is not supported by the W3C specification'); - } + self::skipForW3cProtocol('"equals" is not supported by the W3C specification'); $this->driver->get($this->getTestPageUrl('index.html')); diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 07af88085..9798f6e5a 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -49,7 +49,7 @@ public function testShouldClickOnElement() $logs = ['mouseover item-1', 'mousedown item-1', 'mouseup item-1', 'click item-1']; $loggedEvents = $this->retrieveLoggedEvents(); - if ('1' === getenv('GECKODRIVER')) { + if (getenv('GECKODRIVER') === '1') { $loggedEvents = array_slice($loggedEvents, 0, count($logs)); // Firefox sometimes triggers some extra events // it's not related to Geckodriver, it's Firefox's own behavior @@ -77,7 +77,7 @@ public function testShouldClickAndHoldOnElementAndRelease() ->release() ->perform(); - if ('1' === getenv('GECKODRIVER')) { + if (self::isW3cProtocolBuild()) { $this->assertArraySubset(['mouseover item-1', 'mousedown item-1'], $this->retrieveLoggedEvents()); } else { $this->assertSame( diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 920eb86a7..8b35a7700 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -102,6 +102,32 @@ public static function isSauceLabsBuild() return getenv('SAUCELABS') ? true : false; } + /** + * @return bool + */ + public static function isW3cProtocolBuild() + { + return getenv('GECKODRIVER') === '1' + || (getenv('BROWSER_NAME') === 'chrome' + && getenv('DISABLE_W3C_PROTOCOL') !== '1' + && !self::isSauceLabsBuild()); + } + + public static function skipForW3cProtocol($message = 'Not supported by W3C specification') + { + if (static::isW3cProtocolBuild()) { + static::markTestSkipped($message); + } + } + + public static function skipForJsonWireProtocol($message = 'Not supported by JsonWire protocol') + { + if (getenv('GECKODRIVER') !== '1' + && (getenv('CHROMEDRIVER') !== '1' || getenv('DISABLE_W3C_PROTOCOL') === '1')) { + static::markTestSkipped($message); + } + } + /** * Get the URL of given test HTML on running webserver. * From d249dea3f0da638c2d44c028b4a5d9247cc2fa87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 11 Nov 2019 16:36:05 +0100 Subject: [PATCH 157/487] Update changelog and readme to let the world know about W3C protocol experimental support --- CHANGELOG.md | 3 +++ README.md | 38 ++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd2f4d52..b83a21743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +### Added +- Experimental W3C WebDriver protocol support. The protocol will be used automatically when remote end (like Geckodriver, newer Chromedriver etc.) supports it. + ### Changed - Revert no longer needed workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943). - Allow installation of Symfony 5 components. diff --git a/README.md b/README.md index 0fa9db31c..71742583b 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,16 @@ ## Description Php-webdriver library is PHP language binding for Selenium WebDriver, which allows you to control web browsers from PHP. -This library is compatible with Selenium server version 2.x and 3.x. -It implements the [JsonWireProtocol](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol), which is currently supported -by the Selenium server and will also implement the [W3C WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html) specification in the future. +This library is compatible with Selenium server version 2.x, 3.x and 4.x. + +The library supports [JsonWireProtocol](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol) and also +implements **experimental support** of [W3C WebDriver](https://w3c.github.io/webdriver/webdriver-spec.html). +The W3C WebDriver support is not yet full-featured, however it should allow to control Firefox via Geckodriver and new +versions of Chrome and Chromedriver with just a slight limitations. The concepts of this library are very similar to the "official" Java, .NET, Python and Ruby bindings from the [Selenium project](https://github.com/SeleniumHQ/selenium/). -**As of 2013, this PHP client has been rewritten from scratch.** -Using the old version? Check out [Adam Goucher's fork](https://github.com/Element-34/php-webdriver) of it. - Looking for API documentation of php-webdriver? See [https://facebook.github.io/php-webdriver/](https://facebook.github.io/php-webdriver/latest/) Any complaints, questions, or ideas? Post them in the user group https://www.facebook.com/groups/phpwebdriver/. @@ -41,24 +41,25 @@ Then install the library: The required server is the `selenium-server-standalone-#.jar` file provided here: http://selenium-release.storage.googleapis.com/index.html -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**. +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**. java -jar selenium-server-standalone-#.jar -**NOTE:** If using Firefox, see alternate command below. - ### Create a Browser Session When creating a browser session, be sure to pass the url of your running server. ```php // This would be the url of the host running the server-standalone.jar -$host = 'http://localhost:4444/wd/hub'; // this is the default +$host = 'http://localhost:4444/wd/hub'; // this is the default url and port where Selenium server starts ``` ##### Launch Chrome -Make sure to have latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) versions installed. +Install latest Chrome and [Chromedriver](https://sites.google.com/a/chromium.org/chromedriver/downloads). + +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`). ```php $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); @@ -66,14 +67,11 @@ $driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome()); ##### Launch Firefox -Make sure to have latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases) installed. - -Because Firefox (and Geckodriver) only support the new W3C WebDriver protocol (which is yet to be implemented by php-webdriver - see [issue #469](https://github.com/facebook/php-webdriver/issues/469)), -the protocols must be translated by Selenium Server - this feature is *partially* available in Selenium Server versions 3.5.0-3.8.1 and you can enable it like this: +Install latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases). - java -jar selenium-server-standalone-3.8.1.jar -enablePassThrough false +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`). -Now you can start Firefox from your code: ```php $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); @@ -82,9 +80,9 @@ $driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox()); ### Customize Desired Capabilities ```php -$desired_capabilities = DesiredCapabilities::firefox(); -$desired_capabilities->setCapability('acceptSslCerts', false); -$driver = RemoteWebDriver::create($host, $desired_capabilities); +$desiredCapabilities = DesiredCapabilities::firefox(); +$desiredCapabilities->setCapability('acceptSslCerts', false); +$driver = RemoteWebDriver::create($host, $desiredCapabilities); ``` * See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities for more details. From 42f82dd233ea52f64f36f143a57c6d6df89c22c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 12 Nov 2019 23:54:22 +0100 Subject: [PATCH 158/487] Define branch alias for version 1.8 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e5aef5a03..a6aec98ae 100644 --- a/composer.json +++ b/composer.json @@ -59,7 +59,7 @@ }, "extra": { "branch-alias": { - "dev-community": "1.5-dev" + "dev-community": "1.8.x-dev" } } } From a6cad80b88fcb3c3823afe85d653d0c46c05944c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Thu, 14 Nov 2019 19:10:16 +0100 Subject: [PATCH 159/487] Add convertor to W3C compatible capabilities (fixes #676, part of #469) --- lib/Remote/DesiredCapabilities.php | 73 ++++++++++ lib/Remote/RemoteWebDriver.php | 31 +--- tests/unit/Remote/DesiredCapabilitiesTest.php | 137 ++++++++++++++++++ 3 files changed, 213 insertions(+), 28 deletions(-) diff --git a/lib/Remote/DesiredCapabilities.php b/lib/Remote/DesiredCapabilities.php index 533c8873a..c78d2302b 100644 --- a/lib/Remote/DesiredCapabilities.php +++ b/lib/Remote/DesiredCapabilities.php @@ -178,6 +178,79 @@ public function toArray() return $this->capabilities; } + /** + * @return array + */ + public function toW3cCompatibleArray() + { + $ossToW3c = [ + WebDriverCapabilityType::PLATFORM => 'platformName', + WebDriverCapabilityType::VERSION => 'browserVersion', + WebDriverCapabilityType::ACCEPT_SSL_CERTS => 'acceptInsecureCerts', + ChromeOptions::CAPABILITY => ChromeOptions::CAPABILITY_W3C, + ]; + + $allowedW3cCapabilities = [ + 'browserName', + 'browserVersion', + 'platformName', + 'acceptInsecureCerts', + 'pageLoadStrategy', + 'proxy', + 'setWindowRect', + 'timeouts', + 'strictFileInteractability', + 'unhandledPromptBehavior', + ]; + + $ossCapabilities = $this->toArray(); + $w3cCapabilities = []; + + foreach ($ossCapabilities as $capabilityKey => $capabilityValue) { + // Copy already W3C compatible capabilities + if (in_array($capabilityKey, $allowedW3cCapabilities, true)) { + $w3cCapabilities[$capabilityKey] = $capabilityValue; + } + + // Convert capabilitites with changed name + if (array_key_exists($capabilityKey, $ossToW3c)) { + if ($capabilityKey === 'platform') { + $w3cCapabilities[$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue); + } else { + $w3cCapabilities[$ossToW3c[$capabilityKey]] = $capabilityValue; + } + } + + // Copy vendor extensions + if (mb_strpos($capabilityKey, ':') !== false) { + $w3cCapabilities[$capabilityKey] = $capabilityValue; + } + } + + // 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] + ); + } else { + $w3cCapabilities[ChromeOptions::CAPABILITY_W3C] = $ossCapabilities[ChromeOptions::CAPABILITY]; + } + } + + // Convert Firefox profile + if (array_key_exists(FirefoxDriver::PROFILE, $ossCapabilities)) { + // Convert profile only if not already set in moz:firefoxOptions + if (!array_key_exists('moz:firefoxOptions', $ossCapabilities) + || !array_key_exists('profile', $ossCapabilities['moz:firefoxOptions'])) { + $w3cCapabilities['moz:firefoxOptions']['profile'] = $ossCapabilities[FirefoxDriver::PROFILE]; + } + } + + return $w3cCapabilities; + } + /** * @return static */ diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index d2e302f44..1762f44a8 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -15,7 +15,6 @@ namespace Facebook\WebDriver\Remote; -use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\WebDriver; @@ -123,12 +122,12 @@ public static function create( // W3C $parameters = [ 'capabilities' => [ - 'firstMatch' => [static::convertCapabilitiesToW3c($desired_capabilities->toArray())], + 'firstMatch' => [$desired_capabilities->toW3cCompatibleArray()], ], ]; - if ($required_capabilities !== null && $required_capabilities_array = $required_capabilities->toArray()) { - $parameters['capabilities']['alwaysMatch'] = static::convertCapabilitiesToW3c($required_capabilities_array); + if ($required_capabilities !== null && !empty($required_capabilities->toArray())) { + $parameters['capabilities']['alwaysMatch'] = $required_capabilities->toW3cCompatibleArray(); } // Legacy protocol @@ -660,28 +659,4 @@ protected static function castToDesiredCapabilitiesObject($desired_capabilities return $desired_capabilities; } - - /** - * Convert keys invalid in W3C capabilities to corresponding ones for W3C. - * - * @param array $capabilitiesArray - * @return array - */ - protected static function convertCapabilitiesToW3c(array $capabilitiesArray) - { - if (array_key_exists(ChromeOptions::CAPABILITY, $capabilitiesArray)) { - if (array_key_exists(ChromeOptions::CAPABILITY_W3C, $capabilitiesArray)) { - $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] = array_merge( - $capabilitiesArray[ChromeOptions::CAPABILITY], - $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] - ); - } else { - $capabilitiesArray[ChromeOptions::CAPABILITY_W3C] = $capabilitiesArray[ChromeOptions::CAPABILITY]; - } - - unset($capabilitiesArray[ChromeOptions::CAPABILITY]); - } - - return $capabilitiesArray; - } } diff --git a/tests/unit/Remote/DesiredCapabilitiesTest.php b/tests/unit/Remote/DesiredCapabilitiesTest.php index 6e14ef044..4ef4a055a 100644 --- a/tests/unit/Remote/DesiredCapabilitiesTest.php +++ b/tests/unit/Remote/DesiredCapabilitiesTest.php @@ -15,6 +15,7 @@ namespace Facebook\WebDriver\Remote; +use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Firefox\FirefoxDriver; use Facebook\WebDriver\Firefox\FirefoxPreferences; use Facebook\WebDriver\Firefox\FirefoxProfile; @@ -130,4 +131,140 @@ public function testShouldSetupFirefoxProfileAndDisableReaderViewForFirefoxBrows $this->assertSame('false', $firefoxProfile->getPreference(FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED)); } + + /** + * @dataProvider provideW3cCapabilities + * @param DesiredCapabilities $inputJsonWireCapabilities + * @param array $expectedW3cCapabilities + */ + public function testShouldConvertCapabilitiesToW3cCompatible( + DesiredCapabilities $inputJsonWireCapabilities, + array $expectedW3cCapabilities + ) { + $this->assertEquals( + $expectedW3cCapabilities, + $inputJsonWireCapabilities->toW3cCompatibleArray() + ); + } + + /** + * @return array[] + */ + public function provideW3cCapabilities() + { + $chromeOptions = new ChromeOptions(); + $chromeOptions->addArguments([ + '--headless', + ]); + + $firefoxProfileEncoded = (new FirefoxProfile())->encode(); + + return [ + 'changed name' => [ + new DesiredCapabilities([ + WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME, + WebDriverCapabilityType::VERSION => '67.0.1', + WebDriverCapabilityType::PLATFORM => WebDriverPlatform::LINUX, + WebDriverCapabilityType::ACCEPT_SSL_CERTS => true, + ]), + [ + 'browserName' => 'chrome', + 'browserVersion' => '67.0.1', + 'platformName' => 'linux', + 'acceptInsecureCerts' => true, + ], + ], + 'removed capabilitites' => [ + new DesiredCapabilities([ + WebDriverCapabilityType::WEB_STORAGE_ENABLED => true, + WebDriverCapabilityType::TAKES_SCREENSHOT => false, + ]), + [], + ], + 'custom invalid capability should be removed' => [ + new DesiredCapabilities([ + 'customInvalidCapability' => 'shouldBeRemoved', + ]), + [], + ], + 'already W3C capabilitites' => [ + new DesiredCapabilities([ + 'pageLoadStrategy' => 'eager', + 'strictFileInteractability' => false, + ]), + [ + 'pageLoadStrategy' => 'eager', + 'strictFileInteractability' => false, + ], + ], + 'custom vendor extension' => [ + new DesiredCapabilities([ + 'vendor:prefix' => 'vendor extension should be kept', + ]), + [ + 'vendor:prefix' => 'vendor extension should be kept', + ], + ], + 'chromeOptions should be converted' => [ + new DesiredCapabilities([ + ChromeOptions::CAPABILITY => $chromeOptions, + ]), + [ + 'goog:chromeOptions' => [ + 'args' => ['--headless'], + 'binary' => '', + ], + ], + ], + 'chromeOptions should be merged if already defined' => [ + new DesiredCapabilities([ + ChromeOptions::CAPABILITY => $chromeOptions, + ChromeOptions::CAPABILITY_W3C => [ + 'debuggerAddress' => '127.0.0.1:38947', + 'args' => ['window-size=1024,768'], + ], + ]), + [ + 'goog:chromeOptions' => [ + 'args' => ['--headless', 'window-size=1024,768'], + 'binary' => '', + 'debuggerAddress' => '127.0.0.1:38947', + ], + ], + ], + 'firefox_profile should be converted' => [ + new DesiredCapabilities([ + FirefoxDriver::PROFILE => $firefoxProfileEncoded, + ]), + [ + 'moz:firefoxOptions' => [ + 'profile' => $firefoxProfileEncoded, + ], + ], + ], + 'firefox_profile should not be overwritten if already present' => [ + new DesiredCapabilities([ + FirefoxDriver::PROFILE => $firefoxProfileEncoded, + 'moz:firefoxOptions' => ['profile' => 'w3cProfile'], + ]), + [ + 'moz:firefoxOptions' => [ + 'profile' => 'w3cProfile', + ], + ], + ], + 'firefox_profile should be merged with moz:firefoxOptions if they already exists' => [ + new DesiredCapabilities([ + FirefoxDriver::PROFILE => $firefoxProfileEncoded, + 'moz:firefoxOptions' => ['args' => ['-headless']], + ]), + [ + 'moz:firefoxOptions' => [ + 'profile' => $firefoxProfileEncoded, + 'args' => ['-headless'], + ], + ], + ], + ]; + } } From f81659eaa82820b7a55be5ac173f56188af186c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 15 Nov 2019 00:39:53 +0100 Subject: [PATCH 160/487] Run geckodriver tests in headless mode --- tests/functional/WebDriverTestCase.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 8b35a7700..1f125634e 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -65,6 +65,10 @@ protected function setUp() $this->desiredCapabilities->setCapability(ChromeOptions::CAPABILITY, $chromeOptions); } elseif (getenv('GECKODRIVER') === '1') { $this->serverUrl = 'http://localhost:4444/'; + $this->desiredCapabilities->setCapability( + 'moz:firefoxOptions', + ['args' => ['-headless']] + ); } $this->desiredCapabilities->setBrowserName($browserName); From b4b82e2ccada9a7d251ae4703478e94b5b593370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 15 Nov 2019 00:38:54 +0100 Subject: [PATCH 161/487] Add W3C window size and position commands --- lib/Remote/HttpCommandExecutor.php | 9 ++- tests/functional/WebDriverWindowTest.php | 81 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tests/functional/WebDriverWindowTest.php diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index b87c94069..d137030a4 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -144,8 +144,9 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/accept'], DriverCommand::ACTIONS => ['method' => 'POST', 'url' => '/session/:sessionId/actions'], DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'], - DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], + DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], + DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window'], DriverCommand::GET_ELEMENT_LOCATION => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_ELEMENT_PROPERTY => [ @@ -154,11 +155,15 @@ class HttpCommandExecutor implements WebDriverCommandExecutor ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'], - DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], + DriverCommand::GET_WINDOW_POSITION => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], + DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], + DriverCommand::MAXIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/maximize'], DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert/text'], DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], + DriverCommand::SET_WINDOW_SIZE => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], + DriverCommand::SET_WINDOW_POSITION => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'], ]; /** * @var string diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php new file mode 100644 index 000000000..ccf041b16 --- /dev/null +++ b/tests/functional/WebDriverWindowTest.php @@ -0,0 +1,81 @@ +driver->manage() + ->window() + ->getPosition(); + + $this->assertGreaterThanOrEqual(0, $position->getX()); + $this->assertGreaterThanOrEqual(0, $position->getY()); + } + + public function testShouldGetSize() + { + $size = $this->driver->manage() + ->window() + ->getSize(); + + $this->assertGreaterThan(0, $size->getWidth()); + $this->assertGreaterThan(0, $size->getHeight()); + } + + public function testShouldMaximizeWindow() + { + $sizeBefore = $this->driver->manage() + ->window() + ->getSize(); + + $this->driver->manage() + ->window() + ->maximize(); + + $sizeAfter = $this->driver->manage() + ->window() + ->getSize(); + + $this->assertGreaterThanOrEqual($sizeBefore->getWidth(), $sizeAfter->getWidth()); + $this->assertGreaterThanOrEqual($sizeBefore->getHeight(), $sizeAfter->getHeight()); + } + + public function testShouldSetSize() + { + $sizeBefore = $this->driver->manage() + ->window() + ->getSize(); + $this->assertNotSame(500, $sizeBefore->getWidth()); + $this->assertNotSame(666, $sizeBefore->getHeight()); + + $this->driver->manage() + ->window() + ->setSize(new WebDriverDimension(500, 666)); + + $sizeAfter = $this->driver->manage() + ->window() + ->getSize(); + + $this->assertSame(500, $sizeAfter->getWidth()); + $this->assertSame(666, $sizeAfter->getHeight()); + } + + public function testShouldSetWindowPosition() + { + $this->driver->manage() + ->window() + ->setPosition(new WebDriverPoint(33, 66)); + + $positionAfter = $this->driver->manage() + ->window() + ->getPosition(); + + $this->assertSame(33, $positionAfter->getX()); + $this->assertSame(66, $positionAfter->getY()); + } +} From c7ee5e1852b42d73bc597bb70a1b03344bdcb233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Fri, 15 Nov 2019 12:17:12 +0100 Subject: [PATCH 162/487] Exclude window tests on Saucelabs, because they are not sane --- tests/functional/WebDriverWindowTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/functional/WebDriverWindowTest.php b/tests/functional/WebDriverWindowTest.php index ccf041b16..8de61d3e8 100644 --- a/tests/functional/WebDriverWindowTest.php +++ b/tests/functional/WebDriverWindowTest.php @@ -7,6 +7,9 @@ */ class WebDriverWindowTest extends WebDriverTestCase { + /** + * @group exclude-saucelabs + */ public function testShouldGetPosition() { $position = $this->driver->manage() @@ -45,6 +48,9 @@ public function testShouldMaximizeWindow() $this->assertGreaterThanOrEqual($sizeBefore->getHeight(), $sizeAfter->getHeight()); } + /** + * @group exclude-saucelabs + */ public function testShouldSetSize() { $sizeBefore = $this->driver->manage() From e9f4ec88e7ed10388613aa8b05cf1e3f4d2231d3 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Fri, 22 Nov 2019 00:38:58 +0100 Subject: [PATCH 163/487] Download a compatible version of chromedriver with chrome Following the [algorithm described on the official chromedriver website](https://chromedriver.chromium.org/downloads/version-selection) : > Here are the steps to select the version of ChromeDriver to download: > > - First, find out which version of Chrome you are using. Let's say you have Chrome 72.0.3626.81. > - Take the Chrome version number, remove the last part, and append the result to URL "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_". For example, with Chrome version 72.0.3626.81, you'd get a URL "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_72.0.3626". > - Use the URL created in the last step to retrieve a small file containing the version of ChromeDriver to use. For example, the above URL will get your a file containing "72.0.3626.69". (The actual number may change in the future, of course.) > - Use the version number retrieved from the previous step to construct the URL to download ChromeDriver. With version 72.0.3626.69, the URL would be "https://chromedriver.storage.googleapis.com/index.html?path=72.0.3626.69/". --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a8a268a8..0340b80e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -134,7 +134,13 @@ install: - travis_retry composer update --no-interaction $DEPENDENCIES before_script: - - if [ "$BROWSER_NAME" = "chrome" ]; then mkdir chromedriver; CHROMEDRIVER_VERSION=$(wget -qO- "https://chromedriver.storage.googleapis.com/LATEST_RELEASE"); wget -q -t 3 https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip; unzip chromedriver_linux64 -d chromedriver; fi + - if [ "$BROWSER_NAME" = "chrome" ]; then + mkdir chromedriver; + CHROME_VERSION=$(google-chrome --product-version); + CHROME_VERSION=${CHROME_VERSION%.*}; + wget -q -t 3 https://chromedriver.storage.googleapis.com/$(curl -L https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_VERSION})/chromedriver_linux64.zip; + 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 - sh -e /etc/init.d/xvfb start # TODO: start only when needed (ie. not in headless mode) From ee14c4392399a9fc7b362f488226141ca67e8348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 19 Nov 2019 13:14:34 +0100 Subject: [PATCH 164/487] Use W3C protocol on some Saucelabs builds --- .travis.yml | 20 +++++++------ tests/functional/WebDriverTestCase.php | 33 +++++++++++++++++----- tests/functional/WebDriverTimeoutsTest.php | 3 ++ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0340b80e7..2c80cbb18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -85,8 +85,9 @@ matrix: chrome: stable # Saucelabs builds - - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="firefox" VERSION="47.0" PLATFORM="Windows 10" + - 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" @@ -95,8 +96,9 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="74.0" PLATFORM="Windows 10" # 74 is the last version which don't use W3C WebDriver by default + - 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" @@ -105,8 +107,9 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="chrome" VERSION="75.0" PLATFORM="Windows 10" + - 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" @@ -115,8 +118,9 @@ matrix: jwt: secure: HPq5xFhosa1eSGnaRdJzeyEuaE0mhRlG1gf3G7+dKS0VniF30husSyrxZhbGCCKBGxmIySoAQzd43BCwL69EkUEVKDN87Cpid1Ce9KrSfU3cnN8XIb+4QINyy7x1a47RUAfaaOEx53TrW0ShalvjD+ZwDE8LrgagSox6KQ+nQLE= - - php: '7.3' - env: SAUCELABS=1 BROWSER_NAME="MicrosoftEdge" VERSION="16.16299" PLATFORM="Windows 10" + - 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" diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index 1f125634e..f012d9068 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -112,9 +112,8 @@ public static function isSauceLabsBuild() public static function isW3cProtocolBuild() { return getenv('GECKODRIVER') === '1' - || (getenv('BROWSER_NAME') === 'chrome' - && getenv('DISABLE_W3C_PROTOCOL') !== '1' - && !self::isSauceLabsBuild()); + || (getenv('BROWSER_NAME') === 'chrome' && getenv('DISABLE_W3C_PROTOCOL') !== '1') + || getenv('BROWSER_NAME') === 'MicrosoftEdge'; } public static function skipForW3cProtocol($message = 'Not supported by W3C specification') @@ -153,12 +152,32 @@ protected function setUpSauceLabs() $this->desiredCapabilities->setBrowserName(getenv('BROWSER_NAME')); $this->desiredCapabilities->setVersion(getenv('VERSION')); $this->desiredCapabilities->setPlatform(getenv('PLATFORM')); - $this->desiredCapabilities->setCapability('name', get_class($this) . '::' . $this->getName()); - $this->desiredCapabilities->setCapability('tags', [get_class($this)]); + $name = get_class($this) . '::' . $this->getName(); + $tags = [get_class($this)]; if (getenv('TRAVIS_JOB_NUMBER')) { - $this->desiredCapabilities->setCapability('tunnel-identifier', getenv('TRAVIS_JOB_NUMBER')); - $this->desiredCapabilities->setCapability('build', getenv('TRAVIS_JOB_NUMBER')); + $tunnelIdentifier = getenv('TRAVIS_JOB_NUMBER'); + $build = getenv('TRAVIS_JOB_NUMBER'); + } + + if (!getenv('DISABLE_W3C_PROTOCOL')) { + $sauceOptions = [ + 'name' => $name, + 'tags' => $tags, + ]; + if (isset($tunnelIdentifier, $build)) { + $sauceOptions['tunnelIdentifier'] = $tunnelIdentifier; + $sauceOptions['build'] = $build; + } + $this->desiredCapabilities->setCapability('sauce:options', (object) $sauceOptions); + } else { + $this->desiredCapabilities->setCapability('name', $name); + $this->desiredCapabilities->setCapability('tags', $tags); + + if (isset($tunnelIdentifier, $build)) { + $this->desiredCapabilities->setCapability('tunnel-identifier', $tunnelIdentifier); + $this->desiredCapabilities->setCapability('build', $build); + } } } } diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index 733be545c..cd3e10d99 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -26,6 +26,9 @@ */ class WebDriverTimeoutsTest extends WebDriverTestCase { + /** + * @group exclude-saucelabs + */ public function testShouldFailGettingDelayedElementWithoutWait() { $this->driver->get($this->getTestPageUrl('delayed_element.html')); From a0d6be9a5fb351fe1a554bbd4f4a76f896727414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 19 Nov 2019 13:23:30 +0100 Subject: [PATCH 165/487] Simplify builds --- .travis.yml | 3 --- tests/functional/WebDriverAlertTest.php | 6 ------ tests/functional/WebDriverTestCase.php | 2 +- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c80cbb18..9bb73cc1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,6 @@ matrix: php: '7.3' env: - BROWSER_NAME="chrome" - - CHROME_HEADLESS="1" addons: chrome: stable @@ -68,7 +67,6 @@ matrix: php: '7.3' env: - BROWSER_NAME="chrome" - - CHROME_HEADLESS="1" - CHROMEDRIVER="1" addons: chrome: stable @@ -78,7 +76,6 @@ matrix: php: '7.3' env: - BROWSER_NAME="chrome" - - CHROME_HEADLESS="1" - CHROMEDRIVER="1" - DISABLE_W3C_PROTOCOL="1" addons: diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 163bee157..b99dad1fa 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -25,12 +25,6 @@ class WebDriverAlertTest extends WebDriverTestCase { protected function setUp() { - if (getenv('CHROME_HEADLESS') === '1') { - // Alerts in headless mode should be available in next Chrome version (61), see: - // https://bugs.chromium.org/p/chromium/issues/detail?id=718235 - $this->markTestSkipped('Alerts not yet supported by headless Chrome'); - } - parent::setUp(); $this->driver->get($this->getTestPageUrl('alert.html')); diff --git a/tests/functional/WebDriverTestCase.php b/tests/functional/WebDriverTestCase.php index f012d9068..9b302a1f3 100644 --- a/tests/functional/WebDriverTestCase.php +++ b/tests/functional/WebDriverTestCase.php @@ -113,7 +113,7 @@ public static function isW3cProtocolBuild() { return getenv('GECKODRIVER') === '1' || (getenv('BROWSER_NAME') === 'chrome' && getenv('DISABLE_W3C_PROTOCOL') !== '1') - || getenv('BROWSER_NAME') === 'MicrosoftEdge'; + || (self::isSauceLabsBuild() && getenv('DISABLE_W3C_PROTOCOL') !== '1'); } public static function skipForW3cProtocol($message = 'Not supported by W3C specification') From b54d50371b68926a60d2832db2ed94c1d3e95c12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 23 Nov 2019 14:11:55 +0100 Subject: [PATCH 166/487] Skip tests utilizing xPath selectors, which are not properly supported in Edge --- tests/functional/WebDriverCheckboxesTest.php | 1 + tests/functional/WebDriverRadiosTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index 48b72a53c..91513bd94 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -20,6 +20,7 @@ /** * @covers \Facebook\WebDriver\WebDriverCheckboxes * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + * @group exclude-edge */ class WebDriverCheckboxesTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index 4d659772b..cf7b8378c 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -21,6 +21,7 @@ /** * @covers \Facebook\WebDriver\WebDriverRadios * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + * @group exclude-edge */ class WebDriverRadiosTest extends WebDriverTestCase { From a419816547eed70cfcf54fd0763df33c94adcce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 23 Nov 2019 17:56:26 +0100 Subject: [PATCH 167/487] Skip file upload tests on Saucelabs, because W3C protocol does not support remote file upload See https://github.com/w3c/webdriver/issues/1355 --- lib/Remote/RemoteWebElement.php | 27 ++++++++++++--------------- tests/functional/FileUploadTest.php | 2 ++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/Remote/RemoteWebElement.php b/lib/Remote/RemoteWebElement.php index 75dfa3ba2..33286504b 100644 --- a/lib/Remote/RemoteWebElement.php +++ b/lib/Remote/RemoteWebElement.php @@ -358,6 +358,7 @@ public function isSelected() public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); + if ($local_file === null) { if ($this->isW3cCompliant) { $params = [ @@ -370,22 +371,18 @@ public function sendKeys($value) ':id' => $this->id, ]; } - - $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); - - return $this; - } - - if ($this->isW3cCompliant) { - $params = [ - 'text' => $local_file, - ':id' => $this->id, - ]; } else { - $params = [ - 'value' => WebDriverKeys::encode($this->upload($local_file)), - ':id' => $this->id, - ]; + if ($this->isW3cCompliant) { + $params = [ + 'text' => $local_file, + ':id' => $this->id, + ]; + } else { + $params = [ + 'value' => WebDriverKeys::encode($this->upload($local_file)), + ':id' => $this->id, + ]; + } } $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params); diff --git a/tests/functional/FileUploadTest.php b/tests/functional/FileUploadTest.php index 5b2f6d2d3..1029a2363 100644 --- a/tests/functional/FileUploadTest.php +++ b/tests/functional/FileUploadTest.php @@ -26,6 +26,8 @@ class FileUploadTest extends WebDriverTestCase /** * @group exclude-edge * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6052385/ + * @group exclude-saucelabs + * W3C protocol does not support remote file upload: https://github.com/w3c/webdriver/issues/1355 */ public function testShouldUploadAFile() { From 9d5623f40ab320601f8b2cee2bb463d722bb4393 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 25 Nov 2019 15:10:50 +0800 Subject: [PATCH 168/487] Fix mouseUp W3C action --- lib/Remote/RemoteMouse.php | 2 +- tests/functional/WebDriverActionsTest.php | 24 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/Remote/RemoteMouse.php b/lib/Remote/RemoteMouse.php index 4bd4a849b..6b7375834 100644 --- a/lib/Remote/RemoteMouse.php +++ b/lib/Remote/RemoteMouse.php @@ -241,7 +241,7 @@ public function mouseUp(WebDriverCoordinates $where = null) 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, [ [ - 'type' => 'pointerDown', + 'type' => 'pointerUp', 'duration' => 0, 'button' => 0, ], diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 9798f6e5a..6799ef1d3 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -136,6 +136,30 @@ public function testShouldDoubleClickOnElement() $this->assertContains('dblclick item-3', $this->retrieveLoggedEvents()); } + /** + * @covers ::__construct + * @covers ::dragAndDrop + * @covers ::perform + */ + public function testShouldDragAndDrop() + { + if ($this->desiredCapabilities->getBrowserName() === WebDriverBrowserType::HTMLUNIT) { + $this->markTestSkipped('Not supported by HtmlUnit browser'); + } + + $element = $this->driver->findElement(WebDriverBy::id('item-3')); + $target = $this->driver->findElement(WebDriverBy::id('item-1')); + + $this->driver->action() + ->dragAndDrop($element, $target) + ->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()); + } + /** * @return array */ From a94e33b99a8330c90c56128e430ffc55c507d304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 25 Nov 2019 17:13:57 +0100 Subject: [PATCH 169/487] Exclude unstable drag and drop test on SauceLabs --- tests/functional/WebDriverActionsTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index 6799ef1d3..cb4359183 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -140,6 +140,7 @@ public function testShouldDoubleClickOnElement() * @covers ::__construct * @covers ::dragAndDrop * @covers ::perform + * @group exclude-saucelabs */ public function testShouldDragAndDrop() { From 574684f29b8d513ae213c4567f2f71da92734db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sun, 24 Nov 2019 10:10:35 +0100 Subject: [PATCH 170/487] Add all W3C WebDriver exceptions --- .../ElementClickInterceptedException.php | 24 +++++ .../ElementNotInteractableException.php | 23 +++++ .../ElementNotSelectableException.php | 5 +- lib/Exception/ElementNotVisibleException.php | 3 + lib/Exception/ExpectedException.php | 3 + .../IMEEngineActivationFailedException.php | 3 + lib/Exception/IMENotAvailableException.php | 3 + lib/Exception/IndexOutOfBoundsException.php | 3 + .../InsecureCertificateException.php | 24 +++++ lib/Exception/InvalidArgumentException.php | 23 +++++ .../InvalidCookieDomainException.php | 3 + lib/Exception/InvalidCoordinatesException.php | 3 + .../InvalidElementStateException.php | 4 + lib/Exception/InvalidSelectorException.php | 3 + lib/Exception/InvalidSessionIdException.php | 24 +++++ lib/Exception/JavascriptErrorException.php | 23 +++++ .../MoveTargetOutOfBoundsException.php | 3 + lib/Exception/NoAlertOpenException.php | 5 +- lib/Exception/NoCollectionException.php | 3 + lib/Exception/NoScriptResultException.php | 3 + lib/Exception/NoStringException.php | 3 + lib/Exception/NoStringLengthException.php | 3 + lib/Exception/NoStringWrapperException.php | 3 + lib/Exception/NoSuchAlertException.php | 23 +++++ lib/Exception/NoSuchCollectionException.php | 3 + lib/Exception/NoSuchCookieException.php | 24 +++++ lib/Exception/NoSuchDocumentException.php | 5 +- lib/Exception/NoSuchDriverException.php | 3 + lib/Exception/NoSuchElementException.php | 3 + lib/Exception/NoSuchFrameException.php | 3 + lib/Exception/NoSuchWindowException.php | 3 + lib/Exception/NullPointerException.php | 3 + lib/Exception/ScriptTimeoutException.php | 3 + lib/Exception/SessionNotCreatedException.php | 3 + .../StaleElementReferenceException.php | 3 + lib/Exception/TimeoutException.php | 23 +++++ ...php => UnableToCaptureScreenException.php} | 5 +- lib/Exception/UnableToSetCookieException.php | 3 + .../UnexpectedAlertOpenException.php | 3 + .../UnexpectedJavascriptException.php | 5 +- lib/Exception/UnknownCommandException.php | 3 + lib/Exception/UnknownErrorException.php | 23 +++++ lib/Exception/UnknownMethodException.php | 23 +++++ lib/Exception/UnknownServerException.php | 5 +- .../UnsupportedOperationException.php | 3 + lib/Exception/WebDriverException.php | 92 ++++++++++++------- lib/Exception/XPathLookupException.php | 3 + lib/Net/URLChecker.php | 6 +- lib/WebDriverWait.php | 6 +- tests/functional/WebDriverAlertTest.php | 8 +- tests/functional/WebDriverTimeoutsTest.php | 6 +- .../unit/Exception/WebDriverExceptionTest.php | 49 ++++++++-- 52 files changed, 487 insertions(+), 55 deletions(-) create mode 100644 lib/Exception/ElementClickInterceptedException.php create mode 100644 lib/Exception/ElementNotInteractableException.php create mode 100644 lib/Exception/InsecureCertificateException.php create mode 100644 lib/Exception/InvalidArgumentException.php create mode 100644 lib/Exception/InvalidSessionIdException.php create mode 100644 lib/Exception/JavascriptErrorException.php create mode 100644 lib/Exception/NoSuchAlertException.php create mode 100644 lib/Exception/NoSuchCookieException.php create mode 100644 lib/Exception/TimeoutException.php rename lib/Exception/{TimeOutException.php => UnableToCaptureScreenException.php} (85%) create mode 100644 lib/Exception/UnknownErrorException.php create mode 100644 lib/Exception/UnknownMethodException.php diff --git a/lib/Exception/ElementClickInterceptedException.php b/lib/Exception/ElementClickInterceptedException.php new file mode 100644 index 000000000..e4b3a529e --- /dev/null +++ b/lib/Exception/ElementClickInterceptedException.php @@ -0,0 +1,24 @@ +driver->switchTo()->alert()->accept(); - $this->expectException(NoAlertOpenException::class); + if (self::isW3cProtocolBuild()) { + $this->expectException(NoSuchAlertException::class); + } else { + $this->expectException(NoAlertOpenException::class); + } + $this->driver->switchTo()->alert()->accept(); } diff --git a/tests/functional/WebDriverTimeoutsTest.php b/tests/functional/WebDriverTimeoutsTest.php index cd3e10d99..ca85075b8 100644 --- a/tests/functional/WebDriverTimeoutsTest.php +++ b/tests/functional/WebDriverTimeoutsTest.php @@ -17,7 +17,7 @@ use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\ScriptTimeoutException; -use Facebook\WebDriver\Exception\TimeOutException; +use Facebook\WebDriver\Exception\TimeoutException; use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\Remote\WebDriverBrowserType; @@ -65,8 +65,8 @@ public function testShouldFailIfPageIsLoadingLongerThanPageLoadTimeout() try { $this->driver->get($this->getTestPageUrl('slow_loading.html')); - $this->fail('ScriptTimeoutException or TimeOutException exception should be thrown'); - } catch (TimeOutException $e) { // thrown by Selenium 3.0.0+ + $this->fail('ScriptTimeoutException or TimeoutException exception should be thrown'); + } catch (TimeoutException $e) { // thrown by Selenium 3.0.0+ } catch (ScriptTimeoutException $e) { // thrown by Selenium 2 } } diff --git a/tests/unit/Exception/WebDriverExceptionTest.php b/tests/unit/Exception/WebDriverExceptionTest.php index f9aadd207..be3ab964c 100644 --- a/tests/unit/Exception/WebDriverExceptionTest.php +++ b/tests/unit/Exception/WebDriverExceptionTest.php @@ -29,14 +29,15 @@ public function testShouldStoreResultsOnInstantiation() } /** - * @dataProvider statusCodeProvider - * @param int $statusCode + * @dataProvider jsonWireStatusCodeProvider + * @dataProvider w3CWebDriverErrorCodeProvider + * @param int $errorCode * @param string $expectedExceptionType */ - public function testShouldThrowProperExceptionBasedOnSeleniumStatusCode($statusCode, $expectedExceptionType) + public function testShouldThrowProperExceptionBasedOnWebDriverErrorCode($errorCode, $expectedExceptionType) { try { - WebDriverException::throwException($statusCode, 'exception message', ['results']); + WebDriverException::throwException($errorCode, 'exception message', ['results']); } catch (WebDriverException $e) { $this->assertInstanceOf($expectedExceptionType, $e); @@ -48,7 +49,7 @@ public function testShouldThrowProperExceptionBasedOnSeleniumStatusCode($statusC /** * @return array[] */ - public function statusCodeProvider() + public function jsonWireStatusCodeProvider() { return [ [1337, UnrecognizedExceptionException::class], @@ -72,7 +73,7 @@ public function statusCodeProvider() [18, NoScriptResultException::class], [19, XPathLookupException::class], [20, NoSuchCollectionException::class], - [21, TimeOutException::class], + [21, TimeoutException::class], [22, NullPointerException::class], [23, NoSuchWindowException::class], [24, InvalidCookieDomainException::class], @@ -88,4 +89,40 @@ public function statusCodeProvider() [34, MoveTargetOutOfBoundsException::class], ]; } + + /** + * @return array[] + */ + public function w3CWebDriverErrorCodeProvider() + { + return [ + ['element click intercepted', ElementClickInterceptedException::class], + ['element not interactable', ElementNotInteractableException::class], + ['element not interactable', ElementNotInteractableException::class], + ['insecure certificate', InsecureCertificateException::class], + ['invalid argument', InvalidArgumentException::class], + ['invalid cookie domain', InvalidCookieDomainException::class], + ['invalid element state', InvalidElementStateException::class], + ['invalid selector', InvalidSelectorException::class], + ['invalid session id', InvalidSessionIdException::class], + ['javascript error', JavascriptErrorException::class], + ['move target out of bounds', MoveTargetOutOfBoundsException::class], + ['no such alert', NoSuchAlertException::class], + ['no such cookie', NoSuchCookieException::class], + ['no such element', NoSuchElementException::class], + ['no such frame', NoSuchFrameException::class], + ['no such window', NoSuchWindowException::class], + ['script timeout', ScriptTimeoutException::class], + ['session not created', SessionNotCreatedException::class], + ['stale element reference', StaleElementReferenceException::class], + ['timeout', TimeoutException::class], + ['unable to set cookie', UnableToSetCookieException::class], + ['unable to capture screen', UnableToCaptureScreenException::class], + ['unexpected alert open', UnexpectedAlertOpenException::class], + ['unknown command', UnknownCommandException::class], + ['unknown error', UnknownErrorException::class], + ['unknown method', UnknownMethodException::class], + ['unsupported operation', UnsupportedOperationException::class], + ]; + } } From 88fff59dac637265906d100fb91142d3f5475aca Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 21 Nov 2019 09:32:34 +0800 Subject: [PATCH 171/487] Pass W3C Compliance to RemoteTargetLocator --- lib/Remote/RemoteTargetLocator.php | 11 +++++++---- lib/Remote/RemoteWebDriver.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index 8bd89ef44..1f8cb9270 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -33,11 +33,16 @@ class RemoteTargetLocator implements WebDriverTargetLocator * @var WebDriver */ protected $driver; + /** + * @var bool + */ + protected $isW3cCompliant; - public function __construct($executor, $driver) + public function __construct($executor, $driver, $isW3cCompliant = false) { $this->executor = $executor; $this->driver = $driver; + $this->isW3cCompliant = $isW3cCompliant; } /** @@ -112,8 +117,6 @@ public function activeElement() $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); - $isW3cCompliant = ($this->driver instanceof RemoteWebDriver) ? $this->driver->isW3cCompliant() : false; - - return new RemoteWebElement($method, JsonWireCompat::getElement($response), $isW3cCompliant); + return new RemoteWebElement($method, JsonWireCompat::getElement($response), $this->isW3cCompliant); } } diff --git a/lib/Remote/RemoteWebDriver.php b/lib/Remote/RemoteWebDriver.php index 1762f44a8..3164bad5e 100644 --- a/lib/Remote/RemoteWebDriver.php +++ b/lib/Remote/RemoteWebDriver.php @@ -432,7 +432,7 @@ public function navigate() */ public function switchTo() { - return new RemoteTargetLocator($this->getExecuteMethod(), $this); + return new RemoteTargetLocator($this->getExecuteMethod(), $this, $this->isW3cCompliant); } /** From 57f871eaa558b1108ece0fc4a713dd57796e4d50 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 20 Nov 2019 20:54:38 +0800 Subject: [PATCH 172/487] W3C switchToWindow takes a handle The W3C specification dictates that the parameter used to select the window should be named `handle`. Source: https://www.w3.org/TR/webdriver/#switch-to-window --- lib/Remote/RemoteTargetLocator.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Remote/RemoteTargetLocator.php b/lib/Remote/RemoteTargetLocator.php index 1f8cb9270..7e8dbcd1f 100644 --- a/lib/Remote/RemoteTargetLocator.php +++ b/lib/Remote/RemoteTargetLocator.php @@ -89,7 +89,12 @@ public function frame($frame) */ public function window($handle) { - $params = ['name' => (string) $handle]; + if ($this->isW3cCompliant) { + $params = ['handle' => (string) $handle]; + } else { + $params = ['name' => (string) $handle]; + } + $this->executor->execute(DriverCommand::SWITCH_TO_WINDOW, $params); return $this->driver; From 98686d91d96db975e1e22f6a8bdc5ccda2b54319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Mon, 25 Nov 2019 18:54:33 +0100 Subject: [PATCH 173/487] Add tests for switchTo()->window() of RemoteTargetLocator --- tests/functional/RemoteTargetLocatorTest.php | 55 ++++++++++++++++++++ tests/functional/WebDriverAlertTest.php | 1 + tests/functional/web/index.html | 2 + tests/functional/web/open_new_window.html | 2 +- 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/functional/RemoteTargetLocatorTest.php diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php new file mode 100644 index 000000000..210e90b18 --- /dev/null +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -0,0 +1,55 @@ +driver->get($this->getTestPageUrl('open_new_window.html')); + $originalWindowHandle = $this->driver->getWindowHandle(); + $windowHandlesBefore = $this->driver->getWindowHandles(); + + $this->driver->findElement(WebDriverBy::cssSelector('a#open-new-window')) + ->click(); + + $this->driver->wait()->until( + WebDriverExpectedCondition::numberOfWindowsToBe(2) + ); + + // At first the window should not be switched + $this->assertContains('open_new_window.html', $this->driver->getCurrentURL()); + $this->assertSame($originalWindowHandle, $this->driver->getWindowHandle()); + + /** + * @see https://w3c.github.io/webdriver/#get-window-handles + * > "The order in which the window handles are returned is arbitrary." + * Thus we must first find out which window handle is the new one + */ + $windowHandlesAfter = $this->driver->getWindowHandles(); + $newWindowHandle = array_diff($windowHandlesAfter, $windowHandlesBefore); + $newWindowHandle = reset($newWindowHandle); + + $this->driver->switchTo()->window($newWindowHandle); + + // After switchTo() is called, the active window should be changed + $this->assertContains('index.html', $this->driver->getCurrentURL()); + $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); + } +} diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index b5fd3dec5..72d38d82a 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -21,6 +21,7 @@ /** * @covers \Facebook\WebDriver\WebDriverAlert + * @covers \Facebook\WebDriver\Remote\RemoteTargetLocator */ class WebDriverAlertTest extends WebDriverTestCase { diff --git a/tests/functional/web/index.html b/tests/functional/web/index.html index cf5b9c6c1..ff80fe025 100644 --- a/tests/functional/web/index.html +++ b/tests/functional/web/index.html @@ -11,6 +11,8 @@

    Welcome to the facebook/php-webdriver testing page.

    | Form with file upload | + Form with checkboxes and radios + | New window opener test page | Delayed render diff --git a/tests/functional/web/open_new_window.html b/tests/functional/web/open_new_window.html index b56e93d69..3c790226a 100644 --- a/tests/functional/web/open_new_window.html +++ b/tests/functional/web/open_new_window.html @@ -5,6 +5,6 @@ php-webdriver test page - open new window + open new window From 51353795e94ed34d23f2657bb690827a072ad14a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Thu, 21 Nov 2019 08:32:22 +0800 Subject: [PATCH 174/487] Make GET_ACTIVE_ELEMENT a GET for W3C As defined in the specification 12.2.6: HTTP Method URI Template GET /session/{session id}/element/active Source: https://www.w3.org/TR/webdriver/#get-active-element --- lib/Remote/HttpCommandExecutor.php | 1 + tests/functional/RemoteTargetLocatorTest.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/Remote/HttpCommandExecutor.php b/lib/Remote/HttpCommandExecutor.php index d137030a4..a1b05c5ff 100644 --- a/lib/Remote/HttpCommandExecutor.php +++ b/lib/Remote/HttpCommandExecutor.php @@ -146,6 +146,7 @@ class HttpCommandExecutor implements WebDriverCommandExecutor DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], + DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'GET', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window'], DriverCommand::GET_ELEMENT_LOCATION => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], diff --git a/tests/functional/RemoteTargetLocatorTest.php b/tests/functional/RemoteTargetLocatorTest.php index 210e90b18..becc194c8 100644 --- a/tests/functional/RemoteTargetLocatorTest.php +++ b/tests/functional/RemoteTargetLocatorTest.php @@ -15,6 +15,8 @@ namespace Facebook\WebDriver; +use Facebook\WebDriver\Remote\RemoteWebElement; + /** * @covers \Facebook\WebDriver\Remote\RemoteTargetLocator */ @@ -52,4 +54,21 @@ public function testShouldSwitchToWindow() $this->assertContains('index.html', $this->driver->getCurrentURL()); $this->assertNotSame($originalWindowHandle, $this->driver->getWindowHandle()); } + + /** + * @cover ::activeElement + */ + public function testActiveElement() + { + $this->driver->get($this->getTestPageUrl('index.html')); + + $activeElement = $this->driver->switchTo()->activeElement(); + $this->assertInstanceOf(RemoteWebElement::class, $activeElement); + $this->assertSame('body', $activeElement->getTagName()); + + $this->driver->findElement(WebDriverBy::name('test_name'))->click(); + $activeElement = $this->driver->switchTo()->activeElement(); + $this->assertSame('input', $activeElement->getTagName()); + $this->assertSame('test_name', $activeElement->getAttribute('name')); + } } From 1dcec4bcda282ae4ea6a4e1314a1cd3c953974d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Tue, 26 Nov 2019 17:12:40 +0100 Subject: [PATCH 175/487] Minor codestyle improvements and unifications --- .php_cs.dist | 10 +++++++++- phpstan.neon | 16 ++++++++-------- tests/functional/RemoteWebDriverCreateTest.php | 2 +- tests/functional/RemoteWebDriverTest.php | 2 +- tests/functional/WebDriverActionsTest.php | 2 +- tests/functional/WebDriverAlertTest.php | 2 +- tests/functional/WebDriverByTest.php | 7 +++++-- tests/functional/WebDriverCheckboxesTest.php | 14 +++++++------- tests/functional/WebDriverNavigationTest.php | 2 +- tests/functional/WebDriverRadiosTest.php | 14 +++++++------- tests/functional/WebDriverSelectTest.php | 9 ++++++--- tests/functional/WebDriverTimeoutsTest.php | 4 ++-- tests/unit/CookieTest.php | 4 ++-- tests/unit/Exception/WebDriverExceptionTest.php | 8 ++++---- .../WebDriverButtonReleaseActionTest.php | 4 ++-- .../Internal/WebDriverClickActionTest.php | 4 ++-- .../Internal/WebDriverClickAndHoldActionTest.php | 4 ++-- .../Internal/WebDriverContextClickActionTest.php | 4 ++-- .../Internal/WebDriverCoordinatesTest.php | 10 +++++----- .../Internal/WebDriverDoubleClickActionTest.php | 4 ++-- .../Internal/WebDriverKeyDownActionTest.php | 4 ++-- .../Internal/WebDriverKeyUpActionTest.php | 4 ++-- .../Internal/WebDriverMouseMoveActionTest.php | 4 ++-- .../WebDriverMouseToOffsetActionTest.php | 4 ++-- .../Internal/WebDriverSendKeysActionTest.php | 4 ++-- tests/unit/Remote/DesiredCapabilitiesTest.php | 6 +++--- tests/unit/Remote/HttpCommandExecutorTest.php | 6 +++--- tests/unit/Remote/RemoteWebDriverTest.php | 2 +- tests/unit/Support/XPathEscaperTest.php | 4 ++-- tests/unit/WebDriverOptionsTest.php | 2 +- 30 files changed, 90 insertions(+), 76 deletions(-) diff --git a/.php_cs.dist b/.php_cs.dist index 48aa29de7..b771f8b04 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -13,6 +13,8 @@ return PhpCsFixer\Config::create() 'concat_space' => ['spacing' => 'one'], 'function_typehint_space' => true, 'general_phpdoc_annotation_remove' => ['author'], + 'implode_call' => true, + 'is_null' => true, 'linebreak_after_opening_tag' => true, 'lowercase_cast' => true, 'mb_str_functions' => true, @@ -51,9 +53,15 @@ return PhpCsFixer\Config::create() 'ordered_imports' => true, 'php_unit_construct' => true, 'php_unit_dedicate_assert' => true, - 'php_unit_expectation' => true, + 'php_unit_expectation' => ['target' => '5.6'], + 'php_unit_method_casing' => ['case' => 'camel_case'], 'php_unit_mock' => true, + 'php_unit_mock_short_will_return' => true, + 'php_unit_namespaced' => ['target' => '5.7'], 'php_unit_no_expectation_annotation' => true, + 'php_unit_ordered_covers' => true, + 'php_unit_set_up_tear_down_visibility' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 'phpdoc_add_missing_param_annotation' => true, 'phpdoc_indent' => true, 'phpdoc_no_access' => true, diff --git a/phpstan.neon b/phpstan.neon index 7023c466e..3ea02aee5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,10 +1,10 @@ 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#' + 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\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::getCoordinates\(\)#' - - '#Call to an undefined method Facebook\\WebDriver\\WebDriverElement::equals\(\)#' + - '#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\(\)#' diff --git a/tests/functional/RemoteWebDriverCreateTest.php b/tests/functional/RemoteWebDriverCreateTest.php index a2f6e9d0f..5abec763b 100644 --- a/tests/functional/RemoteWebDriverCreateTest.php +++ b/tests/functional/RemoteWebDriverCreateTest.php @@ -20,8 +20,8 @@ use Facebook\WebDriver\Remote\RemoteWebDriver; /** - * @covers \Facebook\WebDriver\Remote\RemoteWebDriver * @covers \Facebook\WebDriver\Remote\HttpCommandExecutor + * @covers \Facebook\WebDriver\Remote\RemoteWebDriver */ class RemoteWebDriverCreateTest extends WebDriverTestCase { diff --git a/tests/functional/RemoteWebDriverTest.php b/tests/functional/RemoteWebDriverTest.php index 72fc30505..6f3becb0b 100644 --- a/tests/functional/RemoteWebDriverTest.php +++ b/tests/functional/RemoteWebDriverTest.php @@ -38,8 +38,8 @@ public function testShouldGetPageTitle() } /** - * @covers ::getCurrentURL * @covers ::get + * @covers ::getCurrentURL */ public function testShouldGetCurrentUrl() { diff --git a/tests/functional/WebDriverActionsTest.php b/tests/functional/WebDriverActionsTest.php index cb4359183..deb700575 100644 --- a/tests/functional/WebDriverActionsTest.php +++ b/tests/functional/WebDriverActionsTest.php @@ -61,8 +61,8 @@ public function testShouldClickOnElement() /** * @covers ::__construct * @covers ::clickAndHold - * @covers ::release * @covers ::perform + * @covers ::release */ public function testShouldClickAndHoldOnElementAndRelease() { diff --git a/tests/functional/WebDriverAlertTest.php b/tests/functional/WebDriverAlertTest.php index 72d38d82a..d6f5c91a2 100644 --- a/tests/functional/WebDriverAlertTest.php +++ b/tests/functional/WebDriverAlertTest.php @@ -20,8 +20,8 @@ use Facebook\WebDriver\Remote\WebDriverBrowserType; /** - * @covers \Facebook\WebDriver\WebDriverAlert * @covers \Facebook\WebDriver\Remote\RemoteTargetLocator + * @covers \Facebook\WebDriver\WebDriverAlert */ class WebDriverAlertTest extends WebDriverTestCase { diff --git a/tests/functional/WebDriverByTest.php b/tests/functional/WebDriverByTest.php index 028db7920..4ff114751 100644 --- a/tests/functional/WebDriverByTest.php +++ b/tests/functional/WebDriverByTest.php @@ -24,7 +24,7 @@ class WebDriverByTest extends WebDriverTestCase { /** - * @dataProvider textElementsProvider + * @dataProvider provideTextElements * @param string $webDriverByLocatorMethod * @param string $webDriverByLocatorValue * @param string $expectedText @@ -52,7 +52,10 @@ public function testShouldFindTextElementByLocator( } } - public function textElementsProvider() + /** + * @return array[] + */ + public function provideTextElements() { return [ 'id' => ['id', 'id_test', 'Test by ID'], diff --git a/tests/functional/WebDriverCheckboxesTest.php b/tests/functional/WebDriverCheckboxesTest.php index 91513bd94..0ddcccd0d 100644 --- a/tests/functional/WebDriverCheckboxesTest.php +++ b/tests/functional/WebDriverCheckboxesTest.php @@ -18,8 +18,8 @@ use Facebook\WebDriver\Exception\NoSuchElementException; /** - * @covers \Facebook\WebDriver\WebDriverCheckboxes * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + * @covers \Facebook\WebDriver\WebDriverCheckboxes * @group exclude-edge */ class WebDriverCheckboxesTest extends WebDriverTestCase @@ -137,7 +137,7 @@ public function testSelectByIndexInvalid() } /** - * @dataProvider selectByVisibleTextDataProvider + * @dataProvider provideSelectByVisibleTextData * * @param string $text * @param string $value @@ -154,9 +154,9 @@ public function testSelectByVisibleText($text, $value) } /** - * @return array + * @return array[] */ - public function selectByVisibleTextDataProvider() + public function provideSelectByVisibleTextData() { return [ ['J 2 B', 'j2b'], @@ -165,7 +165,7 @@ public function selectByVisibleTextDataProvider() } /** - * @dataProvider selectByVisiblePartialTextDataProvider + * @dataProvider provideSelectByVisiblePartialTextData * * @param string $text * @param string $value @@ -182,9 +182,9 @@ public function testSelectByVisiblePartialText($text, $value) } /** - * @return array + * @return array[] */ - public function selectByVisiblePartialTextDataProvider() + public function provideSelectByVisiblePartialTextData() { return [ ['2 B', 'j2b'], diff --git a/tests/functional/WebDriverNavigationTest.php b/tests/functional/WebDriverNavigationTest.php index d3aaebcb6..c2286f3b2 100644 --- a/tests/functional/WebDriverNavigationTest.php +++ b/tests/functional/WebDriverNavigationTest.php @@ -21,8 +21,8 @@ class WebDriverNavigationTest extends WebDriverTestCase { /** - * @covers ::to * @covers ::__construct + * @covers ::to */ public function testShouldNavigateToUrl() { diff --git a/tests/functional/WebDriverRadiosTest.php b/tests/functional/WebDriverRadiosTest.php index cf7b8378c..bb0eaa41e 100644 --- a/tests/functional/WebDriverRadiosTest.php +++ b/tests/functional/WebDriverRadiosTest.php @@ -19,8 +19,8 @@ use Facebook\WebDriver\Exception\UnsupportedOperationException; /** - * @covers \Facebook\WebDriver\WebDriverRadios * @covers \Facebook\WebDriver\AbstractWebDriverCheckboxOrRadio + * @covers \Facebook\WebDriver\WebDriverRadios * @group exclude-edge */ class WebDriverRadiosTest extends WebDriverTestCase @@ -115,7 +115,7 @@ public function testSelectByIndexInvalid() } /** - * @dataProvider selectByVisibleTextDataProvider + * @dataProvider provideSelectByVisibleTextData * * @param string $text * @param string $value @@ -128,9 +128,9 @@ public function testSelectByVisibleText($text, $value) } /** - * @return array + * @return array[] */ - public function selectByVisibleTextDataProvider() + public function provideSelectByVisibleTextData() { return [ ['J 3 B', 'j3b'], @@ -139,7 +139,7 @@ public function selectByVisibleTextDataProvider() } /** - * @dataProvider selectByVisiblePartialTextDataProvider + * @dataProvider provideSelectByVisiblePartialTextData * * @param string $text * @param string $value @@ -152,9 +152,9 @@ public function testSelectByVisiblePartialText($text, $value) } /** - * @return array + * @return array[] */ - public function selectByVisiblePartialTextDataProvider() + public function provideSelectByVisiblePartialTextData() { return [ ['3 B', 'j3b'], diff --git a/tests/functional/WebDriverSelectTest.php b/tests/functional/WebDriverSelectTest.php index b25dd03af..8d96a1944 100644 --- a/tests/functional/WebDriverSelectTest.php +++ b/tests/functional/WebDriverSelectTest.php @@ -21,8 +21,8 @@ /** * @group exclude-saucelabs - * @covers \Facebook\WebDriver\WebDriverSelect * @covers \Facebook\WebDriver\Exception\UnexpectedTagNameException + * @covers \Facebook\WebDriver\WebDriverSelect */ class WebDriverSelectTest extends WebDriverTestCase { @@ -58,7 +58,7 @@ public function testShouldThrowExceptionWhenNotInstantiatedOnSelectElement() } /** - * @dataProvider selectSelectorProvider + * @dataProvider provideSelectSelector * @param string $selector */ public function testShouldGetOptionsOfSelect($selector) @@ -72,7 +72,10 @@ public function testShouldGetOptionsOfSelect($selector) $this->assertCount(5, $options); } - public function selectSelectorProvider() + /** + * @return array[] + */ + public function provideSelectSelector() { return [ 'simple + +
    +