From c9989440f3ac4b4cea8fdb6145ca2a771ee350f0 Mon Sep 17 00:00:00 2001 From: Sally Young Date: Thu, 5 Oct 2023 14:07:31 +0100 Subject: [PATCH] Wider W3C compatability fixes to match mink/driver-testsuite expectations --- .github/workflows/tests.yml | 2 + src/Selenium2Driver.php | 191 +++++++++++++++++++---- tests/Custom/DesiredCapabilitiesTest.php | 10 +- tests/Custom/TimeoutTest.php | 66 +++++--- tests/Custom/WebDriverTest.php | 10 ++ tests/Custom/WindowNameTest.php | 9 ++ 6 files changed, 234 insertions(+), 54 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e06a9b9b..ce39c643 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,6 +78,8 @@ jobs: mkdir ./logs ./vendor/bin/mink-test-server &> ./logs/mink-test-server.log & + # To run this locally, additionally mount a volume with the repo to retrieve test files + # e.g. -v /home/youruser/MinkSelenium2Driver:/home/youruser/MinkSelenium2Driver - name: Start Selenium run: | docker run --net host --name selenium --volume /dev/shm:/dev/shm --shm-size 2g selenium/standalone-firefox:${{ matrix.selenium }} &> ./logs/selenium.log & diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index ddf37660..00daa26a 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -336,7 +336,7 @@ private function executeJsOnElement(Element $element, string $script, bool $sync 'args' => [ [ 'ELEMENT' => $element->getID(), - 'element-6066-11e4-a52e-4f735466cecf' => $element->getID(), + Element::WEB_ELEMENT_ID => $element->getID(), ] ], ); @@ -451,12 +451,41 @@ public function back() public function switchToWindow(?string $name = null) { - $this->getWebDriverSession()->focusWindow($name ?: ''); + if ($this->isW3C()) { + $allHandles = $this->getWebDriverSession()->getWindowHandles(); + foreach ($allHandles as $handle) { + $script = <<getWebDriverSession()->focusWindow($handle); + $windowName = $this->getWebDriverSession()->execute(array('script' => $script, 'args' => array())); + if ($windowName === $name || (empty($name) && empty($windowName))) { + break; + } + } + } + else { + $this->getWebDriverSession()->focusWindow($name ?: ''); + } } public function switchToIFrame(?string $name = null) { - $this->getWebDriverSession()->frame(array('id' => $name)); + if ($this->isW3C()) { + if (empty($name)) { + $this->getWebDriverSession()->frame(array('id' => null)); + } + else { + $frameElement = $this->findElement("//iframe[@name='$name']"); + $this->getWebDriverSession()->frame(array('id' => [ + Element::WEB_ELEMENT_ID => $frameElement->getID(), + ])); + } + } + else { + $this->getWebDriverSession()->frame(array('id' => $name)); + } } public function setCookie(string $name, ?string $value = null) @@ -586,11 +615,6 @@ public function getAttribute(string $xpath, string $name) public function getValue(string $xpath) { $element = $this->findElement($xpath); - if ($this->isW3C()) { - // This method doesn't exist in older versions of php-webdriver. - /** @phpstan-ignore-next-line variable.undefined */ - return $element->property('value'); - } $elementName = strtolower($element->name()); $elementType = strtolower($element->attribute('type') ?: ''); @@ -642,6 +666,12 @@ public function getValue(string $xpath) return $this->executeJsOnElement($element, $script); } + if ($this->isW3C()) { + // This method doesn't exist in older versions of php-webdriver. + /** @phpstan-ignore-next-line variable.undefined */ + return $element->property('value'); + } + return $element->attribute('value'); } @@ -718,8 +748,16 @@ public function setValue(string $xpath, $value) $value = strval($value); if (in_array($elementName, array('input', 'textarea'))) { - $existingValueLength = strlen($element->attribute('value')); - $value = str_repeat(Key::BACKSPACE . Key::DELETE, $existingValueLength) . $value; + if ($this->isW3C() && $elementName === 'textarea') { + // Backspace doesn't work for textareas for some reason, and + // sending ->clear() will trigger a change event. + $element->postValue(array('text' => Key::CONTROL . 'a')); + $element->postValue(array('text' => Key::DELETE)); + } + else { + $existingValueLength = strlen($element->attribute('value')); + $value = str_repeat(Key::BACKSPACE . Key::DELETE, $existingValueLength) . $value; + } } if ($this->isW3C()) { @@ -827,14 +865,58 @@ private function clickOnElement(Element $element): void public function doubleClick(string $xpath) { - $this->mouseOver($xpath); - $this->getWebDriverSession()->doubleclick(); + if ($this->isW3C()) { + $actions = array( + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse1', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [ + ['type' => 'pointerMove', 'duration' => 0, 'origin' => [Element::WEB_ELEMENT_ID => $this->findElement($xpath)->getID()], 'x' => 0, 'y' => 0], + ['type' => 'pointerDown', "button" => 0], + ['type' => 'pointerUp', "button" => 0], + ['type' => 'pause', 'duration' => 10], + ['type' => 'pointerDown', "button" => 0], + ['type' => 'pointerUp', "button" => 0], + ], + ], + ], + ); + $this->getWebDriverSession()->postActions($actions); + $this->getWebDriverSession()->deleteActions(); + } + else { + $this->mouseOver($xpath); + $this->getWebDriverSession()->doubleclick(); + } } public function rightClick(string $xpath) { - $this->mouseOver($xpath); - $this->getWebDriverSession()->click(array('button' => 2)); + if ($this->isW3C()) { + $actions = array( + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse1', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [ + ['type' => 'pointerMove', 'duration' => 0, 'origin' => [Element::WEB_ELEMENT_ID => $this->findElement($xpath)->getID()], 'x' => 0, 'y' => 0], + ['type' => 'pointerDown', "button" => 2], + ['type' => 'pause', 'duration' => 500], + ['type' => 'pointerUp', "button" => 2], + ], + ], + ], + ); + $this->getWebDriverSession()->postActions($actions); + $this->getWebDriverSession()->deleteActions(); + } + else { + $this->mouseOver($xpath); + $this->getWebDriverSession()->click(array('button' => 2)); + } } public function attachFile(string $xpath, string $path) @@ -867,9 +949,27 @@ public function isVisible(string $xpath) public function mouseOver(string $xpath) { - $this->getWebDriverSession()->moveto(array( - 'element' => $this->findElement($xpath)->getID() - )); + if ($this->isW3C()) { + $actions = array( + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse1', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [ + ['type' => 'pointerMove', 'duration' => 0, 'origin' => [Element::WEB_ELEMENT_ID => $this->findElement($xpath)->getID()], 'x' => 0, 'y' => 0], + ], + ], + ], + ); + $this->getWebDriverSession()->postActions($actions); + $this->getWebDriverSession()->deleteActions(); + } + else { + $this->getWebDriverSession()->moveto(array( + 'element' => $this->findElement($xpath)->getID() + )); + } } public function focus(string $xpath) @@ -905,11 +1005,31 @@ public function dragTo(string $sourceXpath, string $destinationXpath) $source = $this->findElement($sourceXpath); $destination = $this->findElement($destinationXpath); - $this->getWebDriverSession()->moveto(array( - 'element' => $source->getID() - )); + if ($this->isW3C()) { + $actions = array( + 'actions' => [ + [ + 'type' => 'pointer', + 'id' => 'mouse1', + 'parameters' => ['pointerType' => 'mouse'], + 'actions' => [ + ['type' => 'pointerMove', 'duration' => 0, 'origin' => [Element::WEB_ELEMENT_ID => $this->findElement($sourceXpath)->getID()], 'x' => 0, 'y' => 0], + ['type' => 'pointerDown', "button" => 0], + ['type' => 'pointerMove', 'duration' => 0, 'origin' => [Element::WEB_ELEMENT_ID => $this->findElement($destinationXpath)->getID()], 'x' => 0, 'y' => 0], + ['type' => 'pointerUp', "button" => 0], + ], + ], + ], + ); + $this->getWebDriverSession()->postActions($actions); + $this->getWebDriverSession()->deleteActions(); + } + else { + $this->getWebDriverSession()->moveto(array( + 'element' => $source->getID() + )); - $script = <<withSyn()->executeJsOnElement($source, $script); + $this->withSyn()->executeJsOnElement($source, $script); - $this->getWebDriverSession()->buttondown(); - $this->getWebDriverSession()->moveto(array( - 'element' => $destination->getID() - )); - $this->getWebDriverSession()->buttonup(); + $this->getWebDriverSession()->buttondown(); + $this->getWebDriverSession()->moveto(array( + 'element' => $destination->getID() + )); + $this->getWebDriverSession()->buttonup(); - $script = <<withSyn()->executeJsOnElement($destination, $script); + $this->withSyn()->executeJsOnElement($destination, $script); + } } public function executeScript(string $script) @@ -996,7 +1117,17 @@ public function resizeWindow(int $width, int $height, ?string $name = null) public function submitForm(string $xpath) { - $this->findElement($xpath)->submit(); + if ($this->isW3C()) { + $script = <<executeJsOnElement($this->findElement($xpath), $script); + } + else { + $this->findElement($xpath)->submit(); + } } public function maximizeWindow(?string $name = null) diff --git a/tests/Custom/DesiredCapabilitiesTest.php b/tests/Custom/DesiredCapabilitiesTest.php index b0dfe725..cf1ea5dc 100644 --- a/tests/Custom/DesiredCapabilitiesTest.php +++ b/tests/Custom/DesiredCapabilitiesTest.php @@ -3,10 +3,12 @@ namespace Behat\Mink\Tests\Driver\Custom; use Behat\Mink\Driver\Selenium2Driver; +use Behat\Mink\Exception\Exception; use Behat\Mink\Tests\Driver\TestCase; class DesiredCapabilitiesTest extends TestCase { + public function testGetDesiredCapabilities() { $caps = array( @@ -47,6 +49,12 @@ public function testSetDesiredCapabilities() $this->expectException('\Behat\Mink\Exception\DriverException'); $this->expectExceptionMessage('Unable to set desiredCapabilities, the session has already started'); - $driver->setDesiredCapabilities($caps); + try { + $driver->setDesiredCapabilities($caps); + } + catch (Exception $e) { + $session->stop(); + throw $e; + } } } diff --git a/tests/Custom/TimeoutTest.php b/tests/Custom/TimeoutTest.php index 3f036943..a5147175 100644 --- a/tests/Custom/TimeoutTest.php +++ b/tests/Custom/TimeoutTest.php @@ -3,48 +3,66 @@ namespace Behat\Mink\Tests\Driver\Custom; use Behat\Mink\Driver\Selenium2Driver; +use Behat\Mink\Exception\DriverException; use Behat\Mink\Tests\Driver\TestCase; class TimeoutTest extends TestCase { + protected $timeouts; + /** - * @after + * @throws DriverException + * + * @return void */ - protected function resetSessions() + public function setup(): void { - $session = $this->getSession(); - - // Stop the session instead of only resetting it, as timeouts are not reset (they are configuring the session itself) - if ($session->isStarted()) { - $session->stop(); - } - - $driver = $session->getDriver(); + parent::setup(); + $this->getSession()->start(); + $driver = $this->getSession()->getDriver(); \assert($driver instanceof Selenium2Driver); - // Reset the array of timeouts to avoid impacting other tests - $driver->setTimeouts(array()); + $this->timeouts = $this->getSession()->getDriver()->getWebDriverSession()->getTimeouts(); + } - parent::resetSessions(); + /** + * @throws DriverException + * + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + $this->getSession()->getDriver()->setTimeouts($this->timeouts); + $this->getSession()->stop(); } + /** + * @throws DriverException + */ public function testInvalidTimeoutSettingThrowsException() { - $session = $this->getSession(); - $session->start(); + $driver = $this->getSession()->getDriver(); - $driver = $session->getDriver(); - \assert($driver instanceof Selenium2Driver); - - $this->expectException('\Behat\Mink\Exception\DriverException'); - $driver->setTimeouts(array('invalid' => -1)); + if ($driver->isW3C()) { + $this->expectException('\WebDriver\Exception\InvalidArgument'); + // The browser will return a 200 for an invalid key, but 400 as + // expected for an invalid value. + $driver->setTimeouts(array('script' => -1)); + } + else { + $this->expectException('\Behat\Mink\Exception\DriverException'); + $driver->setTimeouts(array('invalid' => 0)); + } } + /** + * @throws DriverException + */ public function testShortTimeoutDoesNotWaitForElementToAppear() { $session = $this->getSession(); - $driver = $session->getDriver(); - \assert($driver instanceof Selenium2Driver); + $driver = $this->getSession()->getDriver(); $driver->setTimeouts(array('implicit' => 0)); @@ -56,11 +74,13 @@ public function testShortTimeoutDoesNotWaitForElementToAppear() $this->assertNull($element); } + /** + * @throws DriverException + */ public function testLongTimeoutWaitsForElementToAppear() { $session = $this->getSession(); $driver = $session->getDriver(); - \assert($driver instanceof Selenium2Driver); $driver->setTimeouts(array('implicit' => 5000)); diff --git a/tests/Custom/WebDriverTest.php b/tests/Custom/WebDriverTest.php index a6f71e6d..366cc705 100644 --- a/tests/Custom/WebDriverTest.php +++ b/tests/Custom/WebDriverTest.php @@ -7,6 +7,16 @@ class WebDriverTest extends TestCase { + + /** + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + $this->getSession()->stop(); + } + public function testGetWebDriverSessionId() { $session = $this->getSession(); diff --git a/tests/Custom/WindowNameTest.php b/tests/Custom/WindowNameTest.php index ce21738f..8fceb9d9 100644 --- a/tests/Custom/WindowNameTest.php +++ b/tests/Custom/WindowNameTest.php @@ -6,6 +6,15 @@ class WindowNameTest extends TestCase { + /** + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + $this->getSession()->stop(); + } + public function testWindowNames() { $session = $this->getSession();