-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use interfaces as much as possible; switch to actions convenience class #59
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,14 +18,18 @@ | |
use Facebook\WebDriver\Exception\TimeoutException; | ||
use Facebook\WebDriver\Exception\UnsupportedOperationException; | ||
use Facebook\WebDriver\Exception\WebDriverException; | ||
use Facebook\WebDriver\Interactions\WebDriverActions; | ||
use Facebook\WebDriver\Internal\WebDriverLocatable; | ||
use Facebook\WebDriver\JavaScriptExecutor; | ||
use Facebook\WebDriver\Remote\DesiredCapabilities; | ||
use Facebook\WebDriver\Remote\RemoteWebDriver; | ||
use Facebook\WebDriver\Remote\RemoteWebElement; | ||
use Facebook\WebDriver\Remote\WebDriverBrowserType; | ||
use Facebook\WebDriver\Remote\WebDriverCapabilityType; | ||
use Facebook\WebDriver\WebDriver; | ||
use Facebook\WebDriver\WebDriverBy; | ||
use Facebook\WebDriver\WebDriverDimension; | ||
use Facebook\WebDriver\WebDriverElement; | ||
use Facebook\WebDriver\WebDriverHasInputDevices; | ||
use Facebook\WebDriver\WebDriverPlatform; | ||
use Facebook\WebDriver\WebDriverRadios; | ||
use Facebook\WebDriver\WebDriverSelect; | ||
|
@@ -35,7 +39,9 @@ | |
* @phpstan-type TTimeouts array{script?: null|numeric, implicit?: null|numeric, page?: null|numeric, "page load"?: null|numeric, pageLoad?: null|numeric} | ||
* @phpstan-type TCapabilities array<string, mixed> | ||
* @phpstan-type TElementValue array<array-key, mixed>|bool|mixed|string|null | ||
* @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): RemoteWebDriver | ||
* @phpstan-type TWebDriver WebDriver&JavaScriptExecutor&WebDriverHasInputDevices | ||
* @phpstan-type TWebDriverElement WebDriverElement&WebDriverLocatable | ||
* @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): TWebDriver | ||
*/ | ||
class WebdriverClassicDriver extends CoreDriver | ||
{ | ||
|
@@ -77,7 +83,10 @@ | |
|
||
private const W3C_WINDOW_HANDLE_PREFIX = 'w3cwh:'; | ||
|
||
private ?RemoteWebDriver $webDriver = null; | ||
/** | ||
* @phpstan-var TWebDriver|null | ||
*/ | ||
private ?WebDriver $webDriver = null; | ||
|
||
private string $browserName; | ||
|
||
|
@@ -210,7 +219,7 @@ | |
public function switchToIFrame(?string $name = null): void | ||
{ | ||
$frameQuery = $name; | ||
if ($name && $this->getWebDriver()->isW3cCompliant()) { | ||
if ($name && $this->isW3cCompliant()) { | ||
try { | ||
$frameQuery = $this->getWebDriver()->findElement(WebDriverBy::id($name)); | ||
} catch (NoSuchElementException $e) { | ||
|
@@ -569,14 +578,18 @@ | |
#[Language('XPath')] | ||
string $xpath | ||
): void { | ||
$this->doubleClickOnElement($this->findElement($xpath)); | ||
$element = $this->findElement($xpath); | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->actions()->doubleClick($element)->perform(); | ||
} | ||
|
||
public function rightClick( | ||
#[Language('XPath')] | ||
string $xpath | ||
): void { | ||
$this->rightClickOnElement($this->findElement($xpath)); | ||
$element = $this->findElement($xpath); | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->actions()->contextClick($element)->perform(); | ||
} | ||
|
||
public function attachFile( | ||
|
@@ -601,7 +614,9 @@ | |
#[Language('XPath')] | ||
string $xpath | ||
): void { | ||
$this->mouseOverElement($this->findElement($xpath)); | ||
$element = $this->findElement($xpath); | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->actions()->moveToElement($element)->perform(); | ||
} | ||
|
||
public function focus( | ||
|
@@ -656,7 +671,7 @@ | |
): void { | ||
$source = $this->findElement($sourceXpath); | ||
$destination = $this->findElement($destinationXpath); | ||
$this->getWebDriver()->action()->dragAndDrop($source, $destination)->perform(); | ||
$this->actions()->dragAndDrop($source, $destination)->perform(); | ||
} | ||
|
||
public function executeScript( | ||
|
@@ -751,8 +766,8 @@ | |
*/ | ||
public function getWebDriverSessionId(): ?string | ||
{ | ||
return $this->isStarted() | ||
? $this->getWebDriver()->getSessionID() | ||
return $this->isStarted() && method_exists($this->getWebDriver(), 'getSessionID') | ||
? $this->getAsString($this->getWebDriver()->getSessionID(), 'Session ID') | ||
: null; | ||
} | ||
|
||
|
@@ -789,15 +804,16 @@ | |
} | ||
|
||
/** | ||
* @phpstan-return TWebDriver | ||
* @throws DriverException | ||
*/ | ||
protected function getWebDriver(): RemoteWebDriver | ||
protected function getWebDriver(): WebDriver | ||
{ | ||
if ($this->webDriver) { | ||
return $this->webDriver; | ||
if (!$this->webDriver) { | ||
throw new DriverException('Base driver has not been created'); | ||
} | ||
|
||
throw new DriverException('Base driver has not been created'); | ||
return $this->webDriver; | ||
} | ||
|
||
// </editor-fold> | ||
|
@@ -886,6 +902,15 @@ | |
} | ||
} | ||
|
||
/** | ||
* @throws DriverException | ||
*/ | ||
private function actions(): WebDriverActions | ||
{ | ||
// WebDriverActions are not reset after being performed - that's why we create a new instance each time. | ||
return new WebDriverActions($this->getWebDriver()); | ||
} | ||
|
||
/** | ||
* @throws DriverException | ||
*/ | ||
|
@@ -963,11 +988,12 @@ | |
* $this->executeJsOnElement($element, 'return argument[0].childNodes.length'); | ||
* ``` | ||
* | ||
* @phpstan-param TWebDriverElement $element | ||
* @return mixed | ||
* @throws DriverException | ||
*/ | ||
private function executeJsOnElement( | ||
WebDriverElement $element, | ||
$element, | ||
#[Language('JavaScript')] | ||
string $script | ||
) { | ||
|
@@ -1040,37 +1066,14 @@ | |
} | ||
} | ||
|
||
private function clickOnElement(WebDriverElement $element): void | ||
{ | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$element->click(); | ||
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @throws DriverException | ||
*/ | ||
private function doubleClickOnElement(RemoteWebElement $element): void | ||
private function clickOnElement($element): void | ||
{ | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->getWebDriver()->getMouse()->doubleClick($element->getCoordinates()); | ||
} | ||
|
||
/** | ||
* @throws DriverException | ||
*/ | ||
private function rightClickOnElement(RemoteWebElement $element): void | ||
{ | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->getWebDriver()->getMouse()->contextClick($element->getCoordinates()); | ||
} | ||
|
||
/** | ||
* @throws DriverException | ||
*/ | ||
private function mouseOverElement(RemoteWebElement $element): void | ||
{ | ||
$element->getLocationOnScreenOnceScrolledIntoView(); | ||
$this->getWebDriver()->getMouse()->mouseMove($element->getCoordinates()); | ||
$element->click(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to be consistent with the other actions by using this: $this->actions()->click($element)->perform(); but for some strange reason, it does not work well in firefox, failing many tests: I thought it might be because of the geckdriver-specific fix in My last remaining guess is that It may be useful to figure out exactly why, it might be the reason behind various fragile tests we have. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to guess. Just do real-time debugging inside a WebDriver code (if you can reproduce a problem locally). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This happens within selenium (and possibly the webbrowser). |
||
} | ||
|
||
/** | ||
|
@@ -1100,24 +1103,28 @@ | |
} | ||
|
||
/** | ||
* @phpstan-return TWebDriverElement | ||
* @throws DriverException | ||
*/ | ||
private function findElement( | ||
#[Language('XPath')] | ||
string $xpath | ||
): RemoteWebElement { | ||
) { | ||
try { | ||
$finder = WebDriverBy::xpath($xpath); | ||
return $this->getWebDriver()->findElement($finder); | ||
$element = $this->getWebDriver()->findElement($finder); | ||
assert($element instanceof WebDriverLocatable); | ||
return $element; | ||
} catch (\Throwable $e) { | ||
throw new DriverException("Failed to find element: {$e->getMessage()}", 0, $e); | ||
} | ||
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @throws DriverException | ||
*/ | ||
private function selectRadioValue(WebDriverElement $element, string $value): void | ||
private function selectRadioValue($element, string $value): void | ||
{ | ||
try { | ||
(new WebDriverRadios($element))->selectByValue($value); | ||
|
@@ -1133,9 +1140,10 @@ | |
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @throws DriverException | ||
*/ | ||
private function selectOptionOnElement(WebDriverElement $element, string $value, bool $multiple = false): void | ||
private function selectOptionOnElement($element, string $value, bool $multiple = false): void | ||
{ | ||
try { | ||
$select = new WebDriverSelect($element); | ||
|
@@ -1163,9 +1171,10 @@ | |
* | ||
* Note: this implementation does not trigger a change event after deselecting the elements. | ||
* | ||
* @phpstan-param TWebDriverElement $element | ||
* @throws DriverException | ||
*/ | ||
private function deselectAllOptions(WebDriverElement $element): void | ||
private function deselectAllOptions($element): void | ||
{ | ||
try { | ||
(new WebDriverSelect($element))->deselectAll(); | ||
|
@@ -1180,10 +1189,11 @@ | |
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @throws DriverException | ||
*/ | ||
private function ensureInputType( | ||
WebDriverElement $element, | ||
$element, | ||
#[Language('XPath')] | ||
string $xpath, | ||
string $type, | ||
|
@@ -1223,10 +1233,11 @@ | |
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @param mixed $value | ||
* @throws DriverException | ||
*/ | ||
private function setElementDomProperty(WebDriverElement $element, string $property, $value): void | ||
private function setElementDomProperty($element, string $property, $value): void | ||
{ | ||
$this->executeJsOnElement( | ||
$element, | ||
|
@@ -1235,13 +1246,14 @@ | |
} | ||
|
||
/** | ||
* @phpstan-param TWebDriverElement $element | ||
* @return mixed | ||
* @throws DriverException | ||
*/ | ||
private function getElementDomProperty(RemoteWebElement $element, string $property) | ||
private function getElementDomProperty($element, string $property) | ||
{ | ||
try { | ||
return $this->getWebDriver()->isW3cCompliant() | ||
return $this->isW3cCompliant() | ||
? $element->getDomProperty($property) | ||
: $this->executeJsOnElement($element, "return arguments[0]['$property']"); | ||
} catch (UnsupportedOperationException $e) { | ||
|
@@ -1270,5 +1282,14 @@ | |
return (string)$value; | ||
} | ||
|
||
private function isW3cCompliant(): bool | ||
{ | ||
if (!method_exists($this->getWebDriver(), 'isW3cCompliant')) { | ||
throw new DriverException('Base driver must implement an `isW3cCompliant` method that returns a boolean.'); | ||
} | ||
|
||
return (bool)$this->getWebDriver()->isW3cCompliant(); | ||
} | ||
|
||
// </editor-fold> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
Internal
namespace suggests, that this class/interface shouldn't be used outside of the library and therefore not included in BC promise. But maybe I'm mistaken.