Файловый менеджер - Редактировать - /home/clickysoft/public_html/jmapi5.clickysoft.net/php-webdriver.tar
Назад
webdriver/composer.json 0000644 00000005203 15021223212 0011245 0 ustar 00 { "name": "php-webdriver/webdriver", "description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.", "license": "MIT", "type": "library", "keywords": [ "webdriver", "selenium", "php", "geckodriver", "chromedriver" ], "homepage": "https://github.com/php-webdriver/php-webdriver", "require": { "php": "^7.3 || ^8.0", "ext-curl": "*", "ext-json": "*", "ext-zip": "*", "symfony/polyfill-mbstring": "^1.12", "symfony/process": "^5.0 || ^6.0 || ^7.0" }, "require-dev": { "ergebnis/composer-normalize": "^2.20.0", "ondram/ci-detector": "^4.0", "php-coveralls/php-coveralls": "^2.4", "php-mock/php-mock-phpunit": "^2.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", "symfony/var-dumper": "^5.0 || ^6.0" }, "replace": { "facebook/webdriver": "*" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" }, "minimum-stability": "dev", "autoload": { "psr-4": { "Facebook\\WebDriver\\": "lib/" }, "files": [ "lib/Exception/TimeoutException.php" ] }, "autoload-dev": { "psr-4": { "Facebook\\WebDriver\\": [ "tests/unit", "tests/functional" ] }, "classmap": [ "tests/functional/" ] }, "config": { "allow-plugins": { "ergebnis/composer-normalize": true }, "sort-packages": true }, "scripts": { "post-install-cmd": [ "@composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction", "@composer install --working-dir=tools/phpstan --no-progress --no-interaction" ], "post-update-cmd": [ "@composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction", "@composer update --working-dir=tools/phpstan --no-progress --no-interaction" ], "all": [ "@lint", "@analyze", "@test" ], "analyze": [ "@php tools/phpstan/vendor/bin/phpstan analyze -c phpstan.neon --ansi", "@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi", "@php vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "fix": [ "@composer normalize", "@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0", "@php vendor/bin/phpcbf --standard=PSR2 --ignore=*.js ./lib/ ./tests/" ], "lint": [ "@php vendor/bin/parallel-lint -j 10 ./lib ./tests example.php", "@composer validate", "@composer normalize --dry-run" ], "test": [ "@php vendor/bin/phpunit --colors=always" ] } } webdriver/lib/WebDriverPoint.php 0000644 00000003076 15021223212 0012713 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Represent a point. */ class WebDriverPoint { private $x; private $y; public function __construct($x, $y) { $this->x = $x; $this->y = $y; } /** * Get the x-coordinate. * * @return int The x-coordinate of the point. */ public function getX() { return (int) $this->x; } /** * Get the y-coordinate. * * @return int The y-coordinate of the point. */ public function getY() { return (int) $this->y; } /** * Set the point to a new position. * * @param int $new_x * @param int $new_y * @return WebDriverPoint The same instance with updated coordinates. */ public function move($new_x, $new_y) { $this->x = $new_x; $this->y = $new_y; return $this; } /** * Move the current by offsets. * * @param int $x_offset * @param int $y_offset * @return WebDriverPoint The same instance with updated coordinates. */ public function moveBy($x_offset, $y_offset) { $this->x += $x_offset; $this->y += $y_offset; return $this; } /** * Check whether the given point is the same as the instance. * * @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(self $point) { return $this->x === $point->getX() && $this->y === $point->getY(); } } webdriver/lib/WebDriverSelect.php 0000644 00000016342 15021223212 0013041 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnexpectedTagNameException; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Support\XPathEscaper; /** * Models a default HTML `<select>` tag, providing helper methods to select and deselect options. */ class WebDriverSelect implements WebDriverSelectInterface { /** @var WebDriverElement */ private $element; /** @var bool */ private $isMulti; public function __construct(WebDriverElement $element) { $tag_name = $element->getTagName(); if ($tag_name !== 'select') { throw new UnexpectedTagNameException('select', $tag_name); } $this->element = $element; $value = $element->getAttribute('multiple'); /** * There is a bug in safari webdriver that returns 'multiple' instead of 'true' which does not match the spec. * Apple Feedback #FB12760673 * * @see https://www.w3.org/TR/webdriver2/#get-element-attribute */ $this->isMulti = $value === 'true' || $value === 'multiple'; } public function isMultiple() { return $this->isMulti; } public function getOptions() { return $this->element->findElements(WebDriverBy::tagName('option')); } public function getAllSelectedOptions() { $selected_options = []; foreach ($this->getOptions() as $option) { if ($option->isSelected()) { $selected_options[] = $option; if (!$this->isMultiple()) { return $selected_options; } } } return $selected_options; } public function getFirstSelectedOption() { foreach ($this->getOptions() as $option) { if ($option->isSelected()) { return $option; } } throw new NoSuchElementException('No options are selected'); } public function selectByIndex($index) { foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->selectOption($option); return; } } throw new NoSuchElementException(sprintf('Cannot locate option with index: %d', $index)); } public function selectByValue($value) { $matched = false; $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with value: %s', $value) ); } } public function selectByVisibleText($text) { $matched = false; $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } // 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 (!$matched) { foreach ($this->getOptions() as $option) { if ($option->getText() === $text) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } } } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function selectByVisiblePartialText($text) { $matched = false; $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->selectOption($option); if (!$this->isMultiple()) { return; } $matched = true; } if (!$matched) { throw new NoSuchElementException( sprintf('Cannot locate option with text: %s', $text) ); } } public function deselectAll() { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect all options of a multi-select'); } foreach ($this->getOptions() as $option) { $this->deselectOption($option); } } public function deselectByIndex($index) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } foreach ($this->getOptions() as $option) { if ($option->getAttribute('index') === (string) $index) { $this->deselectOption($option); return; } } } public function deselectByValue($value) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[@value = ' . XPathEscaper::escapeQuotes($value) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisibleText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[normalize-space(.) = ' . XPathEscaper::escapeQuotes($text) . ']'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } public function deselectByVisiblePartialText($text) { if (!$this->isMultiple()) { throw new UnsupportedOperationException('You may only deselect options of a multi-select'); } $xpath = './/option[contains(normalize-space(.), ' . XPathEscaper::escapeQuotes($text) . ')]'; $options = $this->element->findElements(WebDriverBy::xpath($xpath)); foreach ($options as $option) { $this->deselectOption($option); } } /** * Mark option selected */ protected function selectOption(WebDriverElement $option) { if (!$option->isSelected()) { $option->click(); } } /** * Mark option not selected */ protected function deselectOption(WebDriverElement $option) { if ($option->isSelected()) { $option->click(); } } } webdriver/lib/WebDriverElement.php 0000644 00000010613 15021223212 0013206 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\ShadowRoot; /** * Interface for an HTML element in the WebDriver framework. */ interface WebDriverElement extends WebDriverSearchContext { /** * If this element is a TEXTAREA or text INPUT element, this will clear the value. * * @return WebDriverElement The current instance. */ public function clear(); /** * Click this element. * * @return WebDriverElement The current instance. */ public function click(); /** * Get the value of the given attribute of the element. * Attribute is meant what is declared in the HTML markup of the element. * To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method. * * @param string $attribute_name The name of the attribute. * @return string|null The value of the attribute. */ public function getAttribute($attribute_name); /* * Gets the value of a IDL JavaScript property of this element (for example `innerHTML`, `tagName` etc.). * * @see https://developer.mozilla.org/en-US/docs/Glossary/IDL * @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties * @param string $propertyName * @return string|null The property's current value or null if the value is not set or the property does not exist. * @todo Add in next major release (BC) */ // public function getDomProperty($propertyName); /** * Get the value of a given CSS property. * * @param string $css_property_name The name of the CSS property. * @return string The value of the CSS property. */ public function getCSSValue($css_property_name); /** * Get the location of element relative to the top-left corner of the page. * * @return WebDriverPoint The location of the element. */ public function getLocation(); /** * Try scrolling the element into the view port and return the location of * element relative to the top-left corner of the page afterwards. * * @return WebDriverPoint The location of the element. */ public function getLocationOnScreenOnceScrolledIntoView(); /** * Get the size of element. * * @return WebDriverDimension The dimension of the element. */ public function getSize(); /** * Get the tag name of this element. * * @return string The tag name. */ public function getTagName(); /** * Get the visible (i.e. not hidden by CSS) innerText of this element, * including sub-elements, without any leading or trailing whitespace. * * @return string The visible innerText of this element. */ public function getText(); /** * Is this element displayed or not? This method avoids the problem of having * to parse an element's "style" attribute. * * @return bool */ public function isDisplayed(); /** * Is the element currently enabled or not? This will generally return true * for everything but disabled input elements. * * @return bool */ public function isEnabled(); /** * Determine whether or not this element is selected or not. * * @return bool */ public function isSelected(); /** * Simulate typing into an element, which may set its value. * * @param mixed $value The data to be typed. * @return WebDriverElement The current instance. */ public function sendKeys($value); /** * If this current element is a form, or an element within a form, then this * will be submitted to the remote server. * * @return WebDriverElement The current instance. */ public function submit(); /** * Get the opaque ID of the element. * * @return string The opaque ID. */ public function getID(); /** * Take screenshot of a specific element. * * @param string $save_as The path of the screenshot to be saved. * @return string The screenshot in PNG format. * @todo Add in next major release (BC) */ //public function takeElementScreenshot($save_as = null); /** * Get representation of an element's shadow root for accessing the shadow DOM of a web component. * * @return ShadowRoot * @todo Add in next major release (BC) */ //public function getShadowRoot(); } webdriver/lib/WebDriverNavigation.php 0000644 00000001553 15021223212 0013717 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; class WebDriverNavigation implements WebDriverNavigationInterface { protected $executor; public function __construct(ExecuteMethod $executor) { $this->executor = $executor; } public function back() { $this->executor->execute(DriverCommand::GO_BACK); return $this; } public function forward() { $this->executor->execute(DriverCommand::GO_FORWARD); return $this; } public function refresh() { $this->executor->execute(DriverCommand::REFRESH); return $this; } public function to($url) { $params = ['url' => (string) $url]; $this->executor->execute(DriverCommand::GET, $params); return $this; } } webdriver/lib/WebDriverEventListener.php 0000644 00000003046 15021223212 0014406 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Support\Events\EventFiringWebDriver; use Facebook\WebDriver\Support\Events\EventFiringWebElement; interface WebDriverEventListener { /** * @param string $url */ public function beforeNavigateTo($url, EventFiringWebDriver $driver); /** * @param string $url */ public function afterNavigateTo($url, EventFiringWebDriver $driver); public function beforeNavigateBack(EventFiringWebDriver $driver); public function afterNavigateBack(EventFiringWebDriver $driver); public function beforeNavigateForward(EventFiringWebDriver $driver); public function afterNavigateForward(EventFiringWebDriver $driver); public function beforeFindBy(WebDriverBy $by, $element, EventFiringWebDriver $driver); public function afterFindBy(WebDriverBy $by, $element, EventFiringWebDriver $driver); /** * @param string $script */ public function beforeScript($script, EventFiringWebDriver $driver); /** * @param string $script */ public function afterScript($script, EventFiringWebDriver $driver); public function beforeClickOn(EventFiringWebElement $element); public function afterClickOn(EventFiringWebElement $element); public function beforeChangeValueOf(EventFiringWebElement $element); public function afterChangeValueOf(EventFiringWebElement $element); public function onException(WebDriverException $exception, EventFiringWebDriver $driver = null); } webdriver/lib/WebDriverUpAction.php 0000644 00000001147 15021223212 0013341 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchAction; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen; class WebDriverUpAction extends WebDriverTouchAction implements WebDriverAction { private $x; private $y; /** * @param int $x * @param int $y */ public function __construct(WebDriverTouchScreen $touch_screen, $x, $y) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->up($this->x, $this->y); } } webdriver/lib/Support/IsElementDisplayedAtom.php 0000644 00000003775 15021223212 0016037 0 ustar 00 <?php namespace Facebook\WebDriver\Support; use Facebook\WebDriver\Remote\RemoteExecuteMethod; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\RemoteWebElement; use Facebook\WebDriver\Remote\WebDriverBrowserType; /** * Certain drivers have decided to not provide the endpoint which determines element displayedness, because * the W3C WebDriver specification no longer dictates it. * * In those instances, we determine this using a script ("atom"). * * @see https://w3c.github.io/webdriver/#element-displayedness * * Also note in case more than this one atom is used, this logic here should be refactored to some AbstractAtom. */ class IsElementDisplayedAtom { /** * List of browsers which are known to support /displayed endpoint on their own (so they don't need this atom). * * @var array */ public const BROWSERS_WITH_ENDPOINT_SUPPORT = [ WebDriverBrowserType::CHROME, WebDriverBrowserType::FIREFOX, WebDriverBrowserType::MICROSOFT_EDGE, ]; /** * @var RemoteWebDriver */ private $driver; public function __construct(RemoteWebDriver $driver) { $this->driver = $driver; } public static function match($browserName) { return !in_array($browserName, self::BROWSERS_WITH_ENDPOINT_SUPPORT, true); } public function execute($params) { $element = new RemoteWebElement( new RemoteExecuteMethod($this->driver), $params[':id'], $this->driver->isW3cCompliant() ); return $this->executeAtom('isElementDisplayed', $element); } protected function executeAtom($atomName, ...$params) { return $this->driver->executeScript( sprintf('%s; return (%s).apply(null, arguments);', $this->loadAtomScript($atomName), $atomName), $params ); } private function loadAtomScript($atomName) { return file_get_contents(__DIR__ . '/../scripts/' . $atomName . '.js'); } } webdriver/lib/Support/XPathEscaper.php 0000644 00000001624 15021223212 0014010 0 ustar 00 <?php namespace Facebook\WebDriver\Support; class XPathEscaper { /** * Converts xpath strings with both quotes and ticks into: * `foo'"bar` -> `concat('foo', "'" ,'"bar')` * * @param string $xpathToEscape The xpath to be converted. * @return string The escaped string. */ public static function escapeQuotes($xpathToEscape) { // Single quotes not present => we can quote in them if (mb_strpos($xpathToEscape, "'") === false) { return sprintf("'%s'", $xpathToEscape); } // Double quotes not present => we can quote in them if (mb_strpos($xpathToEscape, '"') === false) { return sprintf('"%s"', $xpathToEscape); } // Both single and double quotes are present return sprintf( "concat('%s')", str_replace("'", "', \"'\" ,'", $xpathToEscape) ); } } webdriver/lib/Support/ScreenshotHelper.php 0000644 00000004445 15021223212 0014742 0 ustar 00 <?php namespace Facebook\WebDriver\Support; use Facebook\WebDriver\Exception\Internal\IOException; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\RemoteExecuteMethod; /** * Helper class to handle taking, decoding and screenshots using WebDriver. */ class ScreenshotHelper { /** @var RemoteExecuteMethod */ private $executor; public function __construct(RemoteExecuteMethod $executor) { $this->executor = $executor; } /** * @param string|null $saveAs * @throws WebDriverException * @return string */ public function takePageScreenshot($saveAs = null) { $commandToExecute = [DriverCommand::SCREENSHOT]; return $this->takeScreenshot($commandToExecute, $saveAs); } public function takeElementScreenshot($elementId, $saveAs = null) { $commandToExecute = [DriverCommand::TAKE_ELEMENT_SCREENSHOT, [':id' => $elementId]]; return $this->takeScreenshot($commandToExecute, $saveAs); } private function takeScreenshot(array $commandToExecute, $saveAs = null) { $response = $this->executor->execute(...$commandToExecute); if (!is_string($response)) { throw UnexpectedResponseException::forError( 'Error taking screenshot, no data received from the remote end' ); } $screenshot = base64_decode($response, true); if ($screenshot === false) { throw UnexpectedResponseException::forError('Error decoding screenshot data'); } if ($saveAs !== null) { $this->saveScreenshotToPath($screenshot, $saveAs); } return $screenshot; } private function saveScreenshotToPath($screenshot, $path) { $this->createDirectoryIfNotExists(dirname($path)); file_put_contents($path, $screenshot); } private function createDirectoryIfNotExists($directoryPath) { if (!file_exists($directoryPath)) { if (!mkdir($directoryPath, 0777, true) && !is_dir($directoryPath)) { throw IOException::forFileError('Directory cannot be not created', $directoryPath); } } } } webdriver/lib/Support/Events/EventFiringWebDriver.php 0000644 00000023300 15021223212 0016752 0 ustar 00 <?php namespace Facebook\WebDriver\Support\Events; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDispatcher; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverOptions; use Facebook\WebDriver\WebDriverTargetLocator; use Facebook\WebDriver\WebDriverWait; class EventFiringWebDriver implements WebDriver, JavaScriptExecutor { /** * @var WebDriver */ protected $driver; /** * @var WebDriverDispatcher */ protected $dispatcher; public function __construct(WebDriver $driver, WebDriverDispatcher $dispatcher = null) { $this->dispatcher = $dispatcher ?: new WebDriverDispatcher(); if (!$this->dispatcher->getDefaultDriver()) { $this->dispatcher->setDefaultDriver($this); } $this->driver = $driver; } /** * @return WebDriverDispatcher */ public function getDispatcher() { return $this->dispatcher; } /** * @return WebDriver */ public function getWebDriver() { return $this->driver; } /** * @param mixed $url * @throws WebDriverException * @return $this */ public function get($url) { $this->dispatch('beforeNavigateTo', $url, $this); try { $this->driver->get($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterNavigateTo', $url, $this); return $this; } /** * @throws WebDriverException * @return array */ public function findElements(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); $elements = []; try { foreach ($this->driver->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $elements; } /** * @throws WebDriverException * @return EventFiringWebElement */ public function findElement(WebDriverBy $by) { $this->dispatch('beforeFindBy', $by, null, $this); try { $element = $this->newElement($this->driver->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterFindBy', $by, null, $this); return $element; } /** * @param string $script * @throws WebDriverException * @return mixed */ public function executeScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } /** * @param string $script * @throws WebDriverException * @return mixed */ public function executeAsyncScript($script, array $arguments = []) { if (!$this->driver instanceof JavaScriptExecutor) { throw new UnsupportedOperationException( 'driver does not implement JavaScriptExecutor' ); } $this->dispatch('beforeScript', $script, $this); try { $result = $this->driver->executeAsyncScript($script, $arguments); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterScript', $script, $this); return $result; } /** * @throws WebDriverException * @return $this */ public function close() { try { $this->driver->close(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getCurrentURL() { try { return $this->driver->getCurrentURL(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getPageSource() { try { return $this->driver->getPageSource(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getTitle() { try { return $this->driver->getTitle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getWindowHandle() { try { return $this->driver->getWindowHandle(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return array */ public function getWindowHandles() { try { return $this->driver->getWindowHandles(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException */ public function quit() { try { $this->driver->quit(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @param null|string $save_as * @throws WebDriverException * @return string */ public function takeScreenshot($save_as = null) { try { return $this->driver->takeScreenshot($save_as); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @param int $timeout_in_second * @param int $interval_in_millisecond * @throws WebDriverException * @return WebDriverWait */ public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { try { return $this->driver->wait($timeout_in_second, $interval_in_millisecond); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverOptions */ public function manage() { try { return $this->driver->manage(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return EventFiringWebDriverNavigation */ public function navigate() { try { return new EventFiringWebDriverNavigation( $this->driver->navigate(), $this->getDispatcher() ); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverTargetLocator */ public function switchTo() { try { return $this->driver->switchTo(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverTouchScreen */ public function getTouch() { try { return $this->driver->getTouch(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function execute($name, $params) { try { return $this->driver->execute($name, $params); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @return EventFiringWebElement */ protected function newElement(WebDriverElement $element) { return new EventFiringWebElement($element, $this->getDispatcher()); } /** * @param mixed $method * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch('onException', $exception, $this); } } webdriver/lib/Support/Events/EventFiringWebElement.php 0000644 00000023152 15021223212 0017115 0 ustar 00 <?php namespace Facebook\WebDriver\Support\Events; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\WebDriverDispatcher; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverPoint; class EventFiringWebElement implements WebDriverElement, WebDriverLocatable { /** * @var WebDriverElement */ protected $element; /** * @var WebDriverDispatcher */ protected $dispatcher; public function __construct(WebDriverElement $element, WebDriverDispatcher $dispatcher) { $this->element = $element; $this->dispatcher = $dispatcher; } /** * @return WebDriverDispatcher */ public function getDispatcher() { return $this->dispatcher; } /** * @return WebDriverElement */ public function getElement() { return $this->element; } /** * @param mixed $value * @throws WebDriverException * @return $this */ public function sendKeys($value) { $this->dispatch('beforeChangeValueOf', $this); try { $this->element->sendKeys($value); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterChangeValueOf', $this); return $this; } /** * @throws WebDriverException * @return $this */ public function click() { $this->dispatch('beforeClickOn', $this); try { $this->element->click(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch('afterClickOn', $this); return $this; } /** * @throws WebDriverException * @return EventFiringWebElement */ public function findElement(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $element = $this->newElement($this->element->findElement($by)); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $element; } /** * @throws WebDriverException * @return array */ public function findElements(WebDriverBy $by) { $this->dispatch( 'beforeFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); try { $elements = []; foreach ($this->element->findElements($by) as $element) { $elements[] = $this->newElement($element); } } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterFindBy', $by, $this, $this->dispatcher->getDefaultDriver() ); return $elements; } /** * @throws WebDriverException * @return $this */ public function clear() { try { $this->element->clear(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @param string $attribute_name * @throws WebDriverException * @return string */ public function getAttribute($attribute_name) { try { return $this->element->getAttribute($attribute_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @param string $css_property_name * @throws WebDriverException * @return string */ public function getCSSValue($css_property_name) { try { return $this->element->getCSSValue($css_property_name); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverPoint */ public function getLocation() { try { return $this->element->getLocation(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverPoint */ public function getLocationOnScreenOnceScrolledIntoView() { try { return $this->element->getLocationOnScreenOnceScrolledIntoView(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @return WebDriverCoordinates */ public function getCoordinates() { try { return $this->element->getCoordinates(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return WebDriverDimension */ public function getSize() { try { return $this->element->getSize(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getTagName() { try { return $this->element->getTagName(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getText() { try { return $this->element->getText(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return bool */ public function isDisplayed() { try { return $this->element->isDisplayed(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return bool */ public function isEnabled() { try { return $this->element->isEnabled(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return bool */ public function isSelected() { try { return $this->element->isSelected(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return $this */ public function submit() { try { $this->element->submit(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * @throws WebDriverException * @return string */ public function getID() { try { return $this->element->getID(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } /** * Test if two element IDs refer to the same DOM element. * * @return bool */ public function equals(WebDriverElement $other) { try { return $this->element->equals($other); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function takeElementScreenshot($save_as = null) { try { return $this->element->takeElementScreenshot($save_as); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function getShadowRoot() { try { return $this->element->getShadowRoot(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch( 'onException', $exception, $this->dispatcher->getDefaultDriver() ); } /** * @param mixed $method * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } /** * @return static */ protected function newElement(WebDriverElement $element) { return new static($element, $this->getDispatcher()); } } webdriver/lib/Support/Events/EventFiringWebDriverNavigation.php 0000644 00000006037 15021223212 0021002 0 ustar 00 <?php namespace Facebook\WebDriver\Support\Events; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\WebDriverDispatcher; use Facebook\WebDriver\WebDriverNavigationInterface; class EventFiringWebDriverNavigation implements WebDriverNavigationInterface { /** * @var WebDriverNavigationInterface */ protected $navigator; /** * @var WebDriverDispatcher */ protected $dispatcher; public function __construct(WebDriverNavigationInterface $navigator, WebDriverDispatcher $dispatcher) { $this->navigator = $navigator; $this->dispatcher = $dispatcher; } /** * @return WebDriverDispatcher */ public function getDispatcher() { return $this->dispatcher; } /** * @return WebDriverNavigationInterface */ public function getNavigator() { return $this->navigator; } public function back() { $this->dispatch( 'beforeNavigateBack', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->back(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateBack', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function forward() { $this->dispatch( 'beforeNavigateForward', $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->forward(); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); } $this->dispatch( 'afterNavigateForward', $this->getDispatcher()->getDefaultDriver() ); return $this; } public function refresh() { try { $this->navigator->refresh(); return $this; } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } } public function to($url) { $this->dispatch( 'beforeNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); try { $this->navigator->to($url); } catch (WebDriverException $exception) { $this->dispatchOnException($exception); throw $exception; } $this->dispatch( 'afterNavigateTo', $url, $this->getDispatcher()->getDefaultDriver() ); return $this; } /** * @param mixed $method * @param mixed ...$arguments */ protected function dispatch($method, ...$arguments) { if (!$this->dispatcher) { return; } $this->dispatcher->dispatch($method, $arguments); } protected function dispatchOnException(WebDriverException $exception) { $this->dispatch('onException', $exception); } } webdriver/lib/Interactions/Internal/WebDriverClickAndHoldAction.php 0000644 00000000534 15021223212 0021451 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; /** * Move the the location, click and hold. */ class WebDriverClickAndHoldAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->mouseDown($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverButtonReleaseAction.php 0000644 00000000551 15021223212 0021745 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; /** * Move to the location and then release the mouse key. */ class WebDriverButtonReleaseAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->mouseUp($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverClickAction.php 0000644 00000000437 15021223212 0020221 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; class WebDriverClickAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->click($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverCoordinates.php 0000644 00000003050 15021223212 0020302 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\WebDriverPoint; /** * Interface representing basic mouse operations. */ class WebDriverCoordinates { /** * @var mixed * @todo remove in next major version (if it is unused) */ private $onScreen; /** * @var callable */ private $inViewPort; /** * @var callable */ private $onPage; /** * @var string */ private $auxiliary; /** * @param mixed $on_screen * @param string $auxiliary */ public function __construct($on_screen, callable $in_view_port, callable $on_page, $auxiliary) { $this->onScreen = $on_screen; $this->inViewPort = $in_view_port; $this->onPage = $on_page; $this->auxiliary = $auxiliary; } /** * @throws UnsupportedOperationException * @return WebDriverPoint */ public function onScreen() { throw new UnsupportedOperationException( 'onScreen is planned but not yet supported by Selenium' ); } /** * @return WebDriverPoint */ public function inViewPort() { return call_user_func($this->inViewPort); } /** * @return WebDriverPoint */ public function onPage() { return call_user_func($this->onPage); } /** * @return string The attached object id. */ public function getAuxiliary() { return $this->auxiliary; } } webdriver/lib/Interactions/Internal/WebDriverSendKeysAction.php 0000644 00000001512 15021223212 0020714 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; class WebDriverSendKeysAction extends WebDriverKeysRelatedAction implements WebDriverAction { /** * @var string */ private $keys = ''; /** * @param string $keys */ public function __construct( WebDriverKeyboard $keyboard, WebDriverMouse $mouse, WebDriverLocatable $location_provider = null, $keys = '' ) { parent::__construct($keyboard, $mouse, $location_provider); $this->keys = $keys; } public function perform() { $this->focusOnElement(); $this->keyboard->sendKeys($this->keys); } } webdriver/lib/Interactions/Internal/WebDriverMouseAction.php 0000644 00000001662 15021223212 0020265 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverMouse; /** * Base class for all mouse-related actions. */ class WebDriverMouseAction { /** * @var WebDriverMouse */ protected $mouse; /** * @var WebDriverLocatable */ protected $locationProvider; public function __construct(WebDriverMouse $mouse, WebDriverLocatable $location_provider = null) { $this->mouse = $mouse; $this->locationProvider = $location_provider; } /** * @return null|WebDriverCoordinates */ protected function getActionLocation() { if ($this->locationProvider !== null) { return $this->locationProvider->getCoordinates(); } return null; } protected function moveToLocation() { $this->mouse->mouseMove($this->locationProvider); } } webdriver/lib/Interactions/Internal/WebDriverContextClickAction.php 0000644 00000000543 15021223212 0021564 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; /** * You can call it 'Right Click' if you like. */ class WebDriverContextClickAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->contextClick($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverKeysRelatedAction.php 0000644 00000001732 15021223212 0021407 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverMouse; /** * Base class for all keyboard-related actions. */ abstract class WebDriverKeysRelatedAction { /** * @var WebDriverKeyboard */ protected $keyboard; /** * @var WebDriverMouse */ protected $mouse; /** * @var WebDriverLocatable|null */ protected $locationProvider; public function __construct( WebDriverKeyboard $keyboard, WebDriverMouse $mouse, WebDriverLocatable $location_provider = null ) { $this->keyboard = $keyboard; $this->mouse = $mouse; $this->locationProvider = $location_provider; } protected function focusOnElement() { if ($this->locationProvider) { $this->mouse->click($this->locationProvider->getCoordinates()); } } } webdriver/lib/Interactions/Internal/WebDriverKeyUpAction.php 0000644 00000000370 15021223212 0020225 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; class WebDriverKeyUpAction extends WebDriverSingleKeyAction { public function perform() { $this->focusOnElement(); $this->keyboard->releaseKey($this->key); } } webdriver/lib/Interactions/Internal/WebDriverSingleKeyAction.php 0000644 00000003112 15021223212 0021057 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverKeys; use Facebook\WebDriver\WebDriverMouse; abstract class WebDriverSingleKeyAction extends WebDriverKeysRelatedAction implements WebDriverAction { public const MODIFIER_KEYS = [ WebDriverKeys::SHIFT, WebDriverKeys::LEFT_SHIFT, WebDriverKeys::RIGHT_SHIFT, WebDriverKeys::CONTROL, WebDriverKeys::LEFT_CONTROL, WebDriverKeys::RIGHT_CONTROL, WebDriverKeys::ALT, WebDriverKeys::LEFT_ALT, WebDriverKeys::RIGHT_ALT, WebDriverKeys::META, WebDriverKeys::RIGHT_META, WebDriverKeys::COMMAND, ]; /** @var string */ protected $key; /** * @param string $key * @todo Remove default $key value in next major version (BC) */ public function __construct( WebDriverKeyboard $keyboard, WebDriverMouse $mouse, WebDriverLocatable $location_provider = null, $key = '' ) { parent::__construct($keyboard, $mouse, $location_provider); if (!in_array($key, self::MODIFIER_KEYS, true)) { throw LogicException::forError( sprintf( 'keyDown / keyUp actions can only be used for modifier keys, but "%s" was given', $key ) ); } $this->key = $key; } } webdriver/lib/Interactions/Internal/WebDriverMouseMoveAction.php 0000644 00000000447 15021223212 0021114 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; class WebDriverMouseMoveAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->mouseMove($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverDoubleClickAction.php 0000644 00000000453 15021223212 0021352 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\WebDriverAction; class WebDriverDoubleClickAction extends WebDriverMouseAction implements WebDriverAction { public function perform() { $this->mouse->doubleClick($this->getActionLocation()); } } webdriver/lib/Interactions/Internal/WebDriverKeyDownAction.php 0000644 00000000370 15021223212 0020550 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; class WebDriverKeyDownAction extends WebDriverSingleKeyAction { public function perform() { $this->focusOnElement(); $this->keyboard->pressKey($this->key); } } webdriver/lib/Interactions/Internal/WebDriverMoveToOffsetAction.php 0000644 00000001725 15021223212 0021555 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Internal; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverMouse; class WebDriverMoveToOffsetAction extends WebDriverMouseAction implements WebDriverAction { /** * @var int|null */ private $xOffset; /** * @var int|null */ private $yOffset; /** * @param int|null $x_offset * @param int|null $y_offset */ public function __construct( WebDriverMouse $mouse, WebDriverLocatable $location_provider = null, $x_offset = null, $y_offset = null ) { parent::__construct($mouse, $location_provider); $this->xOffset = $x_offset; $this->yOffset = $y_offset; } public function perform() { $this->mouse->mouseMove( $this->getActionLocation(), $this->xOffset, $this->yOffset ); } } webdriver/lib/Interactions/Touch/WebDriverMoveAction.php 0000644 00000001046 15021223212 0017405 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverMoveAction extends WebDriverTouchAction implements WebDriverAction { private $x; private $y; /** * @param int $x * @param int $y */ public function __construct(WebDriverTouchScreen $touch_screen, $x, $y) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->move($this->x, $this->y); } } webdriver/lib/Interactions/Touch/WebDriverLongPressAction.php 0000644 00000000447 15021223212 0020417 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverLongPressAction extends WebDriverTouchAction implements WebDriverAction { public function perform() { $this->touchScreen->longPress($this->locationProvider); } } webdriver/lib/Interactions/Touch/WebDriverTouchScreen.php 0000644 00000004634 15021223212 0017571 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverElement; /** * Interface representing touch screen operations. */ interface WebDriverTouchScreen { /** * Single tap on the touch enabled device. * * @return $this */ public function tap(WebDriverElement $element); /** * Double tap on the touch screen using finger motion events. * * @return $this */ public function doubleTap(WebDriverElement $element); /** * Finger down on the screen. * * @param int $x * @param int $y * @return $this */ public function down($x, $y); /** * Flick on the touch screen using finger motion events. Use this flick * command if you don't care where the flick starts on the screen. * * @param int $xspeed * @param int $yspeed * @return $this */ public function flick($xspeed, $yspeed); /** * Flick on the touch screen using finger motion events. * This flickcommand starts at a particular screen location. * * @param int $xoffset * @param int $yoffset * @param int $speed * @return $this */ public function flickFromElement( WebDriverElement $element, $xoffset, $yoffset, $speed ); /** * Long press on the touch screen using finger motion events. * * @return $this */ public function longPress(WebDriverElement $element); /** * Finger move on the screen. * * @param int $x * @param int $y * @return $this */ public function move($x, $y); /** * Scroll on the touch screen using finger based motion events. Use this * command if you don't care where the scroll starts on the screen. * * @param int $xoffset * @param int $yoffset * @return $this */ public function scroll($xoffset, $yoffset); /** * Scroll on the touch screen using finger based motion events. Use this * command to start scrolling at a particular screen location. * * @param int $xoffset * @param int $yoffset * @return $this */ public function scrollFromElement( WebDriverElement $element, $xoffset, $yoffset ); /** * Finger up on the screen. * * @param int $x * @param int $y * @return $this */ public function up($x, $y); } webdriver/lib/Interactions/Touch/WebDriverDownAction.php 0000644 00000001146 15021223212 0017407 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverDownAction extends WebDriverTouchAction implements WebDriverAction { /** * @var int */ private $x; /** * @var int */ private $y; /** * @param int $x * @param int $y */ public function __construct(WebDriverTouchScreen $touch_screen, $x, $y) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->down($this->x, $this->y); } } webdriver/lib/Interactions/Touch/WebDriverScrollFromElementAction.php 0000644 00000001367 15021223212 0022101 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverElement; class WebDriverScrollFromElementAction extends WebDriverTouchAction implements WebDriverAction { private $x; private $y; /** * @param int $x * @param int $y */ public function __construct( WebDriverTouchScreen $touch_screen, WebDriverElement $element, $x, $y ) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->scrollFromElement( $this->locationProvider, $this->x, $this->y ); } } webdriver/lib/Interactions/Touch/WebDriverTouchAction.php 0000644 00000001577 15021223212 0017572 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; /** * Base class for all touch-related actions. */ abstract class WebDriverTouchAction { /** * @var WebDriverTouchScreen */ protected $touchScreen; /** * @var WebDriverLocatable */ protected $locationProvider; public function __construct( WebDriverTouchScreen $touch_screen, WebDriverLocatable $location_provider = null ) { $this->touchScreen = $touch_screen; $this->locationProvider = $location_provider; } /** * @return null|WebDriverCoordinates */ protected function getActionLocation() { return $this->locationProvider !== null ? $this->locationProvider->getCoordinates() : null; } } webdriver/lib/Interactions/Touch/WebDriverTapAction.php 0000644 00000000433 15021223212 0017222 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverTapAction extends WebDriverTouchAction implements WebDriverAction { public function perform() { $this->touchScreen->tap($this->locationProvider); } } webdriver/lib/Interactions/Touch/WebDriverDoubleTapAction.php 0000644 00000000447 15021223212 0020362 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverDoubleTapAction extends WebDriverTouchAction implements WebDriverAction { public function perform() { $this->touchScreen->doubleTap($this->locationProvider); } } webdriver/lib/Interactions/Touch/WebDriverScrollAction.php 0000644 00000001052 15021223212 0017732 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverScrollAction extends WebDriverTouchAction implements WebDriverAction { private $x; private $y; /** * @param int $x * @param int $y */ public function __construct(WebDriverTouchScreen $touch_screen, $x, $y) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->scroll($this->x, $this->y); } } webdriver/lib/Interactions/Touch/WebDriverFlickAction.php 0000644 00000001150 15021223212 0017523 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; class WebDriverFlickAction extends WebDriverTouchAction implements WebDriverAction { /** * @var int */ private $x; /** * @var int */ private $y; /** * @param int $x * @param int $y */ public function __construct(WebDriverTouchScreen $touch_screen, $x, $y) { $this->x = $x; $this->y = $y; parent::__construct($touch_screen); } public function perform() { $this->touchScreen->flick($this->x, $this->y); } } webdriver/lib/Interactions/Touch/WebDriverFlickFromElementAction.php 0000644 00000001713 15021223212 0021666 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions\Touch; use Facebook\WebDriver\WebDriverAction; use Facebook\WebDriver\WebDriverElement; class WebDriverFlickFromElementAction extends WebDriverTouchAction implements WebDriverAction { /** * @var int */ private $x; /** * @var int */ private $y; /** * @var int */ private $speed; /** * @param int $x * @param int $y * @param int $speed */ public function __construct( WebDriverTouchScreen $touch_screen, WebDriverElement $element, $x, $y, $speed ) { $this->x = $x; $this->y = $y; $this->speed = $speed; parent::__construct($touch_screen, $element); } public function perform() { $this->touchScreen->flickFromElement( $this->locationProvider, $this->x, $this->y, $this->speed ); } } webdriver/lib/Interactions/WebDriverActions.php 0000644 00000015610 15021223212 0015661 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions; use Facebook\WebDriver\Interactions\Internal\WebDriverButtonReleaseAction; use Facebook\WebDriver\Interactions\Internal\WebDriverClickAction; use Facebook\WebDriver\Interactions\Internal\WebDriverClickAndHoldAction; use Facebook\WebDriver\Interactions\Internal\WebDriverContextClickAction; use Facebook\WebDriver\Interactions\Internal\WebDriverDoubleClickAction; use Facebook\WebDriver\Interactions\Internal\WebDriverKeyDownAction; use Facebook\WebDriver\Interactions\Internal\WebDriverKeyUpAction; use Facebook\WebDriver\Interactions\Internal\WebDriverMouseMoveAction; use Facebook\WebDriver\Interactions\Internal\WebDriverMoveToOffsetAction; use Facebook\WebDriver\Interactions\Internal\WebDriverSendKeysAction; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverHasInputDevices; /** * WebDriver action builder. It implements the builder pattern. */ class WebDriverActions { protected $driver; protected $keyboard; protected $mouse; protected $action; public function __construct(WebDriverHasInputDevices $driver) { $this->driver = $driver; $this->keyboard = $driver->getKeyboard(); $this->mouse = $driver->getMouse(); $this->action = new WebDriverCompositeAction(); } /** * A convenience method for performing the actions without calling build(). */ public function perform() { $this->action->perform(); } /** * Mouse click. * If $element is provided, move to the middle of the element first. * * @return WebDriverActions */ public function click(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAction($this->mouse, $element) ); return $this; } /** * Mouse click and hold. * If $element is provided, move to the middle of the element first. * * @return WebDriverActions */ public function clickAndHold(WebDriverElement $element = null) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $element) ); return $this; } /** * Context-click (right click). * If $element is provided, move to the middle of the element first. * * @return WebDriverActions */ public function contextClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverContextClickAction($this->mouse, $element) ); return $this; } /** * Double click. * If $element is provided, move to the middle of the element first. * * @return WebDriverActions */ public function doubleClick(WebDriverElement $element = null) { $this->action->addAction( new WebDriverDoubleClickAction($this->mouse, $element) ); return $this; } /** * Drag and drop from $source to $target. * * @return WebDriverActions */ public function dragAndDrop(WebDriverElement $source, WebDriverElement $target) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMouseMoveAction($this->mouse, $target) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $target) ); return $this; } /** * Drag $source and drop by offset ($x_offset, $y_offset). * * @param int $x_offset * @param int $y_offset * @return WebDriverActions */ public function dragAndDropBy(WebDriverElement $source, $x_offset, $y_offset) { $this->action->addAction( new WebDriverClickAndHoldAction($this->mouse, $source) ); $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, null) ); return $this; } /** * Mouse move by offset. * * @param int $x_offset * @param int $y_offset * @return WebDriverActions */ public function moveByOffset($x_offset, $y_offset) { $this->action->addAction( new WebDriverMoveToOffsetAction($this->mouse, null, $x_offset, $y_offset) ); return $this; } /** * Move to the middle of the given WebDriverElement. * Extra shift, calculated from the top-left corner of the element, can be set by passing $x_offset and $y_offset * parameters. * * @param int $x_offset * @param int $y_offset * @return WebDriverActions */ public function moveToElement(WebDriverElement $element, $x_offset = null, $y_offset = null) { $this->action->addAction(new WebDriverMoveToOffsetAction( $this->mouse, $element, $x_offset, $y_offset )); return $this; } /** * Release the mouse button. * If $element is provided, move to the middle of the element first. * * @return WebDriverActions */ public function release(WebDriverElement $element = null) { $this->action->addAction( new WebDriverButtonReleaseAction($this->mouse, $element) ); return $this; } /** * Press a key on keyboard. * If $element is provided, focus on that element first. * * @see WebDriverKeys for special keys like CONTROL, ALT, etc. * @param string $key * @return WebDriverActions */ public function keyDown(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyDownAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } /** * Release a key on keyboard. * If $element is provided, focus on that element first. * * @see WebDriverKeys for special keys like CONTROL, ALT, etc. * @param string $key * @return WebDriverActions */ public function keyUp(WebDriverElement $element = null, $key = null) { $this->action->addAction( new WebDriverKeyUpAction($this->keyboard, $this->mouse, $element, $key) ); return $this; } /** * Send keys by keyboard. * If $element is provided, focus on that element first (using single mouse click). * * @see WebDriverKeys for special keys like CONTROL, ALT, etc. * @param string $keys * @return WebDriverActions */ public function sendKeys(WebDriverElement $element = null, $keys = null) { $this->action->addAction( new WebDriverSendKeysAction( $this->keyboard, $this->mouse, $element, $keys ) ); return $this; } } webdriver/lib/Interactions/WebDriverTouchActions.php 0000644 00000010032 15021223212 0016655 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions; use Facebook\WebDriver\Interactions\Touch\WebDriverDoubleTapAction; use Facebook\WebDriver\Interactions\Touch\WebDriverDownAction; use Facebook\WebDriver\Interactions\Touch\WebDriverFlickAction; use Facebook\WebDriver\Interactions\Touch\WebDriverFlickFromElementAction; use Facebook\WebDriver\Interactions\Touch\WebDriverLongPressAction; use Facebook\WebDriver\Interactions\Touch\WebDriverMoveAction; use Facebook\WebDriver\Interactions\Touch\WebDriverScrollAction; use Facebook\WebDriver\Interactions\Touch\WebDriverScrollFromElementAction; use Facebook\WebDriver\Interactions\Touch\WebDriverTapAction; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverUpAction; /** * WebDriver action builder for touch events */ class WebDriverTouchActions extends WebDriverActions { /** * @var WebDriverTouchScreen */ protected $touchScreen; public function __construct(WebDriver $driver) { parent::__construct($driver); $this->touchScreen = $driver->getTouch(); } /** * @return WebDriverTouchActions */ public function tap(WebDriverElement $element) { $this->action->addAction( new WebDriverTapAction($this->touchScreen, $element) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function down($x, $y) { $this->action->addAction( new WebDriverDownAction($this->touchScreen, $x, $y) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function up($x, $y) { $this->action->addAction( new WebDriverUpAction($this->touchScreen, $x, $y) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function move($x, $y) { $this->action->addAction( new WebDriverMoveAction($this->touchScreen, $x, $y) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function scroll($x, $y) { $this->action->addAction( new WebDriverScrollAction($this->touchScreen, $x, $y) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function scrollFromElement(WebDriverElement $element, $x, $y) { $this->action->addAction( new WebDriverScrollFromElementAction($this->touchScreen, $element, $x, $y) ); return $this; } /** * @return WebDriverTouchActions */ public function doubleTap(WebDriverElement $element) { $this->action->addAction( new WebDriverDoubleTapAction($this->touchScreen, $element) ); return $this; } /** * @return WebDriverTouchActions */ public function longPress(WebDriverElement $element) { $this->action->addAction( new WebDriverLongPressAction($this->touchScreen, $element) ); return $this; } /** * @param int $x * @param int $y * @return WebDriverTouchActions */ public function flick($x, $y) { $this->action->addAction( new WebDriverFlickAction($this->touchScreen, $x, $y) ); return $this; } /** * @param int $x * @param int $y * @param int $speed * @return WebDriverTouchActions */ public function flickFromElement(WebDriverElement $element, $x, $y, $speed) { $this->action->addAction( new WebDriverFlickFromElementAction( $this->touchScreen, $element, $x, $y, $speed ) ); return $this; } } webdriver/lib/Interactions/WebDriverCompositeAction.php 0000644 00000001704 15021223212 0017360 0 ustar 00 <?php namespace Facebook\WebDriver\Interactions; use Facebook\WebDriver\WebDriverAction; /** * An action for aggregating actions and triggering all of them afterwards. */ class WebDriverCompositeAction implements WebDriverAction { /** * @var WebDriverAction[] */ private $actions = []; /** * Add an WebDriverAction to the sequence. * * @return WebDriverCompositeAction The current instance. */ public function addAction(WebDriverAction $action) { $this->actions[] = $action; return $this; } /** * Get the number of actions in the sequence. * * @return int The number of actions. */ public function getNumberOfActions() { return count($this->actions); } /** * Perform the sequence of actions. */ public function perform() { foreach ($this->actions as $action) { $action->perform(); } } } webdriver/lib/WebDriverNavigationInterface.php 0000644 00000002012 15021223212 0015527 0 ustar 00 <?php namespace Facebook\WebDriver; /** * An abstraction allowing the driver to access the browser's history and to * navigate to a given URL. */ interface WebDriverNavigationInterface { /** * Move back a single entry in the browser's history, if possible. * This is equivalent to pressing the back button in the browser or invoking window.history.back. * * @return self */ public function back(); /** * Move forward a single entry in the browser's history, if possible. * This is equivalent to pressing the forward button in the browser or invoking window.history.back. * * @return self */ public function forward(); /** * Refresh the current page * This is equivalent to pressing the refresh button in the browser. * * @return self */ public function refresh(); /** * Navigate to the given URL * * @param string $url * @return self * @see WebDriver::get() */ public function to($url); } webdriver/lib/WebDriverKeys.php 0000644 00000011053 15021223212 0012527 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Representations of pressable keys that aren't text. * These are stored in the Unicode PUA (Private Use Area) code points. * @see https://w3c.github.io/webdriver/#keyboard-actions */ class WebDriverKeys { public const NULL = "\xEE\x80\x80"; public const CANCEL = "\xEE\x80\x81"; public const HELP = "\xEE\x80\x82"; public const BACKSPACE = "\xEE\x80\x83"; public const TAB = "\xEE\x80\x84"; public const CLEAR = "\xEE\x80\x85"; public const RETURN_KEY = "\xEE\x80\x86"; public const ENTER = "\xEE\x80\x87"; public const SHIFT = "\xEE\x80\x88"; public const CONTROL = "\xEE\x80\x89"; public const ALT = "\xEE\x80\x8A"; public const PAUSE = "\xEE\x80\x8B"; public const ESCAPE = "\xEE\x80\x8C"; public const SPACE = "\xEE\x80\x8D"; public const PAGE_UP = "\xEE\x80\x8E"; public const PAGE_DOWN = "\xEE\x80\x8F"; public const END = "\xEE\x80\x90"; public const HOME = "\xEE\x80\x91"; public const ARROW_LEFT = "\xEE\x80\x92"; public const ARROW_UP = "\xEE\x80\x93"; public const ARROW_RIGHT = "\xEE\x80\x94"; public const ARROW_DOWN = "\xEE\x80\x95"; public const INSERT = "\xEE\x80\x96"; public const DELETE = "\xEE\x80\x97"; public const SEMICOLON = "\xEE\x80\x98"; public const EQUALS = "\xEE\x80\x99"; public const NUMPAD0 = "\xEE\x80\x9A"; public const NUMPAD1 = "\xEE\x80\x9B"; public const NUMPAD2 = "\xEE\x80\x9C"; public const NUMPAD3 = "\xEE\x80\x9D"; public const NUMPAD4 = "\xEE\x80\x9E"; public const NUMPAD5 = "\xEE\x80\x9F"; public const NUMPAD6 = "\xEE\x80\xA0"; public const NUMPAD7 = "\xEE\x80\xA1"; public const NUMPAD8 = "\xEE\x80\xA2"; public const NUMPAD9 = "\xEE\x80\xA3"; public const MULTIPLY = "\xEE\x80\xA4"; public const ADD = "\xEE\x80\xA5"; public const SEPARATOR = "\xEE\x80\xA6"; public const SUBTRACT = "\xEE\x80\xA7"; public const DECIMAL = "\xEE\x80\xA8"; public const DIVIDE = "\xEE\x80\xA9"; public const F1 = "\xEE\x80\xB1"; public const F2 = "\xEE\x80\xB2"; public const F3 = "\xEE\x80\xB3"; public const F4 = "\xEE\x80\xB4"; public const F5 = "\xEE\x80\xB5"; public const F6 = "\xEE\x80\xB6"; public const F7 = "\xEE\x80\xB7"; public const F8 = "\xEE\x80\xB8"; public const F9 = "\xEE\x80\xB9"; public const F10 = "\xEE\x80\xBA"; public const F11 = "\xEE\x80\xBB"; public const F12 = "\xEE\x80\xBC"; public const META = "\xEE\x80\xBD"; public const ZENKAKU_HANKAKU = "\xEE\x80\xC0"; public const RIGHT_SHIFT = "\xEE\x81\x90"; public const RIGHT_CONTROL = "\xEE\x81\x91"; public const RIGHT_ALT = "\xEE\x81\x92"; public const RIGHT_META = "\xEE\x81\x93"; public const NUMPAD_PAGE_UP = "\xEE\x81\x94"; public const NUMPAD_PAGE_DOWN = "\xEE\x81\x95"; public const NUMPAD_END = "\xEE\x81\x96"; public const NUMPAD_HOME = "\xEE\x81\x97"; public const NUMPAD_ARROW_LEFT = "\xEE\x81\x98"; public const NUMPAD_ARROW_UP = "\xEE\x81\x99"; public const NUMPAD_ARROW_RIGHT = "\xEE\x81\x9A"; public const NUMPAD_ARROW_DOWN = "\xEE\x81\x9B"; public const NUMPAD_ARROW_INSERT = "\xEE\x81\x9C"; public const NUMPAD_ARROW_DELETE = "\xEE\x81\x9D"; // Aliases public const LEFT_SHIFT = self::SHIFT; public const LEFT_CONTROL = self::CONTROL; public const LEFT_ALT = self::ALT; public const LEFT = self::ARROW_LEFT; public const UP = self::ARROW_UP; public const RIGHT = self::ARROW_RIGHT; public const DOWN = self::ARROW_DOWN; public const COMMAND = self::META; /** * Encode input of `sendKeys()` to appropriate format according to protocol. * * @param string|array|int|float $keys * @param bool $isW3cCompliant * @return array|string */ public static function encode($keys, $isW3cCompliant = false) { if (is_numeric($keys)) { $keys = (string) $keys; } if (is_string($keys)) { $keys = [$keys]; } if (!is_array($keys)) { if (!$isW3cCompliant) { return []; } return ''; } $encoded = []; foreach ($keys as $key) { if (is_array($key)) { // handle key modifiers $key = implode('', $key) . self::NULL; // the NULL clears the input state (eg. previous modifiers) } $encoded[] = (string) $key; } if (!$isW3cCompliant) { return $encoded; } return implode('', $encoded); } } webdriver/lib/WebDriverWindow.php 0000644 00000011603 15021223212 0013064 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\IndexOutOfBoundsException; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; /** * An abstraction allowing the driver to manipulate the browser's window */ class WebDriverWindow { /** * @var ExecuteMethod */ protected $executor; /** * @var bool */ protected $isW3cCompliant; public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } /** * Get the position of the current window, relative to the upper left corner * of the screen. * * @return WebDriverPoint The current window position. */ public function getPosition() { $position = $this->executor->execute( DriverCommand::GET_WINDOW_POSITION, [':windowHandle' => 'current'] ); return new WebDriverPoint( $position['x'], $position['y'] ); } /** * Get the size of the current window. This will return the outer window * dimension, not just the view port. * * @return WebDriverDimension The current window size. */ public function getSize() { $size = $this->executor->execute( DriverCommand::GET_WINDOW_SIZE, [':windowHandle' => 'current'] ); return new WebDriverDimension( $size['width'], $size['height'] ); } /** * Minimizes the current window if it is not already minimized. * * @return WebDriverWindow The instance. */ public function minimize() { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('Minimize window is only supported in W3C mode'); } $this->executor->execute(DriverCommand::MINIMIZE_WINDOW, []); return $this; } /** * Maximizes the current window if it is not already maximized * * @return WebDriverWindow The instance. */ public function maximize() { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::MAXIMIZE_WINDOW, []); } else { $this->executor->execute( DriverCommand::MAXIMIZE_WINDOW, [':windowHandle' => 'current'] ); } return $this; } /** * Makes the current window full screen. * * @return WebDriverWindow The instance. */ public function fullscreen() { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('The Fullscreen window command is only supported in W3C mode'); } $this->executor->execute(DriverCommand::FULLSCREEN_WINDOW, []); return $this; } /** * Set the size of the current window. This will change the outer window * dimension, not just the view port. * * @return WebDriverWindow The instance. */ public function setSize(WebDriverDimension $size) { $params = [ 'width' => $size->getWidth(), 'height' => $size->getHeight(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_SIZE, $params); return $this; } /** * Set the position of the current window. This is relative to the upper left * corner of the screen. * * @return WebDriverWindow The instance. */ public function setPosition(WebDriverPoint $position) { $params = [ 'x' => $position->getX(), 'y' => $position->getY(), ':windowHandle' => 'current', ]; $this->executor->execute(DriverCommand::SET_WINDOW_POSITION, $params); return $this; } /** * Get the current browser orientation. * * @return string Either LANDSCAPE|PORTRAIT */ public function getScreenOrientation() { return $this->executor->execute(DriverCommand::GET_SCREEN_ORIENTATION); } /** * Set the browser orientation. The orientation should either * LANDSCAPE|PORTRAIT * * @param string $orientation * @throws IndexOutOfBoundsException * @return WebDriverWindow The instance. */ public function setScreenOrientation($orientation) { $orientation = mb_strtoupper($orientation); if (!in_array($orientation, ['PORTRAIT', 'LANDSCAPE'], true)) { throw LogicException::forError('Orientation must be either PORTRAIT, or LANDSCAPE'); } $this->executor->execute( DriverCommand::SET_SCREEN_ORIENTATION, ['orientation' => $orientation] ); return $this; } } webdriver/lib/WebDriverSearchContext.php 0000644 00000001460 15021223212 0014367 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\NoSuchElementException; /** * The interface for WebDriver and WebDriverElement which is able to search for WebDriverElement inside. */ interface WebDriverSearchContext { /** * Find the first WebDriverElement within this element using the given mechanism. * * @throws NoSuchElementException If no element is found * @return WebDriverElement * @see WebDriverBy */ public function findElement(WebDriverBy $locator); /** * Find all WebDriverElements within this element using the given mechanism. * * @return WebDriverElement[] A list of all WebDriverElements, or an empty array if nothing matches * @see WebDriverBy */ public function findElements(WebDriverBy $locator); } webdriver/lib/WebDriverTargetLocator.php 0000644 00000004070 15021223212 0014367 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Used to locate a given frame or window. */ interface WebDriverTargetLocator { /** @var string */ public const WINDOW_TYPE_WINDOW = 'window'; /** @var string */ public const WINDOW_TYPE_TAB = 'tab'; /** * Set the current browsing context to the current top-level browsing context. * This is the same as calling `RemoteTargetLocator::frame(null);` * * @return WebDriver The driver focused on the top window or the first frame. */ public function defaultContent(); /** * Switch to the iframe by its id or name. * * @param WebDriverElement|null|int|string $frame The WebDriverElement, the id or the name of the frame. * When null, switch to the current top-level browsing context When int, switch to the WindowProxy identified * by the value. When an Element, switch to that Element. * * @throws \InvalidArgumentException * @return WebDriver The driver focused on the given frame. */ public function frame($frame); // TODO: Add in next major release (BC) ///** // * Switch to the parent iframe. // * // * @return WebDriver This driver focused on the parent frame // */ //public function parent(); /** * Switch the focus to another window by its handle. * * @param string $handle The handle of the window to be focused on. * @return WebDriver The driver focused on the given window. * @see WebDriver::getWindowHandles */ public function window($handle); // TODO: Add in next major release (BC) //public function newWindow($windowType = self::WINDOW_TYPE_TAB); /** * Switch to the currently active modal dialog for this particular driver instance. * * @return WebDriverAlert */ public function alert(); /** * Switches to the element that currently has focus within the document currently "switched to", * or the body element if this cannot be detected. * * @return WebDriverElement */ public function activeElement(); } webdriver/lib/WebDriverDimension.php 0000644 00000002174 15021223212 0013545 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Represent a dimension. */ class WebDriverDimension { /** * @var int|float */ private $height; /** * @var int|float */ private $width; /** * @param int|float $width * @param int|float $height */ public function __construct($width, $height) { $this->width = $width; $this->height = $height; } /** * Get the height. * * @return int The height. */ public function getHeight() { return (int) $this->height; } /** * Get the width. * * @return int The width. */ public function getWidth() { return (int) $this->width; } /** * Check whether the given dimension is the same as the instance. * * @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(self $dimension) { return $this->height === $dimension->getHeight() && $this->width === $dimension->getWidth(); } } webdriver/lib/WebDriverCapabilities.php 0000644 00000002056 15021223212 0014210 0 ustar 00 <?php namespace Facebook\WebDriver; interface WebDriverCapabilities { /** * @return string The name of the browser. */ public function getBrowserName(); /** * @param string $name * @return mixed The value of a capability. */ public function getCapability($name); /** * @return string The name of the platform. */ public function getPlatform(); /** * @return string The version of the browser. */ public function getVersion(); /** * @param string $capability_name * @return bool Whether the value is not null and not false. */ 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(); // TODO: Add in next major release (BC) ///** // * @return array // */ //public function toArray(); } webdriver/lib/WebDriverWait.php 0000644 00000003663 15021223212 0012530 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\TimeoutException; /** * A utility class, designed to help the user to wait until a condition turns true. * * @see WebDriverExpectedCondition. */ class WebDriverWait { /** * @var WebDriver */ protected $driver; /** * @var int */ protected $timeout; /** * @var int */ protected $interval; public function __construct(WebDriver $driver, $timeout_in_second = null, $interval_in_millisecond = null) { $this->driver = $driver; $this->timeout = $timeout_in_second ?? 30; $this->interval = $interval_in_millisecond ?: 250; } /** * Calls the function provided with the driver as an argument until the return value is not falsey. * * @param callable|WebDriverExpectedCondition $func_or_ec * @param string $message * * @throws \Exception * @throws NoSuchElementException * @throws TimeoutException * @return mixed The return value of $func_or_ec */ public function until($func_or_ec, $message = '') { $end = microtime(true) + $this->timeout; $last_exception = null; while ($end > microtime(true)) { try { if ($func_or_ec instanceof WebDriverExpectedCondition) { $ret_val = call_user_func($func_or_ec->getApply(), $this->driver); } else { $ret_val = call_user_func($func_or_ec, $this->driver); } if ($ret_val) { return $ret_val; } } catch (NoSuchElementException $e) { $last_exception = $e; } usleep($this->interval * 1000); } if ($last_exception) { throw $last_exception; } throw new TimeoutException($message); } } webdriver/lib/Cookie.php 0000644 00000014407 15021223212 0011221 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\Internal\LogicException; /** * Set values of an cookie. * * Implements ArrayAccess for backwards compatibility. * * @see https://w3c.github.io/webdriver/#cookies */ class Cookie implements \ArrayAccess { /** @var array */ protected $cookie = []; /** * @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->validateCookieName($name); $this->validateCookieValue($value); $this->cookie['name'] = $name; $this->cookie['value'] = $value; } /** * @param array $cookieArray The cookie fields; must contain name and value. * @return Cookie */ public static function createFromArray(array $cookieArray) { if (!isset($cookieArray['name'])) { throw LogicException::forError('Cookie name should be set'); } if (!isset($cookieArray['value'])) { throw LogicException::forError('Cookie value should be set'); } $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']); } if (isset($cookieArray['sameSite'])) { $cookie->setSameSite($cookieArray['sameSite']); } return $cookie; } /** * @return string */ public function getName() { return $this->offsetGet('name'); } /** * @return string */ public function getValue() { return $this->offsetGet('value'); } /** * The path the cookie is visible to. Defaults to "/" if omitted. * * @param string $path */ public function setPath($path) { $this->offsetSet('path', $path); } /** * @return string|null */ public function getPath() { return $this->offsetGet('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) { if (mb_strpos($domain, ':') !== false) { throw LogicException::forError(sprintf('Cookie domain "%s" should not contain a port', $domain)); } $this->offsetSet('domain', $domain); } /** * @return string|null */ public function getDomain() { return $this->offsetGet('domain'); } /** * The cookie's expiration date, specified in seconds since Unix Epoch. * * @param int $expiry */ public function setExpiry($expiry) { $this->offsetSet('expiry', (int) $expiry); } /** * @return int|null */ public function getExpiry() { return $this->offsetGet('expiry'); } /** * Whether this cookie requires a secure connection (https). Defaults to false if omitted. * * @param bool $secure */ public function setSecure($secure) { $this->offsetSet('secure', $secure); } /** * @return bool|null */ public function isSecure() { return $this->offsetGet('secure'); } /** * Whether the cookie is an HTTP only cookie. Defaults to false if omitted. * * @param bool $httpOnly */ public function setHttpOnly($httpOnly) { $this->offsetSet('httpOnly', $httpOnly); } /** * @return bool|null */ public function isHttpOnly() { return $this->offsetGet('httpOnly'); } /** * The cookie's same-site value. * * @param string $sameSite */ public function setSameSite($sameSite) { $this->offsetSet('sameSite', $sameSite); } /** * @return string|null */ public function getSameSite() { return $this->offsetGet('sameSite'); } /** * @return array */ public function toArray() { $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; } /** * @param mixed $offset * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookie[$offset]); } /** * @param mixed $offset * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->cookie[$offset] : null; } /** * @param mixed $offset * @param mixed $value * @return void */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($value === null) { unset($this->cookie[$offset]); } else { $this->cookie[$offset] = $value; } } /** * @param mixed $offset * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->cookie[$offset]); } /** * @param string $name */ protected function validateCookieName($name) { if ($name === null || $name === '') { throw LogicException::forError('Cookie name should be non-empty'); } if (mb_strpos($name, ';') !== false) { throw LogicException::forError('Cookie name should not contain a ";"'); } } /** * @param string $value */ protected function validateCookieValue($value) { if ($value === null) { throw LogicException::forError('Cookie value is required when setting a cookie'); } } } webdriver/lib/WebDriverTimeouts.php 0000644 00000004777 15021223212 0013444 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; /** * Managing timeout behavior for WebDriver instances. */ class WebDriverTimeouts { /** * @var ExecuteMethod */ protected $executor; /** * @var bool */ protected $isW3cCompliant; public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } /** * 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. */ public function implicitlyWait($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['implicit' => $seconds * 1000] ); return $this; } $this->executor->execute( DriverCommand::IMPLICITLY_WAIT, ['ms' => $seconds * 1000] ); return $this; } /** * 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. */ public function setScriptTimeout($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['script' => $seconds * 1000] ); return $this; } $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['ms' => $seconds * 1000] ); return $this; } /** * 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. */ public function pageLoadTimeout($seconds) { if ($this->isW3cCompliant) { $this->executor->execute( DriverCommand::SET_SCRIPT_TIMEOUT, ['pageLoad' => $seconds * 1000] ); return $this; } $this->executor->execute(DriverCommand::SET_TIMEOUT, [ 'type' => 'page load', 'ms' => $seconds * 1000, ]); return $this; } } webdriver/lib/Internal/WebDriverLocatable.php 0000644 00000000450 15021223212 0015255 0 ustar 00 <?php namespace Facebook\WebDriver\Internal; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; /** * Interface representing basic mouse operations. */ interface WebDriverLocatable { /** * @return WebDriverCoordinates */ public function getCoordinates(); } webdriver/lib/JavaScriptExecutor.php 0000644 00000002323 15021223212 0013567 0 ustar 00 <?php namespace Facebook\WebDriver; /** * WebDriver interface implemented by drivers that support JavaScript. */ interface JavaScriptExecutor { /** * 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. * @return mixed The return value of the script. */ 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. * * The driver will pass a callback as the last argument to the snippet, and * block until the callback is invoked. * * @see WebDriverExecuteAsyncScriptTestCase * * @param string $script The script to inject. * @param array $arguments The arguments of the script. * @return mixed The value passed by the script to the callback. */ public function executeAsyncScript($script, array $arguments = []); } webdriver/lib/Net/URLChecker.php 0000644 00000003754 15021223212 0012470 0 ustar 00 <?php namespace Facebook\WebDriver\Net; use Exception; use Facebook\WebDriver\Exception\TimeoutException; class URLChecker { public const POLL_INTERVAL_MS = 500; public const CONNECT_TIMEOUT_MS = 500; public function waitUntilAvailable($timeout_in_ms, $url) { $end = microtime(true) + $timeout_in_ms / 1000; while ($end > microtime(true)) { if ($this->getHTTPResponseCode($url) === 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeoutException(sprintf( 'Timed out waiting for %s to become available after %d ms.', $url, $timeout_in_ms )); } public function waitUntilUnavailable($timeout_in_ms, $url) { $end = microtime(true) + $timeout_in_ms / 1000; while ($end > microtime(true)) { if ($this->getHTTPResponseCode($url) !== 200) { return $this; } usleep(self::POLL_INTERVAL_MS); } throw new TimeoutException(sprintf( 'Timed out waiting for %s to become unavailable after %d ms.', $url, $timeout_in_ms )); } private function getHTTPResponseCode($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $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')) { define('CURLOPT_CONNECTTIMEOUT_MS', 156); // default value for CURLOPT_CONNECTTIMEOUT_MS } curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, self::CONNECT_TIMEOUT_MS); $code = null; try { curl_exec($ch); $info = curl_getinfo($ch); $code = $info['http_code']; } catch (Exception $e) { } curl_close($ch); return $code; } } webdriver/lib/WebDriverOptions.php 0000644 00000011466 15021223212 0013257 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\NoSuchCookieException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; /** * Managing stuff you would do in a browser. */ class WebDriverOptions { /** * @var ExecuteMethod */ protected $executor; /** * @var bool */ protected $isW3cCompliant; public function __construct(ExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } /** * Add a specific cookie. * * @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. */ public function addCookie($cookie) { if (is_array($cookie)) { // @todo @deprecated remove in 2.0 $cookie = Cookie::createFromArray($cookie); } if (!$cookie instanceof Cookie) { throw LogicException::forError('Cookie must be set from instance of Cookie class or from array.'); } $this->executor->execute( DriverCommand::ADD_COOKIE, ['cookie' => $cookie->toArray()] ); return $this; } /** * Delete all the cookies that are currently visible. * * @return WebDriverOptions The current instance. */ public function deleteAllCookies() { $this->executor->execute(DriverCommand::DELETE_ALL_COOKIES); return $this; } /** * Delete the cookie with the given name. * * @param string $name * @return WebDriverOptions The current instance. */ public function deleteCookieNamed($name) { $this->executor->execute( DriverCommand::DELETE_COOKIE, [':name' => $name] ); return $this; } /** * Get the cookie with a given name. * * @param string $name * @throws NoSuchCookieException In W3C compliant mode if no cookie with the given name is present * @return Cookie|null The cookie, or null in JsonWire mode if no cookie with the given name is present */ public function getCookieNamed($name) { if ($this->isW3cCompliant) { $cookieArray = $this->executor->execute( DriverCommand::GET_NAMED_COOKIE, [':name' => $name] ); if (!is_array($cookieArray)) { // Microsoft Edge returns null even in W3C mode => emulate proper behavior throw new NoSuchCookieException('no such cookie'); } return Cookie::createFromArray($cookieArray); } $cookies = $this->getCookies(); foreach ($cookies as $cookie) { if ($cookie['name'] === $name) { return $cookie; } } return null; } /** * Get all the cookies for the current domain. * * @return Cookie[] The array of cookies presented. */ public function getCookies() { $cookieArrays = $this->executor->execute(DriverCommand::GET_ALL_COOKIES); if (!is_array($cookieArrays)) { // Microsoft Edge returns null if there are no cookies... return []; } $cookies = []; foreach ($cookieArrays as $cookieArray) { $cookies[] = Cookie::createFromArray($cookieArray); } return $cookies; } /** * Return the interface for managing driver timeouts. * * @return WebDriverTimeouts */ public function timeouts() { return new WebDriverTimeouts($this->executor, $this->isW3cCompliant); } /** * An abstraction allowing the driver to manipulate the browser's window * * @return WebDriverWindow * @see WebDriverWindow */ public function window() { return new WebDriverWindow($this->executor, $this->isW3cCompliant); } /** * Get the log for a given log type. Log buffer is reset after each request. * * @param string $log_type The log type. * @return array The list of log entries. * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#log-type */ public function getLog($log_type) { return $this->executor->execute( DriverCommand::GET_LOG, ['type' => $log_type] ); } /** * Get available log types. * * @return array The list of available log types. * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#log-type */ public function getAvailableLogTypes() { return $this->executor->execute(DriverCommand::GET_AVAILABLE_LOG_TYPES); } } webdriver/lib/WebDriverRadios.php 0000644 00000002545 15021223212 0013043 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\InvalidElementStateException; use Facebook\WebDriver\Exception\UnsupportedOperationException; /** * Provides helper methods for radio buttons. */ class WebDriverRadios extends AbstractWebDriverCheckboxOrRadio { public function __construct(WebDriverElement $element) { parent::__construct($element); $this->type = $element->getAttribute('type'); if ($this->type !== 'radio') { throw new InvalidElementStateException('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'); } } webdriver/lib/Exception/JavascriptErrorException.php 0000644 00000000274 15021223212 0016742 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An error occurred while executing JavaScript supplied by the user. */ class JavascriptErrorException extends WebDriverException { } webdriver/lib/Exception/InvalidCookieDomainException.php 0000644 00000000331 15021223212 0017464 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An illegal attempt was made to set a cookie under a different domain than the current page. */ class InvalidCookieDomainException extends WebDriverException { } webdriver/lib/Exception/ScriptTimeoutException.php 0000644 00000000255 15021223212 0016434 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A script did not complete before its timeout expired. */ class ScriptTimeoutException extends WebDriverException { } webdriver/lib/Exception/NoSuchDriverException.php 0000644 00000000330 15021223212 0016166 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoSuchDriverException extends WebDriverException { } webdriver/lib/Exception/IndexOutOfBoundsException.php 0000644 00000000334 15021223212 0017016 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class IndexOutOfBoundsException extends WebDriverException { } webdriver/lib/Exception/MoveTargetOutOfBoundsException.php 0000644 00000000355 15021223212 0020027 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport. */ class MoveTargetOutOfBoundsException extends WebDriverException { } webdriver/lib/Exception/InsecureCertificateException.php 0000644 00000000405 15021223212 0017536 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired * or invalid TLS certificate. */ class InsecureCertificateException extends WebDriverException { } webdriver/lib/Exception/UnknownMethodException.php 0000644 00000000313 15021223212 0016414 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * The requested command matched a known URL but did not match an method for that URL. */ class UnknownMethodException extends WebDriverException { } webdriver/lib/Exception/NoSuchAlertException.php 0000644 00000000275 15021223212 0016012 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An attempt was made to operate on a modal dialog when one was not open. */ class NoSuchAlertException extends WebDriverException { } webdriver/lib/Exception/XPathLookupException.php 0000644 00000000327 15021223212 0016037 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class XPathLookupException extends WebDriverException { } webdriver/lib/Exception/ElementNotVisibleException.php 0000644 00000000335 15021223212 0017210 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class ElementNotVisibleException extends WebDriverException { } webdriver/lib/Exception/UnknownServerException.php 0000644 00000000275 15021223212 0016451 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Use Facebook\WebDriver\Exception\UnknownErrorException */ class UnknownServerException extends UnknownErrorException { } webdriver/lib/Exception/IMENotAvailableException.php 0000644 00000000333 15021223212 0016512 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class IMENotAvailableException extends WebDriverException { } webdriver/lib/Exception/NoStringException.php 0000644 00000000324 15021223212 0015361 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoStringException extends WebDriverException { } webdriver/lib/Exception/ElementNotInteractableException.php 0000644 00000000337 15021223212 0020212 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command could not be completed because the element is not pointer- or keyboard interactable. */ class ElementNotInteractableException extends WebDriverException { } webdriver/lib/Exception/UnableToSetCookieException.php 0000644 00000000267 15021223212 0017143 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command to set a cookie’s value could not be satisfied. */ class UnableToSetCookieException extends WebDriverException { } webdriver/lib/Exception/InvalidArgumentException.php 0000644 00000000274 15021223212 0016713 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * The arguments passed to a command are either invalid or malformed. */ class InvalidArgumentException extends WebDriverException { } webdriver/lib/Exception/ElementClickInterceptedException.php 0000644 00000000423 15021223212 0020344 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * The Element Click command could not be completed because the element receiving the events is obscuring the element * that was requested clicked. */ class ElementClickInterceptedException extends WebDriverException { } webdriver/lib/Exception/InvalidSessionIdException.php 0000644 00000000410 15021223212 0017021 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist * or that it’s not active. */ class InvalidSessionIdException extends WebDriverException { } webdriver/lib/Exception/NoStringLengthException.php 0000644 00000000332 15021223212 0016522 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoStringLengthException extends WebDriverException { } webdriver/lib/Exception/Internal/RuntimeException.php 0000644 00000001401 15021223212 0017012 0 ustar 00 <?php declare(strict_types=1); namespace Facebook\WebDriver\Exception\Internal; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; use Symfony\Component\Process\Process; /** * Exception thrown if an error which can only be found on runtime occurs. */ class RuntimeException extends \RuntimeException implements PhpWebDriverExceptionInterface { public static function forError(string $message): self { return new self($message); } public static function forDriverError(Process $process): self { return new self( sprintf( 'Error starting driver executable "%s": %s', $process->getCommandLine(), $process->getErrorOutput() ) ); } } webdriver/lib/Exception/Internal/IOException.php 0000644 00000000733 15021223212 0015705 0 ustar 00 <?php declare(strict_types=1); namespace Facebook\WebDriver\Exception\Internal; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; /** * Exception class thrown when a filesystem related operation failure happens. */ class IOException extends \LogicException implements PhpWebDriverExceptionInterface { public static function forFileError(string $message, string $path): self { return new self(sprintf($message . ' ("%s")', $path)); } } webdriver/lib/Exception/Internal/WebDriverCurlException.php 0000644 00000001173 15021223212 0020114 0 ustar 00 <?php namespace Facebook\WebDriver\Exception\Internal; /** * @deprecated To be replaced with UnexpectedResponseException in 2.0 */ class WebDriverCurlException extends UnexpectedResponseException { public static function forCurlError(string $httpMethod, string $url, string $curlError, ?array $params): self { $message = sprintf('Curl error thrown for http %s to %s', $httpMethod, $url); if (!empty($params)) { $message .= sprintf(' with params: %s', json_encode($params, JSON_UNESCAPED_SLASHES)); } $message .= "\n\n" . $curlError; return new self($message); } } webdriver/lib/Exception/Internal/DriverServerDiedException.php 0000644 00000000731 15021223212 0020604 0 ustar 00 <?php declare(strict_types=1); namespace Facebook\WebDriver\Exception\Internal; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; /** * The driver server process is unexpectedly no longer available. */ class DriverServerDiedException extends \RuntimeException implements PhpWebDriverExceptionInterface { public function __construct(\Exception $previous = null) { parent::__construct('The driver server has died.', 0, $previous); } } webdriver/lib/Exception/Internal/UnexpectedResponseException.php 0000644 00000003163 15021223212 0021221 0 ustar 00 <?php declare(strict_types=1); namespace Facebook\WebDriver\Exception\Internal; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; /** * Exception thrown on invalid or unexpected server response. */ class UnexpectedResponseException extends \RuntimeException implements PhpWebDriverExceptionInterface { public static function forError(string $message): self { return new self($message); } public static function forElementNotArray($response): self { return new self( sprintf( "Unexpected server response for getting an element. Expected array, but the response was: '%s'\n", print_r($response, true) ) ); } public static function forJsonDecodingError(int $jsonLastError, string $rawResults): self { return new self( sprintf( "JSON decoding of remote response failed.\n" . "Error code: %d\n" . "The response: '%s'\n", $jsonLastError, $rawResults ) ); } public static function forCapabilitiesRetrievalError(\Exception $previousException): self { return new self( sprintf( 'Existing Capabilities were not provided, and they also cannot be read from Selenium Grid' . ' (error: "%s"). You are probably not using Selenium Grid, so to reuse the previous session,' . ' Capabilities must be explicitly provided to createBySessionID() method.', $previousException->getMessage() ) ); } } webdriver/lib/Exception/Internal/LogicException.php 0000644 00000001567 15021223212 0016441 0 ustar 00 <?php declare(strict_types=1); namespace Facebook\WebDriver\Exception\Internal; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; /** * Exception thrown when error in program logic occurs. This includes invalid domain data and unexpected data states. */ class LogicException extends \LogicException implements PhpWebDriverExceptionInterface { public static function forError(string $message): self { return new self($message); } public static function forInvalidHttpMethod(string $url, string $httpMethod, array $params): self { return new self( sprintf( 'The http method called for "%s" is "%s", but it has to be POST' . ' if you want to pass the JSON params %s', $url, $httpMethod, json_encode($params) ) ); } } webdriver/lib/Exception/PhpWebDriverExceptionInterface.php 0000644 00000000335 15021223212 0020002 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * Common interface to identify all exceptions thrown in php-webdriver (both those of WebDriver protocol and internal). */ interface PhpWebDriverExceptionInterface { } webdriver/lib/Exception/NoStringWrapperException.php 0000644 00000000333 15021223212 0016722 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoStringWrapperException extends WebDriverException { } webdriver/lib/Exception/InvalidSelectorException.php 0000644 00000000233 15021223212 0016704 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * Argument was an invalid selector. */ class InvalidSelectorException extends WebDriverException { } webdriver/lib/Exception/UnexpectedJavascriptException.php 0000644 00000000312 15021223212 0017746 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Use Facebook\WebDriver\Exception\JavascriptErrorException */ class UnexpectedJavascriptException extends JavascriptErrorException { } webdriver/lib/Exception/NoSuchShadowRootException.php 0000644 00000000243 15021223212 0017027 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * The element does not have a shadow root. */ class NoSuchShadowRootException extends WebDriverException { } webdriver/lib/Exception/WebDriverException.php 0000644 00000023105 15021223212 0015511 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; use Exception; /** * Ancestor for all exceptions defined in W3C WebDriver standard. * (And also for deprecated JsonWire protocol exceptions.) * * @see https://w3c.github.io/webdriver/#errors */ class WebDriverException extends Exception implements PhpWebDriverExceptionInterface { private $results; /** * @param string $message * @param mixed $results */ public function __construct($message, $results = null) { parent::__construct($message); $this->results = $results; } /** * @return mixed */ public function getResults() { return $this->results; } /** * Throw WebDriverExceptions based on WebDriver status code. * * @param int|string $status_code * @param string $message * @param mixed $results * * @throws ElementClickInterceptedException * @throws ElementNotInteractableException * @throws ElementNotSelectableException * @throws ElementNotVisibleException * @throws ExpectedException * @throws IMEEngineActivationFailedException * @throws IMENotAvailableException * @throws IndexOutOfBoundsException * @throws InsecureCertificateException * @throws InvalidArgumentException * @throws InvalidCookieDomainException * @throws InvalidCoordinatesException * @throws InvalidElementStateException * @throws InvalidSelectorException * @throws InvalidSessionIdException * @throws JavascriptErrorException * @throws MoveTargetOutOfBoundsException * @throws NoAlertOpenException * @throws NoCollectionException * @throws NoScriptResultException * @throws NoStringException * @throws NoStringLengthException * @throws NoStringWrapperException * @throws NoSuchAlertException * @throws NoSuchCollectionException * @throws NoSuchCookieException * @throws NoSuchDocumentException * @throws NoSuchDriverException * @throws NoSuchElementException * @throws NoSuchFrameException * @throws NoSuchWindowException * @throws NullPointerException * @throws ScriptTimeoutException * @throws SessionNotCreatedException * @throws StaleElementReferenceException * @throws TimeoutException * @throws UnableToCaptureScreenException * @throws UnableToSetCookieException * @throws UnexpectedAlertOpenException * @throws UnexpectedJavascriptException * @throws UnknownCommandException * @throws UnknownErrorException * @throws UnknownMethodException * @throws UnknownServerException * @throws UnrecognizedExceptionException * @throws UnsupportedOperationException * @throws XPathLookupException */ public static function throwException($status_code, $message, $results) { if (is_string($status_code)) { // @see https://w3c.github.io/webdriver/#errors switch ($status_code) { case 'element click intercepted': throw new ElementClickInterceptedException($message, $results); case 'element not interactable': throw new ElementNotInteractableException($message, $results); case 'insecure certificate': throw new InsecureCertificateException($message, $results); case 'invalid argument': throw new InvalidArgumentException($message, $results); case 'invalid cookie domain': throw new InvalidCookieDomainException($message, $results); case 'invalid element state': throw new InvalidElementStateException($message, $results); case 'invalid selector': throw new InvalidSelectorException($message, $results); case 'invalid session id': throw new InvalidSessionIdException($message, $results); case 'javascript error': throw new JavascriptErrorException($message, $results); case 'move target out of bounds': throw new MoveTargetOutOfBoundsException($message, $results); case 'no such alert': throw new NoSuchAlertException($message, $results); case 'no such cookie': throw new NoSuchCookieException($message, $results); case 'no such element': throw new NoSuchElementException($message, $results); case 'no such frame': throw new NoSuchFrameException($message, $results); case 'no such window': throw new NoSuchWindowException($message, $results); case 'no such shadow root': throw new NoSuchShadowRootException($message, $results); case 'script timeout': throw new ScriptTimeoutException($message, $results); case 'session not created': throw new SessionNotCreatedException($message, $results); case 'stale element reference': throw new StaleElementReferenceException($message, $results); case 'detached shadow root': throw new DetachedShadowRootException($message, $results); case 'timeout': throw new TimeoutException($message, $results); case 'unable to set cookie': throw new UnableToSetCookieException($message, $results); case 'unable to capture screen': throw new UnableToCaptureScreenException($message, $results); case 'unexpected alert open': throw new UnexpectedAlertOpenException($message, $results); case 'unknown command': throw new UnknownCommandException($message, $results); case 'unknown error': throw new UnknownErrorException($message, $results); case 'unknown method': throw new UnknownMethodException($message, $results); case 'unsupported operation': throw new UnsupportedOperationException($message, $results); default: throw new UnrecognizedExceptionException($message, $results); } } switch ($status_code) { case 1: throw new IndexOutOfBoundsException($message, $results); case 2: throw new NoCollectionException($message, $results); case 3: throw new NoStringException($message, $results); case 4: throw new NoStringLengthException($message, $results); case 5: throw new NoStringWrapperException($message, $results); case 6: throw new NoSuchDriverException($message, $results); case 7: throw new NoSuchElementException($message, $results); case 8: throw new NoSuchFrameException($message, $results); case 9: throw new UnknownCommandException($message, $results); case 10: throw new StaleElementReferenceException($message, $results); case 11: throw new ElementNotVisibleException($message, $results); case 12: throw new InvalidElementStateException($message, $results); case 13: throw new UnknownServerException($message, $results); case 14: throw new ExpectedException($message, $results); case 15: throw new ElementNotSelectableException($message, $results); case 16: throw new NoSuchDocumentException($message, $results); case 17: throw new UnexpectedJavascriptException($message, $results); case 18: throw new NoScriptResultException($message, $results); case 19: throw new XPathLookupException($message, $results); case 20: throw new NoSuchCollectionException($message, $results); case 21: throw new TimeoutException($message, $results); case 22: throw new NullPointerException($message, $results); case 23: throw new NoSuchWindowException($message, $results); case 24: throw new InvalidCookieDomainException($message, $results); case 25: throw new UnableToSetCookieException($message, $results); case 26: throw new UnexpectedAlertOpenException($message, $results); case 27: throw new NoAlertOpenException($message, $results); case 28: throw new ScriptTimeoutException($message, $results); case 29: throw new InvalidCoordinatesException($message, $results); case 30: throw new IMENotAvailableException($message, $results); case 31: throw new IMEEngineActivationFailedException($message, $results); case 32: throw new InvalidSelectorException($message, $results); case 33: throw new SessionNotCreatedException($message, $results); case 34: throw new MoveTargetOutOfBoundsException($message, $results); default: throw new UnrecognizedExceptionException($message, $results); } } } webdriver/lib/Exception/UnsupportedOperationException.php 0000644 00000000337 15021223212 0020033 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * Indicates that a command that should have executed properly cannot be supported for some reason. */ class UnsupportedOperationException extends WebDriverException { } webdriver/lib/Exception/ExpectedException.php 0000644 00000000324 15021223212 0015357 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class ExpectedException extends WebDriverException { } webdriver/lib/Exception/UnknownCommandException.php 0000644 00000000303 15021223212 0016551 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command could not be executed because the remote end is not aware of it. */ class UnknownCommandException extends WebDriverException { } webdriver/lib/Exception/NoCollectionException.php 0000644 00000000330 15021223212 0016203 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoCollectionException extends WebDriverException { } webdriver/lib/Exception/UnknownErrorException.php 0000644 00000000300 15021223212 0016261 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An unknown error occurred in the remote end while processing the command. */ class UnknownErrorException extends WebDriverException { } webdriver/lib/Exception/UnexpectedAlertOpenException.php 0000644 00000000257 15021223212 0017541 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A modal dialog was open, blocking this operation. */ class UnexpectedAlertOpenException extends WebDriverException { } webdriver/lib/Exception/NoSuchDocumentException.php 0000644 00000000276 15021223212 0016522 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Use Facebook\WebDriver\Exception\NoSuchWindowException */ class NoSuchDocumentException extends NoSuchWindowException { } webdriver/lib/Exception/TimeoutException.php 0000644 00000000253 15021223212 0015245 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An operation did not complete before its timeout expired. */ class TimeoutException extends WebDriverException { } webdriver/lib/Exception/StaleElementReferenceException.php 0000644 00000000321 15021223212 0020014 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command failed because the referenced element is no longer attached to the DOM. */ class StaleElementReferenceException extends WebDriverException { } webdriver/lib/Exception/NoSuchElementException.php 0000644 00000000306 15021223212 0016327 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * An element could not be located on the page using the given search parameters. */ class NoSuchElementException extends WebDriverException { } webdriver/lib/Exception/UnrecognizedExceptionException.php 0000644 00000000164 15021223212 0020133 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; class UnrecognizedExceptionException extends WebDriverException { } webdriver/lib/Exception/NoSuchCookieException.php 0000644 00000000376 15021223212 0016156 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s * active document. */ class NoSuchCookieException extends WebDriverException { } webdriver/lib/Exception/ElementNotSelectableException.php 0000644 00000000330 15021223212 0017651 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Use Facebook\WebDriver\Exception\ElementNotInteractableException */ class ElementNotSelectableException extends ElementNotInteractableException { } webdriver/lib/Exception/NoAlertOpenException.php 0000644 00000000271 15021223212 0016005 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Use Facebook\WebDriver\Exception\NoSuchAlertException */ class NoAlertOpenException extends NoSuchAlertException { } webdriver/lib/Exception/NoSuchCollectionException.php 0000644 00000000334 15021223212 0017032 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoSuchCollectionException extends WebDriverException { } webdriver/lib/Exception/UnexpectedTagNameException.php 0000644 00000001000 15021223212 0017147 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; class UnexpectedTagNameException extends WebDriverException { /** * @param string $expected_tag_name * @param string $actual_tag_name */ public function __construct( $expected_tag_name, $actual_tag_name ) { parent::__construct( sprintf( 'Element should have been "%s" but was "%s"', $expected_tag_name, $actual_tag_name ) ); } } webdriver/lib/Exception/NoScriptResultException.php 0000644 00000000332 15021223212 0016555 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NoScriptResultException extends WebDriverException { } webdriver/lib/Exception/SessionNotCreatedException.php 0000644 00000000237 15021223212 0017215 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A new session could not be created. */ class SessionNotCreatedException extends WebDriverException { } webdriver/lib/Exception/IMEEngineActivationFailedException.php 0000644 00000000345 15021223212 0020510 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class IMEEngineActivationFailedException extends WebDriverException { } webdriver/lib/Exception/DetachedShadowRootException.php 0000644 00000000322 15021223212 0017327 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command failed because the referenced shadow root is no longer attached to the DOM. */ class DetachedShadowRootException extends WebDriverException { } webdriver/lib/Exception/NoSuchWindowException.php 0000644 00000000324 15021223212 0016205 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command to switch to a window could not be satisfied because the window could not be found. */ class NoSuchWindowException extends WebDriverException { } webdriver/lib/Exception/UnableToCaptureScreenException.php 0000644 00000000245 15021223212 0020015 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A screen capture was made impossible. */ class UnableToCaptureScreenException extends WebDriverException { } webdriver/lib/Exception/InvalidElementStateException.php 0000644 00000000434 15021223212 0017521 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element * that isn’t both editable and resettable. */ class InvalidElementStateException extends WebDriverException { } webdriver/lib/Exception/InvalidCoordinatesException.php 0000644 00000000336 15021223212 0017402 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class InvalidCoordinatesException extends WebDriverException { } webdriver/lib/Exception/NoSuchFrameException.php 0000644 00000000321 15021223212 0015765 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * A command to switch to a frame could not be satisfied because the frame could not be found. */ class NoSuchFrameException extends WebDriverException { } webdriver/lib/Exception/NullPointerException.php 0000644 00000000327 15021223212 0016074 0 ustar 00 <?php namespace Facebook\WebDriver\Exception; /** * @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686 */ class NullPointerException extends WebDriverException { } webdriver/lib/WebDriverAction.php 0000644 00000000247 15021223212 0013034 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Interface representing a single user-interaction action. */ interface WebDriverAction { public function perform(); } webdriver/lib/WebDriverCheckboxes.php 0000644 00000002245 15021223212 0013675 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\InvalidElementStateException; /** * Provides helper methods for checkboxes. */ class WebDriverCheckboxes extends AbstractWebDriverCheckboxOrRadio { public function __construct(WebDriverElement $element) { parent::__construct($element); $this->type = $element->getAttribute('type'); if ($this->type !== 'checkbox') { throw new InvalidElementStateException('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); } } webdriver/lib/Chrome/ChromeDriver.php 0000644 00000007221 15021223212 0013612 0 ustar 00 <?php namespace Facebook\WebDriver\Chrome; use Facebook\WebDriver\Local\LocalWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; use Facebook\WebDriver\Remote\WebDriverCommand; class ChromeDriver extends LocalWebDriver { /** @var ChromeDevToolsDriver */ private $devTools; /** * Creates a new ChromeDriver using default configuration. * This includes starting a new chromedriver process each time this method is called. However this may be * unnecessary overhead - instead, you can start the process once using ChromeDriverService and pass * this instance to startUsingDriverService() method. * * @todo Remove $service parameter. Use `ChromeDriver::startUsingDriverService` to pass custom $service instance. * @return static */ public static function start(DesiredCapabilities $desired_capabilities = null, ChromeDriverService $service = null) { if ($service === null) { // TODO: Remove the condition (always create default service) $service = ChromeDriverService::createDefaultService(); } return static::startUsingDriverService($service, $desired_capabilities); } /** * Creates a new ChromeDriver using given ChromeDriverService. * This is usable when you for example don't want to start new chromedriver process for each individual test * and want to reuse the already started chromedriver, which will lower the overhead associated with spinning up * a new process. * @return static */ public static function startUsingDriverService( ChromeDriverService $service, DesiredCapabilities $capabilities = null ) { if ($capabilities === null) { $capabilities = DesiredCapabilities::chrome(); } $executor = new DriverCommandExecutor($service); $newSessionCommand = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], ], 'desiredCapabilities' => (object) $capabilities->toArray(), ] ); $response = $executor->execute($newSessionCommand); /* * TODO: in next major version we may not need to use this method, because without OSS compatibility the * driver creation is straightforward. */ return static::createFromResponse($response, $executor); } /** * @todo Remove in next major version. The class is internally no longer used and is kept only to keep BC. * @deprecated Use start or startUsingDriverService method instead. * @codeCoverageIgnore * @internal */ public function startSession(DesiredCapabilities $desired_capabilities) { $command = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], ], 'desiredCapabilities' => (object) $desired_capabilities->toArray(), ] ); $response = $this->executor->execute($command); $value = $response->getValue(); if (!$this->isW3cCompliant = isset($value['capabilities'])) { $this->executor->disableW3cCompliance(); } $this->sessionID = $response->getSessionID(); } /** * @return ChromeDevToolsDriver */ public function getDevTools() { if ($this->devTools === null) { $this->devTools = new ChromeDevToolsDriver($this); } return $this->devTools; } } webdriver/lib/Chrome/ChromeDevToolsDriver.php 0000644 00000002164 15021223212 0015273 0 ustar 00 <?php namespace Facebook\WebDriver\Chrome; use Facebook\WebDriver\Remote\RemoteWebDriver; /** * Provide access to Chrome DevTools Protocol (CDP) commands via HTTP endpoint of Chromedriver. * * @see https://chromedevtools.github.io/devtools-protocol/ */ class ChromeDevToolsDriver { public const SEND_COMMAND = [ 'method' => 'POST', 'url' => '/session/:sessionId/goog/cdp/execute', ]; /** * @var RemoteWebDriver */ private $driver; public function __construct(RemoteWebDriver $driver) { $this->driver = $driver; } /** * Executes a Chrome DevTools command * * @param string $command The DevTools command to execute * @param array $parameters Optional parameters to the command * @return array The result of the command */ public function execute($command, array $parameters = []) { $params = ['cmd' => $command, 'params' => (object) $parameters]; return $this->driver->executeCustomCommand( self::SEND_COMMAND['url'], self::SEND_COMMAND['method'], $params ); } } webdriver/lib/Chrome/ChromeOptions.php 0000644 00000010611 15021223212 0014007 0 ustar 00 <?php namespace Facebook\WebDriver\Chrome; use Facebook\WebDriver\Remote\DesiredCapabilities; use JsonSerializable; use ReturnTypeWillChange; /** * The class manages the capabilities in ChromeDriver. * * @see https://sites.google.com/a/chromium.org/chromedriver/capabilities */ class ChromeOptions implements JsonSerializable { /** * The key of chromeOptions in desired capabilities */ public const CAPABILITY = 'goog:chromeOptions'; /** * @deprecated Use CAPABILITY instead */ public const CAPABILITY_W3C = self::CAPABILITY; /** * @var array */ private $arguments = []; /** * @var string */ private $binary = ''; /** * @var array */ private $extensions = []; /** * @var array */ private $experimentalOptions = []; /** * Return a version of the class which can JSON serialized. * * @return array */ #[ReturnTypeWillChange] public function jsonSerialize() { return $this->toArray(); } /** * Sets the path of the Chrome executable. The path should be either absolute * or relative to the location running ChromeDriver server. * * @param string $path * @return ChromeOptions */ public function setBinary($path) { $this->binary = $path; return $this; } /** * @return ChromeOptions */ public function addArguments(array $arguments) { $this->arguments = array_merge($this->arguments, $arguments); return $this; } /** * Add a Chrome extension to install on browser startup. Each path should be * a packed Chrome extension. * * @return ChromeOptions */ public function addExtensions(array $paths) { foreach ($paths as $path) { $this->addExtension($path); } return $this; } /** * @param array $encoded_extensions An array of base64 encoded of the extensions. * @return ChromeOptions */ public function addEncodedExtensions(array $encoded_extensions) { foreach ($encoded_extensions as $encoded_extension) { $this->addEncodedExtension($encoded_extension); } return $this; } /** * Sets an experimental option which has not exposed officially. * * When using "prefs" to set Chrome preferences, please be aware they are so far not supported by * Chrome running in headless mode, see https://bugs.chromium.org/p/chromium/issues/detail?id=775911 * * @param string $name * @param mixed $value * @return ChromeOptions */ public function setExperimentalOption($name, $value) { $this->experimentalOptions[$name] = $value; return $this; } /** * @return DesiredCapabilities The DesiredCapabilities for Chrome with this options. */ public function toCapabilities() { $capabilities = DesiredCapabilities::chrome(); $capabilities->setCapability(self::CAPABILITY, $this); return $capabilities; } /** * @return \ArrayObject|array */ public function toArray() { // The selenium server expects a 'dictionary' instead of a 'list' when // reading the chrome option. However, an empty array in PHP will be // converted to a 'list' instead of a 'dictionary'. To fix it, we work // with `ArrayObject` $options = new \ArrayObject($this->experimentalOptions); if (!empty($this->binary)) { $options['binary'] = $this->binary; } if (!empty($this->arguments)) { $options['args'] = $this->arguments; } if (!empty($this->extensions)) { $options['extensions'] = $this->extensions; } return $options; } /** * Add a Chrome extension to install on browser startup. Each path should be a * packed Chrome extension. * * @param string $path * @return ChromeOptions */ private function addExtension($path) { $this->addEncodedExtension(base64_encode(file_get_contents($path))); return $this; } /** * @param string $encoded_extension Base64 encoded of the extension. * @return ChromeOptions */ private function addEncodedExtension($encoded_extension) { $this->extensions[] = $encoded_extension; return $this; } } webdriver/lib/Chrome/ChromeDriverService.php 0000644 00000002335 15021223212 0015134 0 ustar 00 <?php namespace Facebook\WebDriver\Chrome; use Facebook\WebDriver\Remote\Service\DriverService; class ChromeDriverService extends DriverService { /** * The environment variable storing the path to the chrome driver executable. * @deprecated Use ChromeDriverService::CHROME_DRIVER_EXECUTABLE */ public const CHROME_DRIVER_EXE_PROPERTY = 'webdriver.chrome.driver'; /** @var string The environment variable storing the path to the chrome driver executable */ public const CHROME_DRIVER_EXECUTABLE = 'WEBDRIVER_CHROME_DRIVER'; /** * @var string Default executable used when no other is provided * @internal */ public const DEFAULT_EXECUTABLE = 'chromedriver'; /** * @return static */ public static function createDefaultService() { $pathToExecutable = getenv(self::CHROME_DRIVER_EXECUTABLE) ?: getenv(self::CHROME_DRIVER_EXE_PROPERTY); if ($pathToExecutable === false || $pathToExecutable === '') { $pathToExecutable = static::DEFAULT_EXECUTABLE; } $port = 9515; // TODO: Get another port if the default port is used. $args = ['--port=' . $port]; return new static($pathToExecutable, $port, $args); } } webdriver/lib/Local/LocalWebDriver.php 0000644 00000002400 15021223212 0013674 0 ustar 00 <?php namespace Facebook\WebDriver\Local; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; /** * @codeCoverageIgnore * @todo Break inheritance from RemoteWebDriver in next major version. (Composition over inheritance!) */ abstract class LocalWebDriver extends RemoteWebDriver { // @todo Remove in next major version (should not be inherited) public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, $connection_timeout_in_ms = null, $request_timeout_in_ms = null, $http_proxy = null, $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { throw LogicException::forError('Use start() method to start local WebDriver.'); } // @todo Remove in next major version (should not be inherited) public static function createBySessionID( $session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', $connection_timeout_in_ms = null, $request_timeout_in_ms = null ) { throw LogicException::forError('Use start() method to start local WebDriver.'); } } webdriver/lib/WebDriverPlatform.php 0000644 00000001075 15021223212 0013403 0 ustar 00 <?php namespace Facebook\WebDriver; /** * The platforms supported by WebDriver. * * @codeCoverageIgnore */ class WebDriverPlatform { public const ANDROID = 'ANDROID'; /** @deprecated ANY has no meaning in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/731 */ public const ANY = 'ANY'; public const LINUX = 'LINUX'; public const MAC = 'MAC'; public const UNIX = 'UNIX'; public const VISTA = 'VISTA'; public const WINDOWS = 'WINDOWS'; public const XP = 'XP'; private function __construct() { } } webdriver/lib/WebDriverBy.php 0000644 00000005516 15021223212 0012175 0 ustar 00 <?php namespace Facebook\WebDriver; /** * The basic 8 mechanisms supported by webdriver to locate a web element. * ie. 'class name', 'css selector', 'id', 'name', 'link text', * 'partial link text', 'tag name' and 'xpath'. * * @see WebDriver::findElement, WebDriverElement::findElement */ class WebDriverBy { /** * @var string */ private $mechanism; /** * @var string */ private $value; protected function __construct($mechanism, $value) { $this->mechanism = $mechanism; $this->value = $value; } /** * @return string */ public function getMechanism() { return $this->mechanism; } /** * @return string */ public function getValue() { return $this->value; } /** * Locates elements whose class name contains the search value; compound class * names are not permitted. * * @param string $class_name * @return static */ public static function className($class_name) { return new static('class name', $class_name); } /** * Locates elements matching a CSS selector. * * @param string $css_selector * @return static */ public static function cssSelector($css_selector) { return new static('css selector', $css_selector); } /** * Locates elements whose ID attribute matches the search value. * * @param string $id * @return static */ public static function id($id) { return new static('id', $id); } /** * Locates elements whose NAME attribute matches the search value. * * @param string $name * @return static */ public static function name($name) { return new static('name', $name); } /** * Locates anchor elements whose visible text matches the search value. * * @param string $link_text * @return static */ public static function linkText($link_text) { return new static('link text', $link_text); } /** * Locates anchor elements whose visible text partially matches the search * value. * * @param string $partial_link_text * @return static */ public static function partialLinkText($partial_link_text) { return new static('partial link text', $partial_link_text); } /** * Locates elements whose tag name matches the search value. * * @param string $tag_name * @return static */ public static function tagName($tag_name) { return new static('tag name', $tag_name); } /** * Locates elements matching an XPath expression. * * @param string $xpath * @return static */ public static function xpath($xpath) { return new static('xpath', $xpath); } } webdriver/lib/Remote/Service/DriverService.php 0000644 00000010305 15021223212 0015410 0 ustar 00 <?php namespace Facebook\WebDriver\Remote\Service; use Facebook\WebDriver\Exception\Internal\IOException; use Facebook\WebDriver\Exception\Internal\RuntimeException; use Facebook\WebDriver\Net\URLChecker; use Symfony\Component\Process\Process; /** * Start local WebDriver service (when remote WebDriver server is not used). * This will start new process of respective browser driver and take care of its lifecycle. */ class DriverService { /** * @var string */ private $executable; /** * @var string */ private $url; /** * @var array */ private $args; /** * @var array */ private $environment; /** * @var Process|null */ private $process; /** * @param string $executable * @param int $port The given port the service should use. * @param array $args * @param array|null $environment Use the system environment if it is null */ public function __construct($executable, $port, $args = [], $environment = null) { $this->setExecutable($executable); $this->url = sprintf('http://localhost:%d', $port); $this->args = $args; $this->environment = $environment ?: $_ENV; } /** * @return string */ public function getURL() { return $this->url; } /** * @return DriverService */ public function start() { if ($this->process !== null) { return $this; } $this->process = $this->createProcess(); $this->process->start(); $this->checkWasStarted($this->process); $checker = new URLChecker(); $checker->waitUntilAvailable(20 * 1000, $this->url . '/status'); return $this; } /** * @return DriverService */ public function stop() { if ($this->process === null) { return $this; } $this->process->stop(); $this->process = null; $checker = new URLChecker(); $checker->waitUntilUnavailable(3 * 1000, $this->url . '/shutdown'); return $this; } /** * @return bool */ public function isRunning() { if ($this->process === null) { return false; } return $this->process->isRunning(); } /** * @deprecated Has no effect. Will be removed in next major version. Executable is now checked * when calling setExecutable(). * @param string $executable * @return string */ protected static function checkExecutable($executable) { return $executable; } /** * @param string $executable * @throws IOException */ protected function setExecutable($executable) { if ($this->isExecutable($executable)) { $this->executable = $executable; return; } throw IOException::forFileError( 'File is not executable. Make sure the path is correct or use environment variable to specify' . ' location of the executable.', $executable ); } /** * @param Process $process */ protected function checkWasStarted($process) { usleep(10000); // wait 10ms, otherwise the asynchronous process failure may not yet be propagated if (!$process->isRunning()) { throw RuntimeException::forDriverError($process); } } private function createProcess(): Process { $commandLine = array_merge([$this->executable], $this->args); return new Process($commandLine, null, $this->environment); } /** * Check whether given file is executable directly or using system PATH */ private function isExecutable(string $filename): bool { if (is_executable($filename)) { return true; } if ($filename !== basename($filename)) { // $filename is an absolute path, do no try to search it in PATH return false; } $paths = explode(PATH_SEPARATOR, getenv('PATH')); foreach ($paths as $path) { if (is_executable($path . DIRECTORY_SEPARATOR . $filename)) { return true; } } return false; } } webdriver/lib/Remote/Service/DriverCommandExecutor.php 0000644 00000002623 15021223212 0017111 0 ustar 00 <?php namespace Facebook\WebDriver\Remote\Service; use Facebook\WebDriver\Exception\Internal\DriverServerDiedException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\HttpCommandExecutor; use Facebook\WebDriver\Remote\WebDriverCommand; use Facebook\WebDriver\Remote\WebDriverResponse; /** * A HttpCommandExecutor that talks to a local driver service instead of a remote server. */ class DriverCommandExecutor extends HttpCommandExecutor { /** * @var DriverService */ private $service; public function __construct(DriverService $service) { parent::__construct($service->getURL()); $this->service = $service; } /** * @throws \Exception * @throws WebDriverException * @return WebDriverResponse */ public function execute(WebDriverCommand $command) { if ($command->getName() === DriverCommand::NEW_SESSION) { $this->service->start(); } try { $value = parent::execute($command); if ($command->getName() === DriverCommand::QUIT) { $this->service->stop(); } return $value; } catch (\Exception $e) { if (!$this->service->isRunning()) { throw new DriverServerDiedException($e); } throw $e; } } } webdriver/lib/Remote/JsonWireCompat.php 0000644 00000006036 15021223212 0014146 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\WebDriverBy; /** * Compatibility layer between W3C's WebDriver and the legacy JsonWire protocol. * * @internal */ abstract class JsonWireCompat { /** * Element identifier defined in the W3C's WebDriver protocol. * * @see https://w3c.github.io/webdriver/#elements */ public const WEB_DRIVER_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf'; /** * @param mixed $rawElement Value is validated to by an array, exception is thrown otherwise * @throws UnexpectedResponseException When value of other type than array is given */ public static function getElement($rawElement) { // The method intentionally accept mixed, so that assertion of the rawElement format could be done on one place if (!is_array($rawElement)) { throw UnexpectedResponseException::forElementNotArray($rawElement); } if (array_key_exists(self::WEB_DRIVER_ELEMENT_IDENTIFIER, $rawElement)) { // W3C's WebDriver return $rawElement[self::WEB_DRIVER_ELEMENT_IDENTIFIER]; } // Legacy JsonWire return $rawElement['ELEMENT']; } /** * @param bool $isW3cCompliant * * @return array */ public static function getUsing(WebDriverBy $by, $isW3cCompliant) { $mechanism = $by->getMechanism(); $value = $by->getValue(); if ($isW3cCompliant) { 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); } } webdriver/lib/Remote/ExecuteMethod.php 0000644 00000000341 15021223212 0013776 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; interface ExecuteMethod { /** * @param string $command_name * @return WebDriverResponse */ public function execute($command_name, array $parameters = []); } webdriver/lib/Remote/HttpCommandExecutor.php 0000644 00000047732 15021223212 0015207 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\Internal\WebDriverCurlException; use Facebook\WebDriver\Exception\WebDriverException; use Facebook\WebDriver\WebDriverCommandExecutor; /** * Command executor talking to the standalone server via HTTP. */ class HttpCommandExecutor implements WebDriverCommandExecutor { public const DEFAULT_HTTP_HEADERS = [ 'Content-Type: application/json;charset=UTF-8', 'Accept: application/json', ]; /** * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#command-reference */ protected static $commands = [ DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/accept_alert'], DriverCommand::ADD_COOKIE => ['method' => 'POST', 'url' => '/session/:sessionId/cookie'], DriverCommand::CLEAR_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/clear'], DriverCommand::CLICK_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/click'], DriverCommand::CLOSE => ['method' => 'DELETE', 'url' => '/session/:sessionId/window'], DriverCommand::DELETE_ALL_COOKIES => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie'], DriverCommand::DELETE_COOKIE => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie/:name'], DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/dismiss_alert'], DriverCommand::ELEMENT_EQUALS => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/equals/:other'], DriverCommand::FIND_CHILD_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/element'], DriverCommand::FIND_CHILD_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/elements'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute'], DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute_async'], DriverCommand::FIND_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element'], DriverCommand::FIND_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/elements'], DriverCommand::SWITCH_TO_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame'], DriverCommand::SWITCH_TO_PARENT_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame/parent'], DriverCommand::SWITCH_TO_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window'], DriverCommand::GET => ['method' => 'POST', 'url' => '/session/:sessionId/url'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert_text'], DriverCommand::GET_ALL_COOKIES => ['method' => 'GET', 'url' => '/session/:sessionId/cookie'], DriverCommand::GET_NAMED_COOKIE => ['method' => 'GET', 'url' => '/session/:sessionId/cookie/:name'], DriverCommand::GET_ALL_SESSIONS => ['method' => 'GET', 'url' => '/sessions'], DriverCommand::GET_AVAILABLE_LOG_TYPES => ['method' => 'GET', 'url' => '/session/:sessionId/log/types'], DriverCommand::GET_CURRENT_URL => ['method' => 'GET', 'url' => '/session/:sessionId/url'], DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window_handle'], DriverCommand::GET_ELEMENT_ATTRIBUTE => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/attribute/:name', ], DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/css/:propertyName', ], DriverCommand::GET_ELEMENT_LOCATION => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location', ], DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/location_in_view', ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/size'], DriverCommand::GET_ELEMENT_TAG_NAME => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/name'], DriverCommand::GET_ELEMENT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/text'], DriverCommand::GET_LOG => ['method' => 'POST', 'url' => '/session/:sessionId/log'], DriverCommand::GET_PAGE_SOURCE => ['method' => 'GET', 'url' => '/session/:sessionId/source'], DriverCommand::GET_SCREEN_ORIENTATION => ['method' => 'GET', 'url' => '/session/:sessionId/orientation'], DriverCommand::GET_CAPABILITIES => ['method' => 'GET', 'url' => '/session/:sessionId'], DriverCommand::GET_TITLE => ['method' => 'GET', 'url' => '/session/:sessionId/title'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window_handles'], DriverCommand::GET_WINDOW_POSITION => [ 'method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/size'], DriverCommand::GO_BACK => ['method' => 'POST', 'url' => '/session/:sessionId/back'], DriverCommand::GO_FORWARD => ['method' => 'POST', 'url' => '/session/:sessionId/forward'], DriverCommand::IS_ELEMENT_DISPLAYED => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/displayed', ], DriverCommand::IS_ELEMENT_ENABLED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/enabled'], DriverCommand::IS_ELEMENT_SELECTED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/selected'], DriverCommand::MAXIMIZE_WINDOW => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/maximize', ], DriverCommand::MOUSE_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/buttondown'], DriverCommand::MOUSE_UP => ['method' => 'POST', 'url' => '/session/:sessionId/buttonup'], DriverCommand::CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/click'], DriverCommand::DOUBLE_CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/doubleclick'], DriverCommand::MOVE_TO => ['method' => 'POST', 'url' => '/session/:sessionId/moveto'], DriverCommand::NEW_SESSION => ['method' => 'POST', 'url' => '/session'], DriverCommand::QUIT => ['method' => 'DELETE', 'url' => '/session/:sessionId'], DriverCommand::REFRESH => ['method' => 'POST', 'url' => '/session/:sessionId/refresh'], DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/file'], // undocumented DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/keys'], DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert_text'], DriverCommand::SEND_KEYS_TO_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/value'], DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/implicit_wait'], DriverCommand::SET_SCREEN_ORIENTATION => ['method' => 'POST', 'url' => '/session/:sessionId/orientation'], DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'], DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/async_script'], DriverCommand::SET_WINDOW_POSITION => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/position', ], DriverCommand::SET_WINDOW_SIZE => [ 'method' => 'POST', 'url' => '/session/:sessionId/window/:windowHandle/size', ], DriverCommand::STATUS => ['method' => 'GET', 'url' => '/status'], DriverCommand::SUBMIT_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/submit'], DriverCommand::SCREENSHOT => ['method' => 'GET', 'url' => '/session/:sessionId/screenshot'], DriverCommand::TAKE_ELEMENT_SCREENSHOT => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/screenshot', ], DriverCommand::TOUCH_SINGLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/click'], DriverCommand::TOUCH_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/touch/down'], DriverCommand::TOUCH_DOUBLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/doubleclick'], DriverCommand::TOUCH_FLICK => ['method' => 'POST', 'url' => '/session/:sessionId/touch/flick'], DriverCommand::TOUCH_LONG_PRESS => ['method' => 'POST', 'url' => '/session/:sessionId/touch/longclick'], DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'], DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'], DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'], DriverCommand::CUSTOM_COMMAND => [], ]; /** * @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_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'], DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'], DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT => [ 'method' => 'POST', 'url' => '/session/:sessionId/shadow/:id/element', ], DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT => [ 'method' => 'POST', 'url' => '/session/:sessionId/shadow/:id/elements', ], DriverCommand::FULLSCREEN_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/fullscreen'], DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'GET', 'url' => '/session/:sessionId/element/active'], DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'], 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_SHADOW_ROOT => [ 'method' => 'GET', 'url' => '/session/:sessionId/element/:id/shadow', ], DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'], DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'], DriverCommand::GET_WINDOW_POSITION => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'], 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::MINIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/minimize'], DriverCommand::NEW_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/new'], 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 */ protected $url; /** * @var resource */ protected $curl; /** * @var bool */ protected $isW3cCompliant = true; /** * @param string $url * @param string|null $http_proxy * @param int|null $http_proxy_port */ 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(); if (!empty($http_proxy)) { curl_setopt($this->curl, CURLOPT_PROXY, $http_proxy); if ($http_proxy_port !== null) { curl_setopt($this->curl, CURLOPT_PROXYPORT, $http_proxy_port); } } // Get credentials from $url (if any) $matches = null; if (preg_match("/^(https?:\/\/)(.*):(.*)@(.*?)/U", $url, $matches)) { $this->url = $matches[1] . $matches[4]; $auth_creds = $matches[2] . ':' . $matches[3]; curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($this->curl, CURLOPT_USERPWD, $auth_creds); } curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); $this->setConnectionTimeout(30 * 1000); // 30 seconds $this->setRequestTimeout(180 * 1000); // 3 minutes } public function disableW3cCompliance() { $this->isW3cCompliant = false; } /** * Set timeout for the connect phase * * @param int $timeout_in_ms Timeout in milliseconds * @return HttpCommandExecutor */ public function setConnectionTimeout($timeout_in_ms) { // There is a PHP bug in some versions which didn't define the constant. curl_setopt( $this->curl, /* CURLOPT_CONNECTTIMEOUT_MS */ 156, $timeout_in_ms ); return $this; } /** * Set the maximum time of a request * * @param int $timeout_in_ms Timeout in milliseconds * @return HttpCommandExecutor */ public function setRequestTimeout($timeout_in_ms) { // There is a PHP bug in some versions (at least for PHP 5.3.3) which // didn't define the constant. curl_setopt( $this->curl, /* CURLOPT_TIMEOUT_MS */ 155, $timeout_in_ms ); return $this; } /** * @return WebDriverResponse */ public function execute(WebDriverCommand $command) { $http_options = $this->getCommandHttpOptions($command); $http_method = $http_options['method']; $url = $http_options['url']; $sessionID = $command->getSessionID(); $url = str_replace(':sessionId', $sessionID ?? '', $url); $params = $command->getParameters(); foreach ($params as $name => $value) { if ($name[0] === ':') { $url = str_replace($name, $value, $url); unset($params[$name]); } } if (is_array($params) && !empty($params) && $http_method !== 'POST') { throw LogicException::forInvalidHttpMethod($url, $http_method, $params); } curl_setopt($this->curl, CURLOPT_URL, $this->url . $url); // https://github.com/facebook/php-webdriver/issues/173 if ($command->getName() === DriverCommand::NEW_SESSION) { curl_setopt($this->curl, CURLOPT_POST, 1); } else { curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method); } if (in_array($http_method, ['POST', 'PUT'], true)) { // Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy // https://tools.ietf.org/html/rfc7231#section-5.1.1 curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_merge(static::DEFAULT_HTTP_HEADERS, ['Expect:'])); } else { curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS); } $encoded_params = null; if ($http_method === 'POST') { if (is_array($params) && !empty($params)) { $encoded_params = json_encode($params); } elseif ($this->isW3cCompliant) { // 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); $raw_results = trim(curl_exec($this->curl)); if ($error = curl_error($this->curl)) { throw WebDriverCurlException::forCurlError($http_method, $url, $error, is_array($params) ? $params : null); } $results = json_decode($raw_results, true); if ($results === null && json_last_error() !== JSON_ERROR_NONE) { throw UnexpectedResponseException::forJsonDecodingError(json_last_error(), $raw_results); } $value = null; if (is_array($results) && array_key_exists('value', $results)) { $value = $results['value']; } $message = null; if (is_array($value) && array_key_exists('message', $value)) { $message = $value['message']; } $sessionId = null; 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/#errors if (isset($value['error'])) { // W3C's WebDriver WebDriverException::throwException($value['error'], $message, $results); } $status = $results['status'] ?? 0; if ($status !== 0) { // Legacy JsonWire WebDriverException::throwException($status, $message, $results); } $response = new WebDriverResponse($sessionId); return $response ->setStatus($status) ->setValue($value); } /** * @return string */ public function getAddressOfRemoteServer() { return $this->url; } /** * @return array */ protected function getCommandHttpOptions(WebDriverCommand $command) { $commandName = $command->getName(); if (!isset(self::$commands[$commandName])) { if ($this->isW3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) { throw LogicException::forError($command->getName() . ' is not a valid command.'); } } if ($this->isW3cCompliant) { $raw = self::$w3cCompliantCommands[$command->getName()]; } else { $raw = self::$commands[$command->getName()]; } if ($command instanceof CustomWebDriverCommand) { $url = $command->getCustomUrl(); $method = $command->getCustomMethod(); } else { $url = $raw['url']; $method = $raw['method']; } return [ 'url' => $url, 'method' => $method, ]; } } webdriver/lib/Remote/RemoteStatus.php 0000644 00000003223 15021223212 0013674 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; /** * Represents status of remote end * * @see https://www.w3.org/TR/webdriver/#status */ class RemoteStatus { /** @var bool */ protected $isReady; /** @var string */ protected $message; /** @var array */ protected $meta = []; /** * @param bool $isReady * @param string $message */ protected function __construct($isReady, $message, array $meta = []) { $this->isReady = (bool) $isReady; $this->message = (string) $message; $this->setMeta($meta); } /** * @return RemoteStatus */ public static function createFromResponse(array $responseBody) { $object = new static($responseBody['ready'], $responseBody['message'], $responseBody); return $object; } /** * The remote end's readiness state. * False if an attempt to create a session at the current time would fail. * However, the value true does not guarantee that a New Session command will succeed. * * @return bool */ public function isReady() { return $this->isReady; } /** * An implementation-defined string explaining the remote end's readiness state. * * @return string */ public function getMessage() { return $this->message; } /** * Arbitrary meta information specific to remote-end implementation. * * @return array */ public function getMeta() { return $this->meta; } protected function setMeta(array $meta) { unset($meta['ready'], $meta['message']); $this->meta = $meta; } } webdriver/lib/Remote/DesiredCapabilities.php 0000644 00000031104 15021223212 0015125 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Firefox\FirefoxDriver; use Facebook\WebDriver\Firefox\FirefoxOptions; use Facebook\WebDriver\Firefox\FirefoxProfile; use Facebook\WebDriver\WebDriverCapabilities; use Facebook\WebDriver\WebDriverPlatform; class DesiredCapabilities implements WebDriverCapabilities { /** @var array */ private $capabilities; /** @var array */ private static $ossToW3c = [ WebDriverCapabilityType::PLATFORM => 'platformName', WebDriverCapabilityType::VERSION => 'browserVersion', WebDriverCapabilityType::ACCEPT_SSL_CERTS => 'acceptInsecureCerts', ]; public function __construct(array $capabilities = []) { $this->capabilities = $capabilities; } public static function createFromW3cCapabilities(array $capabilities = []) { $w3cToOss = array_flip(self::$ossToW3c); foreach ($w3cToOss as $w3cCapability => $ossCapability) { // Copy W3C capabilities to OSS ones if (array_key_exists($w3cCapability, $capabilities)) { $capabilities[$ossCapability] = $capabilities[$w3cCapability]; } } return new self($capabilities); } /** * @return string The name of the browser. */ public function getBrowserName() { return $this->get(WebDriverCapabilityType::BROWSER_NAME, ''); } /** * @param string $browser_name * @return DesiredCapabilities */ public function setBrowserName($browser_name) { $this->set(WebDriverCapabilityType::BROWSER_NAME, $browser_name); return $this; } /** * @return string The version of the browser. */ public function getVersion() { return $this->get(WebDriverCapabilityType::VERSION, ''); } /** * @param string $version * @return DesiredCapabilities */ public function setVersion($version) { $this->set(WebDriverCapabilityType::VERSION, $version); return $this; } /** * @param string $name * @return mixed The value of a capability. */ public function getCapability($name) { return $this->get($name); } /** * @param string $name * @param mixed $value * @return DesiredCapabilities */ public function setCapability($name, $value) { // When setting 'moz:firefoxOptions' from an array and not from instance of FirefoxOptions, we must merge // it with default FirefoxOptions to keep previous behavior (where the default preferences were added // using FirefoxProfile, thus not overwritten by adding 'moz:firefoxOptions') // TODO: remove in next major version, once FirefoxOptions are only accepted as object instance and not as array if ($name === FirefoxOptions::CAPABILITY && is_array($value)) { $defaultOptions = (new FirefoxOptions())->toArray(); $value = array_merge($defaultOptions, $value); } $this->set($name, $value); return $this; } /** * @return string The name of the platform. */ public function getPlatform() { return $this->get(WebDriverCapabilityType::PLATFORM, ''); } /** * @param string $platform * @return DesiredCapabilities */ public function setPlatform($platform) { $this->set(WebDriverCapabilityType::PLATFORM, $platform); return $this; } /** * @param string $capability_name * @return bool Whether the value is not null and not false. */ public function is($capability_name) { return (bool) $this->get($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() { return $this->get(WebDriverCapabilityType::JAVASCRIPT_ENABLED, false); } /** * This is a htmlUnit-only option. * * @param bool $enabled * @throws UnsupportedOperationException * @return DesiredCapabilities * @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities */ public function setJavascriptEnabled($enabled) { $browser = $this->getBrowserName(); if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) { throw new UnsupportedOperationException( 'isJavascriptEnabled() is a htmlunit-only option. ' . 'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.' ); } $this->set(WebDriverCapabilityType::JAVASCRIPT_ENABLED, $enabled); return $this; } /** * @todo Remove side-effects - not change eg. ChromeOptions::CAPABILITY from instance of ChromeOptions to an array * @return array */ public function toArray() { if (isset($this->capabilities[ChromeOptions::CAPABILITY]) && $this->capabilities[ChromeOptions::CAPABILITY] instanceof ChromeOptions ) { $this->capabilities[ChromeOptions::CAPABILITY] = $this->capabilities[ChromeOptions::CAPABILITY]->toArray(); } if (isset($this->capabilities[FirefoxOptions::CAPABILITY]) && $this->capabilities[FirefoxOptions::CAPABILITY] instanceof FirefoxOptions ) { $this->capabilities[FirefoxOptions::CAPABILITY] = $this->capabilities[FirefoxOptions::CAPABILITY]->toArray(); } if (isset($this->capabilities[FirefoxDriver::PROFILE]) && $this->capabilities[FirefoxDriver::PROFILE] instanceof FirefoxProfile ) { $this->capabilities[FirefoxDriver::PROFILE] = $this->capabilities[FirefoxDriver::PROFILE]->encode(); } return $this->capabilities; } /** * @return array */ public function toW3cCompatibleArray() { $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 capabilities with changed name if (array_key_exists($capabilityKey, self::$ossToW3c)) { if ($capabilityKey === WebDriverCapabilityType::PLATFORM) { $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue); // Remove platformName if it is set to "any" if ($w3cCapabilities[self::$ossToW3c[$capabilityKey]] === 'any') { unset($w3cCapabilities[self::$ossToW3c[$capabilityKey]]); } } else { $w3cCapabilities[self::$ossToW3c[$capabilityKey]] = $capabilityValue; } } // Copy vendor extensions if (mb_strpos($capabilityKey, ':') !== false) { $w3cCapabilities[$capabilityKey] = $capabilityValue; } } // Convert ChromeOptions if (array_key_exists(ChromeOptions::CAPABILITY, $ossCapabilities)) { $w3cCapabilities[ChromeOptions::CAPABILITY] = $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(FirefoxOptions::CAPABILITY, $ossCapabilities) || !array_key_exists('profile', $ossCapabilities[FirefoxOptions::CAPABILITY])) { $w3cCapabilities[FirefoxOptions::CAPABILITY]['profile'] = $ossCapabilities[FirefoxDriver::PROFILE]; } } return $w3cCapabilities; } /** * @return static */ public static function android() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::ANDROID, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANDROID, ]); } /** * @return static */ public static function chrome() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } /** * @return static */ public static function firefox() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::FIREFOX, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); $caps->setCapability(FirefoxOptions::CAPABILITY, new FirefoxOptions()); // to add default options return $caps; } /** * @return static */ public static function htmlUnit() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } /** * @return static */ public static function htmlUnitWithJS() { $caps = new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); return $caps->setJavascriptEnabled(true); } /** * @return static */ public static function internetExplorer() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } /** * @return static */ public static function microsoftEdge() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::MICROSOFT_EDGE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS, ]); } /** * @return static */ public static function iphone() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPHONE, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } /** * @return static */ public static function ipad() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPAD, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC, ]); } /** * @return static */ public static function opera() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::OPERA, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } /** * @return static */ public static function safari() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::SAFARI, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } /** * @deprecated PhantomJS is no longer developed and its support will be removed in next major version. * Use headless Chrome or Firefox instead. * @return static */ public static function phantomjs() { return new static([ WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::PHANTOMJS, WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY, ]); } /** * @param string $key * @param mixed $value * @return DesiredCapabilities */ private function set($key, $value) { $this->capabilities[$key] = $value; return $this; } /** * @param string $key * @param mixed $default * @return mixed */ private function get($key, $default = null) { return $this->capabilities[$key] ?? $default; } } webdriver/lib/Remote/CustomWebDriverCommand.php 0000644 00000004261 15021223212 0015623 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\WebDriverException; class CustomWebDriverCommand extends WebDriverCommand { public const METHOD_GET = 'GET'; public const METHOD_POST = 'POST'; /** @var string */ private $customUrl; /** @var string */ private $customMethod; /** * @param string $session_id * @param string $url * @param string $method */ public function __construct($session_id, $url, $method, array $parameters) { $this->setCustomRequestParameters($url, $method); parent::__construct($session_id, DriverCommand::CUSTOM_COMMAND, $parameters); } /** * @throws WebDriverException * @return string */ public function getCustomUrl() { if ($this->customUrl === null) { throw LogicException::forError('URL of custom command is not set'); } return $this->customUrl; } /** * @throws WebDriverException * @return string */ public function getCustomMethod() { if ($this->customMethod === null) { throw LogicException::forError('Method of custom command is not set'); } return $this->customMethod; } /** * @param string $custom_url * @param string $custom_method * @throws WebDriverException */ protected function setCustomRequestParameters($custom_url, $custom_method) { $allowedMethods = [static::METHOD_GET, static::METHOD_POST]; if (!in_array($custom_method, $allowedMethods, true)) { throw LogicException::forError( sprintf( 'Invalid custom method "%s", must be one of [%s]', $custom_method, implode(', ', $allowedMethods) ) ); } $this->customMethod = $custom_method; if (mb_strpos($custom_url, '/') !== 0) { throw LogicException::forError( sprintf('URL of custom command has to start with / but is "%s"', $custom_url) ); } $this->customUrl = $custom_url; } } webdriver/lib/Remote/RemoteWebDriver.php 0000644 00000056733 15021223212 0014320 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Interactions\WebDriverActions; use Facebook\WebDriver\JavaScriptExecutor; use Facebook\WebDriver\Support\IsElementDisplayedAtom; use Facebook\WebDriver\Support\ScreenshotHelper; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverCapabilities; use Facebook\WebDriver\WebDriverCommandExecutor; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverHasInputDevices; use Facebook\WebDriver\WebDriverNavigation; use Facebook\WebDriver\WebDriverOptions; use Facebook\WebDriver\WebDriverWait; class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInputDevices { /** * @var HttpCommandExecutor|null */ protected $executor; /** * @var WebDriverCapabilities|null */ protected $capabilities; /** * @var string */ protected $sessionID; /** * @var RemoteMouse */ protected $mouse; /** * @var RemoteKeyboard */ protected $keyboard; /** * @var RemoteTouchScreen */ protected $touch; /** * @var RemoteExecuteMethod */ protected $executeMethod; /** * @var bool */ protected $isW3cCompliant; /** * @param string $sessionId * @param bool $isW3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec */ protected function __construct( HttpCommandExecutor $commandExecutor, $sessionId, WebDriverCapabilities $capabilities, $isW3cCompliant = false ) { $this->executor = $commandExecutor; $this->sessionID = $sessionId; $this->isW3cCompliant = $isW3cCompliant; $this->capabilities = $capabilities; } /** * Construct the RemoteWebDriver by a desired capabilities. * * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param DesiredCapabilities|array $desired_capabilities The desired capabilities * @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 DesiredCapabilities $required_capabilities The required capabilities * * @return static */ public static function create( $selenium_server_url = 'http://localhost:4444/wd/hub', $desired_capabilities = null, $connection_timeout_in_ms = null, $request_timeout_in_ms = null, $http_proxy = null, $http_proxy_port = null, DesiredCapabilities $required_capabilities = null ) { $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url); $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities); $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } if ($request_timeout_in_ms !== null) { $executor->setRequestTimeout($request_timeout_in_ms); } // W3C $parameters = [ 'capabilities' => [ 'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()], ], ]; if ($required_capabilities !== null && !empty($required_capabilities->toArray())) { $parameters['capabilities']['alwaysMatch'] = (object) $required_capabilities->toW3cCompatibleArray(); } // 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 // values with the legacy protocol. $desired_capabilities->setCapability('requiredCapabilities', (object) $required_capabilities->toArray()); } $parameters['desiredCapabilities'] = (object) $desired_capabilities->toArray(); $command = WebDriverCommand::newSession($parameters); $response = $executor->execute($command); return static::createFromResponse($response, $executor); } /** * [Experimental] Construct the RemoteWebDriver by an existing session. * * This constructor can boost the performance by reusing the same browser for the whole test suite. On the other * hand, because the browser is not pristine, this may lead to flaky and dependent tests. So carefully * consider the tradeoffs. * * To create the instance, we need to know Capabilities of the previously created session. You can either * pass them in $existingCapabilities parameter, or we will attempt to receive them from the Selenium Grid server. * However, if Capabilities were not provided and the attempt to get them was not successful, * exception will be thrown. * * @param string $session_id The existing session id * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server * @param bool $isW3cCompliant True to use W3C WebDriver (default), false to use the legacy JsonWire protocol * @param WebDriverCapabilities|null $existingCapabilities Provide capabilities of the existing previously created * session. If not provided, we will attempt to read them, but this will only work when using Selenium Grid. * @return static */ public static function createBySessionID( $session_id, $selenium_server_url = 'http://localhost:4444/wd/hub', $connection_timeout_in_ms = null, $request_timeout_in_ms = null ) { // BC layer to not break the method signature $isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : true; $existingCapabilities = func_num_args() > 5 ? func_get_arg(5) : null; $executor = new HttpCommandExecutor($selenium_server_url, null, null); if ($connection_timeout_in_ms !== null) { $executor->setConnectionTimeout($connection_timeout_in_ms); } if ($request_timeout_in_ms !== null) { $executor->setRequestTimeout($request_timeout_in_ms); } if (!$isW3cCompliant) { $executor->disableW3cCompliance(); } // if capabilities were not provided, attempt to read them from the Selenium Grid API if ($existingCapabilities === null) { $existingCapabilities = self::readExistingCapabilitiesFromSeleniumGrid($session_id, $executor); } return new static($executor, $session_id, $existingCapabilities, $isW3cCompliant); } /** * Close the current window. * * @return RemoteWebDriver The current instance. */ public function close() { $this->execute(DriverCommand::CLOSE, []); return $this; } /** * Create a new top-level browsing context. * * @codeCoverageIgnore * @deprecated Use $driver->switchTo()->newWindow() * @return WebDriver The current instance. */ public function newWindow() { return $this->switchTo()->newWindow(); } /** * Find the first WebDriverElement using the given mechanism. * * @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found. * @see WebDriverBy */ public function findElement(WebDriverBy $by) { $raw_element = $this->execute( DriverCommand::FIND_ELEMENT, JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); return $this->newElement(JsonWireCompat::getElement($raw_element)); } /** * Find all WebDriverElements within the current page using the given mechanism. * * @return RemoteWebElement[] A list of all WebDriverElements, or an empty array if nothing matches * @see WebDriverBy */ public function findElements(WebDriverBy $by) { $raw_elements = $this->execute( DriverCommand::FIND_ELEMENTS, JsonWireCompat::getUsing($by, $this->isW3cCompliant) ); if (!is_array($raw_elements)) { throw UnexpectedResponseException::forError('Server response to findElements command is not an array'); } $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; } /** * Load a new web page in the current browser window. * * @param string $url * * @return RemoteWebDriver The current instance. */ public function get($url) { $params = ['url' => (string) $url]; $this->execute(DriverCommand::GET, $params); return $this; } /** * Get a string representing the current URL that the browser is looking at. * * @return string The current URL. */ public function getCurrentURL() { return $this->execute(DriverCommand::GET_CURRENT_URL); } /** * Get the source of the last loaded page. * * @return string The current page source. */ public function getPageSource() { return $this->execute(DriverCommand::GET_PAGE_SOURCE); } /** * Get the title of the current page. * * @return string The title of the current page. */ public function getTitle() { return $this->execute(DriverCommand::GET_TITLE); } /** * Return an opaque handle to this window that uniquely identifies it within this driver instance. * * @return string The current window handle. */ public function getWindowHandle() { return $this->execute( DriverCommand::GET_CURRENT_WINDOW_HANDLE, [] ); } /** * Get all window handles available to the current session. * * Note: Do not use `end($driver->getWindowHandles())` to find the last open window, for proper solution see: * https://github.com/php-webdriver/php-webdriver/wiki/Alert,-tabs,-frames,-iframes#switch-to-the-new-window * * @return array An array of string containing all available window handles. */ public function getWindowHandles() { return $this->execute(DriverCommand::GET_WINDOW_HANDLES, []); } /** * Quits this driver, closing every associated window. */ public function quit() { $this->execute(DriverCommand::QUIT); $this->executor = null; } /** * 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. * @return mixed The return value of the script. */ public function executeScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params); } /** * 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. * * 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. * @return mixed The value passed by the script to the callback. */ public function executeAsyncScript($script, array $arguments = []) { $params = [ 'script' => $script, 'args' => $this->prepareScriptArguments($arguments), ]; return $this->execute( DriverCommand::EXECUTE_ASYNC_SCRIPT, $params ); } /** * Take a screenshot of the current page. * * @param string $save_as The path of the screenshot to be saved. * @return string The screenshot in PNG format. */ public function takeScreenshot($save_as = null) { return (new ScreenshotHelper($this->getExecuteMethod()))->takePageScreenshot($save_as); } /** * Status returns information about whether a remote end is in a state in which it can create new sessions. */ public function getStatus() { $response = $this->execute(DriverCommand::STATUS); return RemoteStatus::createFromResponse($response); } /** * 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 * * @return WebDriverWait */ public function wait($timeout_in_second = 30, $interval_in_millisecond = 250) { return new WebDriverWait( $this, $timeout_in_second, $interval_in_millisecond ); } /** * An abstraction for managing stuff you would do in a browser menu. For example, adding and deleting cookies. * * @return WebDriverOptions */ public function manage() { return new WebDriverOptions($this->getExecuteMethod(), $this->isW3cCompliant); } /** * An abstraction allowing the driver to access the browser's history and to navigate to a given URL. * * @return WebDriverNavigation * @see WebDriverNavigation */ public function navigate() { return new WebDriverNavigation($this->getExecuteMethod()); } /** * Switch to a different window or frame. * * @return RemoteTargetLocator * @see RemoteTargetLocator */ public function switchTo() { return new RemoteTargetLocator($this->getExecuteMethod(), $this, $this->isW3cCompliant); } /** * @return RemoteMouse */ public function getMouse() { if (!$this->mouse) { $this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->isW3cCompliant); } return $this->mouse; } /** * @return RemoteKeyboard */ public function getKeyboard() { if (!$this->keyboard) { $this->keyboard = new RemoteKeyboard($this->getExecuteMethod(), $this, $this->isW3cCompliant); } return $this->keyboard; } /** * @return RemoteTouchScreen */ public function getTouch() { if (!$this->touch) { $this->touch = new RemoteTouchScreen($this->getExecuteMethod()); } return $this->touch; } /** * Construct a new action builder. * * @return WebDriverActions */ public function action() { return new WebDriverActions($this); } /** * Set the command executor of this RemoteWebdriver * * @deprecated To be removed in the future. Executor should be passed in the constructor. * @internal * @codeCoverageIgnore * @param WebDriverCommandExecutor $executor Despite the typehint, it have be an instance of HttpCommandExecutor. * @return RemoteWebDriver */ public function setCommandExecutor(WebDriverCommandExecutor $executor) { $this->executor = $executor; return $this; } /** * Get the command executor of this RemoteWebdriver * * @return HttpCommandExecutor */ public function getCommandExecutor() { return $this->executor; } /** * Set the session id of the RemoteWebDriver. * * @deprecated To be removed in the future. Session ID should be passed in the constructor. * @internal * @codeCoverageIgnore * @param string $session_id * @return RemoteWebDriver */ public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } /** * Get current selenium sessionID * * @return string */ public function getSessionID() { return $this->sessionID; } /** * Get capabilities of the RemoteWebDriver. * * @return WebDriverCapabilities|null */ public function getCapabilities() { return $this->capabilities; } /** * Returns a list of the currently active sessions. * * @deprecated Removed in W3C WebDriver. * @param string $selenium_server_url The url of the remote Selenium WebDriver server * @param int $timeout_in_ms * @return array */ public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000) { $executor = new HttpCommandExecutor($selenium_server_url, null, null); $executor->setConnectionTimeout($timeout_in_ms); $command = new WebDriverCommand( null, DriverCommand::GET_ALL_SESSIONS, [] ); return $executor->execute($command)->getValue(); } public function execute($command_name, $params = []) { // As we so far only use atom for IS_ELEMENT_DISPLAYED, this condition is hardcoded here. In case more atoms // are used, this should be rewritten and separated from this class (e.g. to some abstract matcher logic). if ($command_name === DriverCommand::IS_ELEMENT_DISPLAYED && ( // When capabilities are missing in php-webdriver 1.13.x, always fallback to use the atom $this->getCapabilities() === null // If capabilities are present, use the atom only if condition matches || IsElementDisplayedAtom::match($this->getCapabilities()->getBrowserName()) ) ) { return (new IsElementDisplayedAtom($this))->execute($params); } $command = new WebDriverCommand( $this->sessionID, $command_name, $params ); if ($this->executor) { $response = $this->executor->execute($command); return $response->getValue(); } return null; } /** * Execute custom commands on remote end. * For example vendor-specific commands or other commands not implemented by php-webdriver. * * @see https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands * @param string $endpointUrl * @param string $method * @param array $params * @return mixed|null */ public function executeCustomCommand($endpointUrl, $method = 'GET', $params = []) { $command = new CustomWebDriverCommand( $this->sessionID, $endpointUrl, $method, $params ); if ($this->executor) { $response = $this->executor->execute($command); return $response->getValue(); } return null; } /** * @internal * @return bool */ public function isW3cCompliant() { return $this->isW3cCompliant; } /** * Create instance based on response to NEW_SESSION command. * Also detect W3C/OSS dialect and setup the driver/executor accordingly. * * @internal * @return static */ protected static function createFromResponse(WebDriverResponse $response, HttpCommandExecutor $commandExecutor) { $responseValue = $response->getValue(); if (!$isW3cCompliant = isset($responseValue['capabilities'])) { $commandExecutor->disableW3cCompliance(); } if ($isW3cCompliant) { $returnedCapabilities = DesiredCapabilities::createFromW3cCapabilities($responseValue['capabilities']); } else { $returnedCapabilities = new DesiredCapabilities($responseValue); } return new static($commandExecutor, $response->getSessionID(), $returnedCapabilities, $isW3cCompliant); } /** * Prepare arguments for JavaScript injection * * @return array */ protected function prepareScriptArguments(array $arguments) { $args = []; foreach ($arguments as $key => $value) { if ($value instanceof WebDriverElement) { $args[$key] = [ $this->isW3cCompliant ? JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER : 'ELEMENT' => $value->getID(), ]; } else { if (is_array($value)) { $value = $this->prepareScriptArguments($value); } $args[$key] = $value; } } return $args; } /** * @return RemoteExecuteMethod */ protected function getExecuteMethod() { if (!$this->executeMethod) { $this->executeMethod = new RemoteExecuteMethod($this); } return $this->executeMethod; } /** * Return the WebDriverElement with the given id. * * @param string $id The id of the element to be created. * @return RemoteWebElement */ protected function newElement($id) { return new RemoteWebElement($this->getExecuteMethod(), $id, $this->isW3cCompliant); } /** * Cast legacy types (array or null) to DesiredCapabilities object. To be removed in future when instance of * DesiredCapabilities will be required. * * @param array|DesiredCapabilities|null $desired_capabilities * @return DesiredCapabilities */ protected static function castToDesiredCapabilitiesObject($desired_capabilities = null) { if ($desired_capabilities === null) { return new DesiredCapabilities(); } if (is_array($desired_capabilities)) { return new DesiredCapabilities($desired_capabilities); } return $desired_capabilities; } protected static function readExistingCapabilitiesFromSeleniumGrid( string $session_id, HttpCommandExecutor $executor ): DesiredCapabilities { $getCapabilitiesCommand = new CustomWebDriverCommand($session_id, '/se/grid/session/:sessionId', 'GET', []); try { $capabilitiesResponse = $executor->execute($getCapabilitiesCommand); $existingCapabilities = DesiredCapabilities::createFromW3cCapabilities( $capabilitiesResponse->getValue()['capabilities'] ); if ($existingCapabilities === null) { throw UnexpectedResponseException::forError('Empty capabilities received'); } } catch (\Exception $e) { throw UnexpectedResponseException::forCapabilitiesRetrievalError($e); } return $existingCapabilities; } } webdriver/lib/Remote/ShadowRoot.php 0000644 00000005015 15021223212 0013327 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\UnknownErrorException; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverSearchContext; class ShadowRoot implements WebDriverSearchContext { /** * Shadow root identifier defined in the W3CWebDriver protocol. * * @see https://w3c.github.io/webdriver/#shadow-root */ public const SHADOW_ROOT_IDENTIFIER = 'shadow-6066-11e4-a52e-4f735466cecf'; /** * @var RemoteExecuteMethod */ private $executor; /** * @var string */ private $id; public function __construct(RemoteExecuteMethod $executor, $id) { $this->executor = $executor; $this->id = $id; } /** * @return self */ public static function createFromResponse(RemoteExecuteMethod $executor, array $response) { if (empty($response[self::SHADOW_ROOT_IDENTIFIER])) { throw new UnknownErrorException('Shadow root is missing in server response'); } return new self($executor, $response[self::SHADOW_ROOT_IDENTIFIER]); } /** * @return RemoteWebElement */ public function findElement(WebDriverBy $locator) { $params = JsonWireCompat::getUsing($locator, true); $params[':id'] = $this->id; $rawElement = $this->executor->execute( DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT, $params ); return new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true); } /** * @return WebDriverElement[] */ public function findElements(WebDriverBy $locator) { $params = JsonWireCompat::getUsing($locator, true); $params[':id'] = $this->id; $rawElements = $this->executor->execute( DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT, $params ); if (!is_array($rawElements)) { throw UnexpectedResponseException::forError( 'Server response to findElementsFromShadowRoot command is not an array' ); } $elements = []; foreach ($rawElements as $rawElement) { $elements[] = new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true); } return $elements; } /** * @return string */ public function getID() { return $this->id; } } webdriver/lib/Remote/RemoteWebElement.php 0000644 00000050646 15021223212 0014453 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\ElementNotInteractableException; use Facebook\WebDriver\Exception\Internal\IOException; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException; use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface; use Facebook\WebDriver\Exception\UnsupportedOperationException; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\Internal\WebDriverLocatable; use Facebook\WebDriver\Support\ScreenshotHelper; use Facebook\WebDriver\WebDriverBy; use Facebook\WebDriver\WebDriverDimension; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverKeys; use Facebook\WebDriver\WebDriverPoint; use ZipArchive; /** * Represents an HTML element. */ class RemoteWebElement implements WebDriverElement, WebDriverLocatable { /** * @var RemoteExecuteMethod */ protected $executor; /** * @var string */ protected $id; /** * @var FileDetector */ protected $fileDetector; /** * @var bool */ protected $isW3cCompliant; /** * @param string $id * @param bool $isW3cCompliant */ public function __construct(RemoteExecuteMethod $executor, $id, $isW3cCompliant = false) { $this->executor = $executor; $this->id = $id; $this->fileDetector = new UselessFileDetector(); $this->isW3cCompliant = $isW3cCompliant; } /** * Clear content editable or resettable element * * @return RemoteWebElement The current instance. */ public function clear() { $this->executor->execute( DriverCommand::CLEAR_ELEMENT, [':id' => $this->id] ); return $this; } /** * Click this element. * * @return RemoteWebElement The current instance. */ public function click() { try { $this->executor->execute( DriverCommand::CLICK_ELEMENT, [':id' => $this->id] ); } catch (ElementNotInteractableException $e) { // An issue with geckodriver (https://github.com/mozilla/geckodriver/issues/653) prevents clicking on a link // if the first child is a block-level element. // The workaround in this case is to click on a child element. $this->clickChildElement($e); } return $this; } /** * Find the first WebDriverElement within this element using the given mechanism. * * When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will * search the entire document from the root, not just the children (relative context) of this current node. * Use ".//" to limit your search to the children of this element. * * @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found. * @see WebDriverBy */ public function findElement(WebDriverBy $by) { $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_element = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENT, $params ); return $this->newElement(JsonWireCompat::getElement($raw_element)); } /** * Find all WebDriverElements within this element using the given mechanism. * * When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will * search the entire document from the root, not just the children (relative context) of this current node. * Use ".//" to limit your search to the children of this element. * * @return RemoteWebElement[] A list of all WebDriverElements, or an empty * array if nothing matches * @see WebDriverBy */ public function findElements(WebDriverBy $by) { $params = JsonWireCompat::getUsing($by, $this->isW3cCompliant); $params[':id'] = $this->id; $raw_elements = $this->executor->execute( DriverCommand::FIND_CHILD_ELEMENTS, $params ); if (!is_array($raw_elements)) { throw UnexpectedResponseException::forError('Server response to findChildElements command is not an array'); } $elements = []; foreach ($raw_elements as $raw_element) { $elements[] = $this->newElement(JsonWireCompat::getElement($raw_element)); } return $elements; } /** * Get the value of the given attribute of the element. * Attribute is meant what is declared in the HTML markup of the element. * To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method. * * @param string $attribute_name The name of the attribute. * @return string|true|null The value of the attribute. If this is boolean attribute, return true if the element * has it, otherwise return null. */ public function getAttribute($attribute_name) { $params = [ ':name' => $attribute_name, ':id' => $this->id, ]; if ($this->isW3cCompliant && ($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); } /** * Gets the value of a IDL JavaScript property of this element (for example `innerHTML`, `tagName` etc.). * * @see https://developer.mozilla.org/en-US/docs/Glossary/IDL * @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties * @param string $propertyName * @return mixed|null The property's current value or null if the value is not set or the property does not exist. */ public function getDomProperty($propertyName) { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('This method is only supported in W3C mode'); } $params = [ ':name' => $propertyName, ':id' => $this->id, ]; return $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params); } /** * Get the value of a given CSS property. * * @param string $css_property_name The name of the CSS property. * @return string The value of the CSS property. */ public function getCSSValue($css_property_name) { $params = [ ':propertyName' => $css_property_name, ':id' => $this->id, ]; return $this->executor->execute( DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY, $params ); } /** * Get the location of element relative to the top-left corner of the page. * * @return WebDriverPoint The location of the element. */ public function getLocation() { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION, [':id' => $this->id] ); return new WebDriverPoint($location['x'], $location['y']); } /** * Try scrolling the element into the view port and return the location of * element relative to the top-left corner of the page afterwards. * * @return WebDriverPoint The location of the element. */ public function getLocationOnScreenOnceScrolledIntoView() { if ($this->isW3cCompliant) { $script = <<<JS var e = arguments[0]; e.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'nearest' }); var rect = e.getBoundingClientRect(); return {'x': rect.left, 'y': rect.top}; JS; $result = $this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $script, 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], ]); $location = ['x' => $result['x'], 'y' => $result['y']]; } else { $location = $this->executor->execute( DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW, [':id' => $this->id] ); } return new WebDriverPoint($location['x'], $location['y']); } /** * @return WebDriverCoordinates */ public function getCoordinates() { $element = $this; $on_screen = null; // planned but not yet implemented $in_view_port = static function () use ($element) { return $element->getLocationOnScreenOnceScrolledIntoView(); }; $on_page = static function () use ($element) { return $element->getLocation(); }; $auxiliary = $this->getID(); return new WebDriverCoordinates( $on_screen, $in_view_port, $on_page, $auxiliary ); } /** * Get the size of element. * * @return WebDriverDimension The dimension of the element. */ public function getSize() { $size = $this->executor->execute( DriverCommand::GET_ELEMENT_SIZE, [':id' => $this->id] ); return new WebDriverDimension($size['width'], $size['height']); } /** * 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 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. return mb_strtolower($this->executor->execute( DriverCommand::GET_ELEMENT_TAG_NAME, [':id' => $this->id] )); } /** * Get the visible (i.e. not hidden by CSS) innerText of this element, * including sub-elements, without any leading or trailing whitespace. * * @return string The visible innerText of this element. */ public function getText() { return $this->executor->execute( DriverCommand::GET_ELEMENT_TEXT, [':id' => $this->id] ); } /** * Is this element displayed or not? This method avoids the problem of having * to parse an element's "style" attribute. * * @return bool */ public function isDisplayed() { return $this->executor->execute( DriverCommand::IS_ELEMENT_DISPLAYED, [':id' => $this->id] ); } /** * Is the element currently enabled or not? This will generally return true * for everything but disabled input elements. * * @return bool */ public function isEnabled() { return $this->executor->execute( DriverCommand::IS_ELEMENT_ENABLED, [':id' => $this->id] ); } /** * Determine whether this element is selected or not. * * @return bool */ public function isSelected() { return $this->executor->execute( DriverCommand::IS_ELEMENT_SELECTED, [':id' => $this->id] ); } /** * Simulate typing into an element, which may set its value. * * @param mixed $value The data to be typed. * @return RemoteWebElement The current instance. */ public function sendKeys($value) { $local_file = $this->fileDetector->getLocalFile($value); $params = []; if ($local_file === null) { if ($this->isW3cCompliant) { // Work around the Geckodriver NULL issue by splitting on NULL and calling sendKeys multiple times. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1494661. $encodedValues = explode(WebDriverKeys::NULL, WebDriverKeys::encode($value, true)); foreach ($encodedValues as $encodedValue) { $params[] = [ 'text' => $encodedValue, ':id' => $this->id, ]; } } else { $params[] = [ 'value' => WebDriverKeys::encode($value), ':id' => $this->id, ]; } } else { if ($this->isW3cCompliant) { try { // Attempt to upload the file to the remote browser. // This is so far non-W3C compliant method, so it may fail - if so, we just ignore the exception. // @see https://github.com/w3c/webdriver/issues/1355 $fileName = $this->upload($local_file); } catch (PhpWebDriverExceptionInterface $e) { $fileName = $local_file; } $params[] = [ 'text' => $fileName, ':id' => $this->id, ]; } else { $params[] = [ 'value' => WebDriverKeys::encode($this->upload($local_file)), ':id' => $this->id, ]; } } foreach ($params as $param) { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $param); } return $this; } /** * Set the fileDetector in order to let the RemoteWebElement to know that you are going to upload a file. * * Basically, if you want WebDriver trying to send a file, set the fileDetector * to be LocalFileDetector. Otherwise, keep it UselessFileDetector. * * eg. `$element->setFileDetector(new LocalFileDetector);` * * @return RemoteWebElement * @see FileDetector * @see LocalFileDetector * @see UselessFileDetector */ public function setFileDetector(FileDetector $detector) { $this->fileDetector = $detector; return $this; } /** * If this current element is a form, or an element within a form, then this will be submitted to the remote server. * * @return RemoteWebElement The current instance. */ public function submit() { if ($this->isW3cCompliant) { // Submit method cannot be called directly in case an input of this form is named "submit". // We use this polyfill to trigger 'submit' event using form.dispatchEvent(). $submitPolyfill = <<<HTXT var form = arguments[0]; while (form.nodeName !== "FORM" && form.parentNode) { // find the parent form of this element form = form.parentNode; } if (!form) { throw Error('Unable to find containing form element'); } var event = new Event('submit', {bubbles: true, cancelable: true}); if (form.dispatchEvent(event)) { HTMLFormElement.prototype.submit.call(form); } HTXT; $this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [ 'script' => $submitPolyfill, 'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]], ]); return $this; } $this->executor->execute( DriverCommand::SUBMIT_ELEMENT, [':id' => $this->id] ); return $this; } /** * Get the opaque ID of the element. * * @return string The opaque ID. */ public function getID() { return $this->id; } /** * Take a screenshot of a specific element. * * @param string $save_as The path of the screenshot to be saved. * @return string The screenshot in PNG format. */ public function takeElementScreenshot($save_as = null) { return (new ScreenshotHelper($this->executor))->takeElementScreenshot($this->id, $save_as); } /** * Test if two elements IDs refer to the same DOM element. * * @return bool */ public function equals(WebDriverElement $other) { if ($this->isW3cCompliant) { return $this->getID() === $other->getID(); } return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [ ':id' => $this->id, ':other' => $other->getID(), ]); } /** * Get representation of an element's shadow root for accessing the shadow DOM of a web component. * * @return ShadowRoot */ public function getShadowRoot() { if (!$this->isW3cCompliant) { throw new UnsupportedOperationException('This method is only supported in W3C mode'); } $response = $this->executor->execute( DriverCommand::GET_ELEMENT_SHADOW_ROOT, [ ':id' => $this->id, ] ); return ShadowRoot::createFromResponse($this->executor, $response); } /** * Attempt to click on a child level element. * * This provides a workaround for geckodriver bug 653 whereby a link whose first element is a block-level element * throws an ElementNotInteractableException could not scroll into view exception. * * The workaround provided here attempts to click on a child node of the element. * In case the first child is hidden, other elements are processed until we run out of elements. * * @param ElementNotInteractableException $originalException The exception to throw if unable to click on any child * @see https://github.com/mozilla/geckodriver/issues/653 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1374283 */ protected function clickChildElement(ElementNotInteractableException $originalException) { $children = $this->findElements(WebDriverBy::xpath('./*')); foreach ($children as $child) { try { // Note: This does not use $child->click() as this would cause recursion into all children. // Where the element is hidden, all children will also be hidden. $this->executor->execute( DriverCommand::CLICK_ELEMENT, [':id' => $child->id] ); return; } catch (ElementNotInteractableException $e) { // Ignore the ElementNotInteractableException exception on this node. Try the next child instead. } } throw $originalException; } /** * Return the WebDriverElement with $id * * @param string $id * * @return static */ protected function newElement($id) { return new static($this->executor, $id, $this->isW3cCompliant); } /** * Upload a local file to the server * * @param string $local_file * * @throws LogicException * @return string The remote path of the file. */ protected function upload($local_file) { if (!is_file($local_file)) { throw LogicException::forError('You may only upload files: ' . $local_file); } $temp_zip_path = $this->createTemporaryZipArchive($local_file); $remote_path = $this->executor->execute( DriverCommand::UPLOAD_FILE, ['file' => base64_encode(file_get_contents($temp_zip_path))] ); unlink($temp_zip_path); return $remote_path; } /** * @param string $fileToZip * @return string */ protected function createTemporaryZipArchive($fileToZip) { // Create a temporary file in the system temp directory. // Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle. $tempZipPath = sys_get_temp_dir() . '/' . uniqid('WebDriverZip', false); $zip = new ZipArchive(); if (($errorCode = $zip->open($tempZipPath, ZipArchive::CREATE)) !== true) { throw IOException::forFileError(sprintf('Error creating zip archive: %s', $errorCode), $tempZipPath); } $info = pathinfo($fileToZip); $file_name = $info['basename']; $zip->addFile($fileToZip, $file_name); $zip->close(); return $tempZipPath; } } webdriver/lib/Remote/WebDriverCommand.php 0000644 00000002575 15021223212 0014436 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; class WebDriverCommand { /** @var string|null */ protected $sessionID; /** @var string */ protected $name; /** @var array */ protected $parameters; /** * @param string $session_id * @param string $name Constant from DriverCommand * @param array $parameters * @todo In 2.0 force parameters to be an array, then remove is_array() checks in HttpCommandExecutor * @todo In 2.0 make constructor private. Use by default static `::create()` with sessionID type string. */ public function __construct($session_id, $name, $parameters) { $this->sessionID = $session_id; $this->name = $name; $this->parameters = $parameters; } /** * @return self */ public static function newSession(array $parameters) { // TODO: In 2.0 call empty constructor and assign properties directly. return new self(null, DriverCommand::NEW_SESSION, $parameters); } /** * @return string */ public function getName() { return $this->name; } /** * @return string|null Could be null for newSession command */ public function getSessionID() { return $this->sessionID; } /** * @return array */ public function getParameters() { return $this->parameters; } } webdriver/lib/Remote/FileDetector.php 0000644 00000000526 15021223212 0013611 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; interface FileDetector { /** * Try to detect whether the given $file is a file or not. Return the path * of the file. Otherwise, return null. * * @param string $file * * @return null|string The path of the file. */ public function getLocalFile($file); } webdriver/lib/Remote/RemoteExecuteMethod.php 0000644 00000000747 15021223212 0015164 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; class RemoteExecuteMethod implements ExecuteMethod { /** * @var RemoteWebDriver */ private $driver; public function __construct(RemoteWebDriver $driver) { $this->driver = $driver; } /** * @param string $command_name * @return mixed */ public function execute($command_name, array $parameters = []) { return $this->driver->execute($command_name, $parameters); } } webdriver/lib/Remote/WebDriverCapabilityType.php 0000644 00000002133 15021223212 0015771 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; /** * WebDriverCapabilityType contains all constants defined in the WebDriver Wire Protocol. * * @codeCoverageIgnore */ class WebDriverCapabilityType { public const BROWSER_NAME = 'browserName'; public const VERSION = 'version'; public const PLATFORM = 'platform'; public const JAVASCRIPT_ENABLED = 'javascriptEnabled'; public const TAKES_SCREENSHOT = 'takesScreenshot'; public const HANDLES_ALERTS = 'handlesAlerts'; public const DATABASE_ENABLED = 'databaseEnabled'; public const LOCATION_CONTEXT_ENABLED = 'locationContextEnabled'; public const APPLICATION_CACHE_ENABLED = 'applicationCacheEnabled'; public const BROWSER_CONNECTION_ENABLED = 'browserConnectionEnabled'; public const CSS_SELECTORS_ENABLED = 'cssSelectorsEnabled'; public const WEB_STORAGE_ENABLED = 'webStorageEnabled'; public const ROTATABLE = 'rotatable'; public const ACCEPT_SSL_CERTS = 'acceptSslCerts'; public const NATIVE_EVENTS = 'nativeEvents'; public const PROXY = 'proxy'; private function __construct() { } } webdriver/lib/Remote/WebDriverResponse.php 0000644 00000002441 15021223212 0014646 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; class WebDriverResponse { /** * @var int */ private $status; /** * @var mixed */ private $value; /** * @var string */ private $sessionID; /** * @param null|string $session_id */ public function __construct($session_id = null) { $this->sessionID = $session_id; } /** * @return null|int */ public function getStatus() { return $this->status; } /** * @param int $status * @return WebDriverResponse */ public function setStatus($status) { $this->status = $status; return $this; } /** * @return mixed */ public function getValue() { return $this->value; } /** * @param mixed $value * @return WebDriverResponse */ public function setValue($value) { $this->value = $value; return $this; } /** * @return null|string */ public function getSessionID() { return $this->sessionID; } /** * @param mixed $session_id * @return WebDriverResponse */ public function setSessionID($session_id) { $this->sessionID = $session_id; return $this; } } webdriver/lib/Remote/RemoteTouchScreen.php 0000644 00000007404 15021223212 0014640 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen; use Facebook\WebDriver\WebDriverElement; /** * Execute touch commands for RemoteWebDriver. */ class RemoteTouchScreen implements WebDriverTouchScreen { /** * @var RemoteExecuteMethod */ private $executor; public function __construct(RemoteExecuteMethod $executor) { $this->executor = $executor; } /** * @return RemoteTouchScreen The instance. */ public function tap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_SINGLE_TAP, ['element' => $element->getID()] ); return $this; } /** * @return RemoteTouchScreen The instance. */ public function doubleTap(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_DOUBLE_TAP, ['element' => $element->getID()] ); return $this; } /** * @param int $x * @param int $y * * @return RemoteTouchScreen The instance. */ public function down($x, $y) { $this->executor->execute(DriverCommand::TOUCH_DOWN, [ 'x' => $x, 'y' => $y, ]); return $this; } /** * @param int $xspeed * @param int $yspeed * * @return RemoteTouchScreen The instance. */ public function flick($xspeed, $yspeed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xspeed' => $xspeed, 'yspeed' => $yspeed, ]); return $this; } /** * @param int $xoffset * @param int $yoffset * @param int $speed * * @return RemoteTouchScreen The instance. */ public function flickFromElement(WebDriverElement $element, $xoffset, $yoffset, $speed) { $this->executor->execute(DriverCommand::TOUCH_FLICK, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, 'element' => $element->getID(), 'speed' => $speed, ]); return $this; } /** * @return RemoteTouchScreen The instance. */ public function longPress(WebDriverElement $element) { $this->executor->execute( DriverCommand::TOUCH_LONG_PRESS, ['element' => $element->getID()] ); return $this; } /** * @param int $x * @param int $y * * @return RemoteTouchScreen The instance. */ public function move($x, $y) { $this->executor->execute(DriverCommand::TOUCH_MOVE, [ 'x' => $x, 'y' => $y, ]); return $this; } /** * @param int $xoffset * @param int $yoffset * * @return RemoteTouchScreen The instance. */ public function scroll($xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } /** * @param int $xoffset * @param int $yoffset * * @return RemoteTouchScreen The instance. */ public function scrollFromElement(WebDriverElement $element, $xoffset, $yoffset) { $this->executor->execute(DriverCommand::TOUCH_SCROLL, [ 'element' => $element->getID(), 'xoffset' => $xoffset, 'yoffset' => $yoffset, ]); return $this; } /** * @param int $x * @param int $y * * @return RemoteTouchScreen The instance. */ public function up($x, $y) { $this->executor->execute(DriverCommand::TOUCH_UP, [ 'x' => $x, 'y' => $y, ]); return $this; } } webdriver/lib/Remote/WebDriverBrowserType.php 0000644 00000002250 15021223212 0015333 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; /** * All the browsers supported by selenium. * * @codeCoverageIgnore */ class WebDriverBrowserType { public const FIREFOX = 'firefox'; public const FIREFOX_PROXY = 'firefoxproxy'; public const FIREFOX_CHROME = 'firefoxchrome'; public const GOOGLECHROME = 'googlechrome'; public const SAFARI = 'safari'; public const SAFARI_PROXY = 'safariproxy'; public const OPERA = 'opera'; public const MICROSOFT_EDGE = 'MicrosoftEdge'; public const IEXPLORE = 'iexplore'; public const IEXPLORE_PROXY = 'iexploreproxy'; public const CHROME = 'chrome'; public const KONQUEROR = 'konqueror'; public const MOCK = 'mock'; public const IE_HTA = 'iehta'; public const ANDROID = 'android'; public const HTMLUNIT = 'htmlunit'; public const IE = 'internet explorer'; public const IPHONE = 'iphone'; public const IPAD = 'iPad'; /** * @deprecated PhantomJS is no longer developed and its support will be removed in next major version. * Use headless Chrome or Firefox instead. */ public const PHANTOMJS = 'phantomjs'; private function __construct() { } } webdriver/lib/Remote/LocalFileDetector.php 0000644 00000000504 15021223212 0014560 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; class LocalFileDetector implements FileDetector { /** * @param string $file * * @return null|string */ public function getLocalFile($file) { if (is_file($file)) { return realpath($file); } return null; } } webdriver/lib/Remote/RemoteKeyboard.php 0000644 00000005233 15021223212 0014154 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\WebDriver; use Facebook\WebDriver\WebDriverKeyboard; use Facebook\WebDriver\WebDriverKeys; /** * Execute keyboard commands for RemoteWebDriver. */ class RemoteKeyboard implements WebDriverKeyboard { /** @var RemoteExecuteMethod */ private $executor; /** @var WebDriver */ private $driver; /** @var bool */ private $isW3cCompliant; /** * @param bool $isW3cCompliant */ public function __construct(RemoteExecuteMethod $executor, WebDriver $driver, $isW3cCompliant = false) { $this->executor = $executor; $this->driver = $driver; $this->isW3cCompliant = $isW3cCompliant; } /** * Send keys to active element * @param string|array $keys * @return $this */ public function sendKeys($keys) { if ($this->isW3cCompliant) { $activeElement = $this->driver->switchTo()->activeElement(); $activeElement->sendKeys($keys); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => WebDriverKeys::encode($keys), ]); } return $this; } /** * Press a modifier key * * @see WebDriverKeys * @param string $key * @return $this */ public function pressKey($key) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'key', 'id' => 'keyboard', 'actions' => [['type' => 'keyDown', 'value' => $key]], ], ], ]); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); } return $this; } /** * Release a modifier key * * @see WebDriverKeys * @param string $key * @return $this */ public function releaseKey($key) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'key', 'id' => 'keyboard', 'actions' => [['type' => 'keyUp', 'value' => $key]], ], ], ]); } else { $this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [ 'value' => [(string) $key], ]); } return $this; } } webdriver/lib/Remote/UselessFileDetector.php 0000644 00000000254 15021223212 0015153 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; class UselessFileDetector implements FileDetector { public function getLocalFile($file) { return null; } } webdriver/lib/Remote/RemoteTargetLocator.php 0000644 00000011265 15021223212 0015170 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\WebDriverAlert; use Facebook\WebDriver\WebDriverElement; use Facebook\WebDriver\WebDriverTargetLocator; /** * Used to locate a given frame or window for RemoteWebDriver. */ class RemoteTargetLocator implements WebDriverTargetLocator { /** @var RemoteExecuteMethod */ protected $executor; /** @var RemoteWebDriver */ protected $driver; /** @var bool */ protected $isW3cCompliant; public function __construct(RemoteExecuteMethod $executor, RemoteWebDriver $driver, $isW3cCompliant = false) { $this->executor = $executor; $this->driver = $driver; $this->isW3cCompliant = $isW3cCompliant; } /** * @return RemoteWebDriver */ public function defaultContent() { $params = ['id' => null]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } /** * @param WebDriverElement|null|int|string $frame The WebDriverElement, the id or the name of the frame. * When null, switch to the current top-level browsing context When int, switch to the WindowProxy identified * by the value. When an Element, switch to that Element. * @return RemoteWebDriver */ public function frame($frame) { if ($this->isW3cCompliant) { if ($frame instanceof WebDriverElement) { $id = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $frame->getID()]; } elseif ($frame === null) { $id = null; } elseif (is_int($frame)) { $id = $frame; } else { throw LogicException::forError( 'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null' ); } } else { if ($frame instanceof WebDriverElement) { $id = ['ELEMENT' => $frame->getID()]; } elseif ($frame === null) { $id = null; } elseif (is_int($frame)) { $id = $frame; } else { $id = (string) $frame; } } $params = ['id' => $id]; $this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params); return $this->driver; } /** * Switch to the parent iframe. * * @return RemoteWebDriver This driver focused on the parent frame */ public function parent() { $this->executor->execute(DriverCommand::SWITCH_TO_PARENT_FRAME, []); return $this->driver; } /** * @param string $handle The handle of the window to be focused on. * @return RemoteWebDriver */ public function window($handle) { if ($this->isW3cCompliant) { $params = ['handle' => (string) $handle]; } else { $params = ['name' => (string) $handle]; } $this->executor->execute(DriverCommand::SWITCH_TO_WINDOW, $params); return $this->driver; } /** * Creates a new browser window and switches the focus for future commands of this driver to the new window. * * @see https://w3c.github.io/webdriver/#new-window * @param string $windowType The type of a new browser window that should be created. One of [tab, window]. * The created window is not guaranteed to be of the requested type; if the driver does not support the requested * type, a new browser window will be created of whatever type the driver does support. * @throws LogicException * @return RemoteWebDriver This driver focused on the given window */ public function newWindow($windowType = self::WINDOW_TYPE_TAB) { if ($windowType !== self::WINDOW_TYPE_TAB && $windowType !== self::WINDOW_TYPE_WINDOW) { throw LogicException::forError('Window type must by either "tab" or "window"'); } if (!$this->isW3cCompliant) { throw LogicException::forError('New window is only supported in W3C mode'); } $response = $this->executor->execute(DriverCommand::NEW_WINDOW, ['type' => $windowType]); $this->window($response['handle']); return $this->driver; } public function alert() { return new WebDriverAlert($this->executor); } /** * @return RemoteWebElement */ public function activeElement() { $response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []); $method = new RemoteExecuteMethod($this->driver); return new RemoteWebElement($method, JsonWireCompat::getElement($response), $this->isW3cCompliant); } } webdriver/lib/Remote/RemoteMouse.php 0000644 00000017605 15021223212 0013512 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; use Facebook\WebDriver\WebDriverMouse; /** * Execute mouse commands for RemoteWebDriver. */ class RemoteMouse implements WebDriverMouse { /** @internal */ public const BUTTON_LEFT = 0; /** @internal */ public const BUTTON_MIDDLE = 1; /** @internal */ public const BUTTON_RIGHT = 2; /** * @var RemoteExecuteMethod */ private $executor; /** * @var bool */ private $isW3cCompliant; /** * @param bool $isW3cCompliant */ public function __construct(RemoteExecuteMethod $executor, $isW3cCompliant = false) { $this->executor = $executor; $this->isW3cCompliant = $isW3cCompliant; } /** * @return RemoteMouse */ public function click(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $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' => self::BUTTON_LEFT, ]); return $this; } /** * @return RemoteMouse */ public function contextClick(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerDown', 'button' => self::BUTTON_RIGHT, ], [ 'type' => 'pointerUp', 'button' => self::BUTTON_RIGHT, ], ]), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::CLICK, [ 'button' => self::BUTTON_RIGHT, ]); return $this; } /** * @return RemoteMouse */ public function doubleClick(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $clickActions = $this->createClickActions(); $moveAction = $where === null ? [] : [$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); return $this; } /** * @return RemoteMouse */ public function mouseDown(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => [ $this->createMoveAction($where), [ 'type' => 'pointerDown', 'button' => self::BUTTON_LEFT, ], ], ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_DOWN); return $this; } /** * @param int|null $x_offset * @param int|null $y_offset * * @return RemoteMouse */ public function mouseMove( WebDriverCoordinates $where = null, $x_offset = null, $y_offset = null ) { if ($this->isW3cCompliant) { $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(); } if ($x_offset !== null) { $params['xoffset'] = $x_offset; } if ($y_offset !== null) { $params['yoffset'] = $y_offset; } $this->executor->execute(DriverCommand::MOVE_TO, $params); return $this; } /** * @return RemoteMouse */ public function mouseUp(WebDriverCoordinates $where = null) { if ($this->isW3cCompliant) { $moveAction = $where ? [$this->createMoveAction($where)] : []; $this->executor->execute(DriverCommand::ACTIONS, [ 'actions' => [ [ 'type' => 'pointer', 'id' => 'mouse', 'parameters' => ['pointerType' => 'mouse'], 'actions' => array_merge($moveAction, [ [ 'type' => 'pointerUp', 'button' => self::BUTTON_LEFT, ], ]), ], ], ]); return $this; } $this->moveIfNeeded($where); $this->executor->execute(DriverCommand::MOUSE_UP); return $this; } protected function moveIfNeeded(WebDriverCoordinates $where = null) { if ($where) { $this->mouseMove($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' => 100, // to simulate human delay 'x' => $x_offset ?? 0, 'y' => $y_offset ?? 0, ]; if ($where !== null) { $move_action['origin'] = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $where->getAuxiliary()]; } else { $move_action['origin'] = 'pointer'; } return $move_action; } /** * @return array */ private function createClickActions() { return [ [ 'type' => 'pointerDown', 'button' => self::BUTTON_LEFT, ], [ 'type' => 'pointerUp', 'button' => self::BUTTON_LEFT, ], ]; } } webdriver/lib/Remote/DriverCommand.php 0000644 00000016300 15021223212 0013767 0 ustar 00 <?php namespace Facebook\WebDriver\Remote; /** * This list of command defined in the WebDriver json wire protocol. * * @codeCoverageIgnore */ class DriverCommand { public const GET_ALL_SESSIONS = 'getAllSessions'; public const GET_CAPABILITIES = 'getCapabilities'; public const NEW_SESSION = 'newSession'; public const STATUS = 'status'; public const CLOSE = 'close'; public const QUIT = 'quit'; public const GET = 'get'; public const GO_BACK = 'goBack'; public const GO_FORWARD = 'goForward'; public const REFRESH = 'refresh'; public const ADD_COOKIE = 'addCookie'; public const GET_ALL_COOKIES = 'getCookies'; public const DELETE_COOKIE = 'deleteCookie'; public const DELETE_ALL_COOKIES = 'deleteAllCookies'; public const FIND_ELEMENT = 'findElement'; public const FIND_ELEMENTS = 'findElements'; public const FIND_CHILD_ELEMENT = 'findChildElement'; public const FIND_CHILD_ELEMENTS = 'findChildElements'; public const CLEAR_ELEMENT = 'clearElement'; public const CLICK_ELEMENT = 'clickElement'; public const SEND_KEYS_TO_ELEMENT = 'sendKeysToElement'; public const SEND_KEYS_TO_ACTIVE_ELEMENT = 'sendKeysToActiveElement'; public const SUBMIT_ELEMENT = 'submitElement'; public const UPLOAD_FILE = 'uploadFile'; public const GET_CURRENT_WINDOW_HANDLE = 'getCurrentWindowHandle'; public const GET_WINDOW_HANDLES = 'getWindowHandles'; public const GET_CURRENT_CONTEXT_HANDLE = 'getCurrentContextHandle'; public const GET_CONTEXT_HANDLES = 'getContextHandles'; // Switching between to window/frame/iframe public const SWITCH_TO_WINDOW = 'switchToWindow'; public const SWITCH_TO_CONTEXT = 'switchToContext'; public const SWITCH_TO_FRAME = 'switchToFrame'; public const SWITCH_TO_PARENT_FRAME = 'switchToParentFrame'; public const GET_ACTIVE_ELEMENT = 'getActiveElement'; // Information of the page public const GET_CURRENT_URL = 'getCurrentUrl'; public const GET_PAGE_SOURCE = 'getPageSource'; public const GET_TITLE = 'getTitle'; // Javascript API public const EXECUTE_SCRIPT = 'executeScript'; public const EXECUTE_ASYNC_SCRIPT = 'executeAsyncScript'; // API getting information from an element. public const GET_ELEMENT_TEXT = 'getElementText'; public const GET_ELEMENT_TAG_NAME = 'getElementTagName'; public const IS_ELEMENT_SELECTED = 'isElementSelected'; public const IS_ELEMENT_ENABLED = 'isElementEnabled'; public const IS_ELEMENT_DISPLAYED = 'isElementDisplayed'; public const GET_ELEMENT_LOCATION = 'getElementLocation'; public const GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = 'getElementLocationOnceScrolledIntoView'; public const GET_ELEMENT_SIZE = 'getElementSize'; public const GET_ELEMENT_ATTRIBUTE = 'getElementAttribute'; public const GET_ELEMENT_VALUE_OF_CSS_PROPERTY = 'getElementValueOfCssProperty'; public const ELEMENT_EQUALS = 'elementEquals'; public const SCREENSHOT = 'screenshot'; // Alert API public const ACCEPT_ALERT = 'acceptAlert'; public const DISMISS_ALERT = 'dismissAlert'; public const GET_ALERT_TEXT = 'getAlertText'; public const SET_ALERT_VALUE = 'setAlertValue'; // Timeout API public const SET_TIMEOUT = 'setTimeout'; public const IMPLICITLY_WAIT = 'implicitlyWait'; public const SET_SCRIPT_TIMEOUT = 'setScriptTimeout'; /** @deprecated */ public const EXECUTE_SQL = 'executeSQL'; public const GET_LOCATION = 'getLocation'; public const SET_LOCATION = 'setLocation'; public const GET_APP_CACHE = 'getAppCache'; public const GET_APP_CACHE_STATUS = 'getStatus'; public const CLEAR_APP_CACHE = 'clearAppCache'; public const IS_BROWSER_ONLINE = 'isBrowserOnline'; public const SET_BROWSER_ONLINE = 'setBrowserOnline'; // Local storage public const GET_LOCAL_STORAGE_ITEM = 'getLocalStorageItem'; public const GET_LOCAL_STORAGE_KEYS = 'getLocalStorageKeys'; public const SET_LOCAL_STORAGE_ITEM = 'setLocalStorageItem'; public const REMOVE_LOCAL_STORAGE_ITEM = 'removeLocalStorageItem'; public const CLEAR_LOCAL_STORAGE = 'clearLocalStorage'; public const GET_LOCAL_STORAGE_SIZE = 'getLocalStorageSize'; // Session storage public const GET_SESSION_STORAGE_ITEM = 'getSessionStorageItem'; public const GET_SESSION_STORAGE_KEYS = 'getSessionStorageKey'; public const SET_SESSION_STORAGE_ITEM = 'setSessionStorageItem'; public const REMOVE_SESSION_STORAGE_ITEM = 'removeSessionStorageItem'; public const CLEAR_SESSION_STORAGE = 'clearSessionStorage'; public const GET_SESSION_STORAGE_SIZE = 'getSessionStorageSize'; // Screen orientation public const SET_SCREEN_ORIENTATION = 'setScreenOrientation'; public const GET_SCREEN_ORIENTATION = 'getScreenOrientation'; // These belong to the Advanced user interactions - an element is optional for these commands. public const CLICK = 'mouseClick'; public const DOUBLE_CLICK = 'mouseDoubleClick'; public const MOUSE_DOWN = 'mouseButtonDown'; public const MOUSE_UP = 'mouseButtonUp'; public const MOVE_TO = 'mouseMoveTo'; // Those allow interactions with the Input Methods installed on the system. public const IME_GET_AVAILABLE_ENGINES = 'imeGetAvailableEngines'; public const IME_GET_ACTIVE_ENGINE = 'imeGetActiveEngine'; public const IME_IS_ACTIVATED = 'imeIsActivated'; public const IME_DEACTIVATE = 'imeDeactivate'; public const IME_ACTIVATE_ENGINE = 'imeActivateEngine'; // These belong to the Advanced Touch API public const TOUCH_SINGLE_TAP = 'touchSingleTap'; public const TOUCH_DOWN = 'touchDown'; public const TOUCH_UP = 'touchUp'; public const TOUCH_MOVE = 'touchMove'; public const TOUCH_SCROLL = 'touchScroll'; public const TOUCH_DOUBLE_TAP = 'touchDoubleTap'; public const TOUCH_LONG_PRESS = 'touchLongPress'; public const TOUCH_FLICK = 'touchFlick'; // Window API (beta) public const SET_WINDOW_SIZE = 'setWindowSize'; public const SET_WINDOW_POSITION = 'setWindowPosition'; public const GET_WINDOW_SIZE = 'getWindowSize'; public const GET_WINDOW_POSITION = 'getWindowPosition'; public const MAXIMIZE_WINDOW = 'maximizeWindow'; public const FULLSCREEN_WINDOW = 'fullscreenWindow'; // Logging API public const GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes'; public const GET_LOG = 'getLog'; public const GET_SESSION_LOGS = 'getSessionLogs'; // Mobile API public const GET_NETWORK_CONNECTION = 'getNetworkConnection'; public const SET_NETWORK_CONNECTION = 'setNetworkConnection'; // Custom command public const CUSTOM_COMMAND = 'customCommand'; // W3C specific public const ACTIONS = 'actions'; public const GET_ELEMENT_PROPERTY = 'getElementProperty'; public const GET_NAMED_COOKIE = 'getNamedCookie'; public const NEW_WINDOW = 'newWindow'; public const TAKE_ELEMENT_SCREENSHOT = 'takeElementScreenshot'; public const MINIMIZE_WINDOW = 'minimizeWindow'; public const GET_ELEMENT_SHADOW_ROOT = 'getElementShadowRoot'; public const FIND_ELEMENT_FROM_SHADOW_ROOT = 'findElementFromShadowRoot'; public const FIND_ELEMENTS_FROM_SHADOW_ROOT = 'findElementsFromShadowRoot'; private function __construct() { } } webdriver/lib/WebDriverMouse.php 0000644 00000001673 15021223212 0012713 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates; /** * Interface representing basic mouse operations. */ interface WebDriverMouse { /** * @return WebDriverMouse */ public function click(WebDriverCoordinates $where); /** * @return WebDriverMouse */ public function contextClick(WebDriverCoordinates $where); /** * @return WebDriverMouse */ public function doubleClick(WebDriverCoordinates $where); /** * @return WebDriverMouse */ public function mouseDown(WebDriverCoordinates $where); /** * @param int $x_offset * @param int $y_offset * @return WebDriverMouse */ public function mouseMove( WebDriverCoordinates $where, $x_offset = null, $y_offset = null ); /** * @return WebDriverMouse */ public function mouseUp(WebDriverCoordinates $where); } webdriver/lib/WebDriverAlert.php 0000644 00000002510 15021223212 0012661 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\DriverCommand; use Facebook\WebDriver\Remote\ExecuteMethod; /** * An abstraction allowing the driver to manipulate the javascript alerts */ class WebDriverAlert { /** * @var ExecuteMethod */ protected $executor; public function __construct(ExecuteMethod $executor) { $this->executor = $executor; } /** * Accept alert * * @return WebDriverAlert The instance. */ public function accept() { $this->executor->execute(DriverCommand::ACCEPT_ALERT); return $this; } /** * Dismiss alert * * @return WebDriverAlert The instance. */ public function dismiss() { $this->executor->execute(DriverCommand::DISMISS_ALERT); return $this; } /** * Get alert text * * @return string */ public function getText() { return $this->executor->execute(DriverCommand::GET_ALERT_TEXT); } /** * Send keystrokes to javascript prompt() dialog * * @param string $value * @return WebDriverAlert */ public function sendKeys($value) { $this->executor->execute( DriverCommand::SET_ALERT_VALUE, ['text' => $value] ); return $this; } } webdriver/lib/WebDriverSelectInterface.php 0000644 00000007674 15021223212 0014672 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnsupportedOperationException; /** * Models an element of select type, providing helper methods to select and deselect options. */ interface WebDriverSelectInterface { /** * @return bool Whether this select element support selecting multiple options. */ public function isMultiple(); /** * @return WebDriverElement[] All options belonging to this select tag. */ public function getOptions(); /** * @return WebDriverElement[] All selected options belonging to this select tag. */ public function getAllSelectedOptions(); /** * @throws NoSuchElementException * * @return WebDriverElement The first selected option in this select tag (or the currently selected option in a * normal select) */ public function getFirstSelectedOption(); /** * Select the option at the given index. * * @param int $index The index of the option. (0-based) * * @throws NoSuchElementException */ 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: * * `<option value="foo">Bar</option>` * * @param string $value The value to match against. * * @throws NoSuchElementException */ public function selectByValue($value); /** * Select all options that display text matching the argument. That is, when given "Bar" this would * select an option like: * * `<option value="foo">Bar</option>` * * @param string $text The visible text to match against. * * @throws NoSuchElementException */ 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: * * `<option value="bar">Foo Bar Baz</option>` * * @param string $text The visible text to match against. * * @throws NoSuchElementException */ public function selectByVisiblePartialText($text); /** * Deselect all options in multiple select tag. * * @throws UnsupportedOperationException If the SELECT does not support multiple selections */ public function deselectAll(); /** * Deselect the option at the given index. * * @param int $index The index of the option. (0-based) * @throws UnsupportedOperationException If the SELECT does not support multiple selections */ 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: * * `<option value="foo">Bar</option>` * * @param string $value The value to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections */ public function deselectByValue($value); /** * Deselect all options that display text matching the argument. That is, when given "Bar" this would * deselect an option like: * * `<option value="foo">Bar</option>` * * @param string $text The visible text to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections */ public function deselectByVisibleText($text); /** * Deselect all options that display text matching the argument. That is, when given "Bar" this would * deselect an option like: * * `<option value="foo">Foo Bar Baz</option>` * * @param string $text The visible text to match against. * @throws UnsupportedOperationException If the SELECT does not support multiple selections */ public function deselectByVisiblePartialText($text); } webdriver/lib/WebDriverKeyboard.php 0000644 00000001006 15021223212 0013351 0 ustar 00 <?php namespace Facebook\WebDriver; interface WebDriverKeyboard { /** * Send a sequence of keys. * * @param string $keys * @return $this */ public function sendKeys($keys); /** * Press a key * * @see WebDriverKeys * @param string $key * @return $this */ public function pressKey($key); /** * Release a key * * @see WebDriverKeys * @param string $key * @return $this */ public function releaseKey($key); } webdriver/lib/WebDriverCommandExecutor.php 0000644 00000000507 15021223212 0014713 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Remote\WebDriverCommand; use Facebook\WebDriver\Remote\WebDriverResponse; /** * Interface for all command executor. */ interface WebDriverCommandExecutor { /** * @return WebDriverResponse */ public function execute(WebDriverCommand $command); } webdriver/lib/WebDriver.php 0000755 00000007112 15021223212 0011677 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen; /** * The interface for WebDriver. */ interface WebDriver extends WebDriverSearchContext { /** * Close the current window. * * @return WebDriver The current instance. */ public function close(); /** * Load a new web page in the current browser window. * * @param string $url * @return WebDriver The current instance. */ public function get($url); /** * Get a string representing the current URL that the browser is looking at. * * @return string The current URL. */ public function getCurrentURL(); /** * Get the source of the last loaded page. * * @return string The current page source. */ public function getPageSource(); /** * Get the title of the current page. * * @return string The title of the current page. */ public function getTitle(); /** * Return an opaque handle to this window that uniquely identifies it within * this driver instance. * * @return string The current window handle. */ public function getWindowHandle(); /** * Get all window handles available to the current session. * * @return array An array of string containing all available window handles. */ public function getWindowHandles(); /** * Quits this driver, closing every associated window. */ public function quit(); /** * Take a screenshot of the current page. * * @param string $save_as The path of the screenshot to be saved. * @return string The screenshot in PNG format. */ 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 * @return WebDriverWait */ public function wait( $timeout_in_second = 30, $interval_in_millisecond = 250 ); /** * An abstraction for managing stuff you would do in a browser menu. For * example, adding and deleting cookies. * * @return WebDriverOptions */ public function manage(); /** * An abstraction allowing the driver to access the browser's history and to * navigate to a given URL. * * @return WebDriverNavigationInterface * @see WebDriverNavigation */ public function navigate(); /** * Switch to a different window or frame. * * @return WebDriverTargetLocator * @see WebDriverTargetLocator */ public function switchTo(); // TODO: Add in next major release (BC) ///** // * @return WebDriverTouchScreen // */ //public function getTouch(); /** * @param string $name * @param array $params * @return mixed */ public function execute($name, $params); // TODO: Add in next major release (BC) ///** // * Execute custom commands on remote end. // * For example vendor-specific commands or other commands not implemented by php-webdriver. // * // * @see https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands // * @param string $endpointUrl // * @param string $method // * @param array $params // * @return mixed|null // */ //public function executeCustomCommand($endpointUrl, $method = 'GET', $params = []); } webdriver/lib/AbstractWebDriverCheckboxOrRadio.php 0000644 00000016132 15021223212 0016311 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\InvalidElementStateException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\UnexpectedTagNameException; use Facebook\WebDriver\Support\XPathEscaper; /** * Provides helper methods for checkboxes and radio buttons. */ abstract class AbstractWebDriverCheckboxOrRadio implements WebDriverSelectInterface { /** @var WebDriverElement */ protected $element; /** @var string */ protected $type; /** @var string */ protected $name; public function __construct(WebDriverElement $element) { $tagName = $element->getTagName(); if ($tagName !== 'input') { throw new UnexpectedTagNameException('input', $tagName); } $this->name = $element->getAttribute('name'); if ($this->name === null) { throw new InvalidElementStateException('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', $this->type === 'radio' ? '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 === '' || $formId === null) { return $form->findElements(WebDriverBy::xpath( sprintf('.//input[@name = %s%s]', 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 )) ); } /** * 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(); } } } webdriver/lib/scripts/isElementDisplayed.js 0000644 00000022564 15021223212 0015113 0 ustar 00 /* * Imported from WebdriverIO project. * https://github.com/webdriverio/webdriverio/blob/main/packages/webdriverio/src/scripts/isElementDisplayed.ts * * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /** * check if element is visible * @param {HTMLElement} elem element to check * @return {Boolean} true if element is within viewport */ function isElementDisplayed(element) { function nodeIsElement(node) { if (!node) { return false; } switch (node.nodeType) { case Node.ELEMENT_NODE: case Node.DOCUMENT_NODE: case Node.DOCUMENT_FRAGMENT_NODE: return true; default: return false; } } function parentElementForElement(element) { if (!element) { return null; } return enclosingNodeOrSelfMatchingPredicate(element.parentNode, nodeIsElement); } function enclosingNodeOrSelfMatchingPredicate(targetNode, predicate) { for (let node = targetNode; node && node !== targetNode.ownerDocument; node = node.parentNode) if (predicate(node)) { return node; } return null; } function enclosingElementOrSelfMatchingPredicate(targetElement, predicate) { for (let element = targetElement; element && element !== targetElement.ownerDocument; element = parentElementForElement(element)) if (predicate(element)) { return element; } return null; } function cascadedStylePropertyForElement(element, property) { if (!element || !property) { return null; } // if document-fragment, skip it and use element.host instead. This happens // when the element is inside a shadow root. // window.getComputedStyle errors on document-fragment. if (element instanceof ShadowRoot) { element = element.host; } let computedStyle = window.getComputedStyle(element); let computedStyleProperty = computedStyle.getPropertyValue(property); if (computedStyleProperty && computedStyleProperty !== 'inherit') { return computedStyleProperty; } // Ideally getPropertyValue would return the 'used' or 'actual' value, but // it doesn't for legacy reasons. So we need to do our own poor man's cascade. // Fall back to the first non-'inherit' value found in an ancestor. // In any case, getPropertyValue will not return 'initial'. // FIXME: will this incorrectly inherit non-inheritable CSS properties? // I think all important non-inheritable properties (width, height, etc.) // for our purposes here are specially resolved, so this may not be an issue. // Specification is here: https://drafts.csswg.org/cssom/#resolved-values let parentElement = parentElementForElement(element); return cascadedStylePropertyForElement(parentElement, property); } function elementSubtreeHasNonZeroDimensions(element) { let boundingBox = element.getBoundingClientRect(); if (boundingBox.width > 0 && boundingBox.height > 0) { return true; } // Paths can have a zero width or height. Treat them as shown if the stroke width is positive. if (element.tagName.toUpperCase() === 'PATH' && boundingBox.width + boundingBox.height > 0) { let strokeWidth = cascadedStylePropertyForElement(element, 'stroke-width'); return !!strokeWidth && (parseInt(strokeWidth, 10) > 0); } let cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow'); if (cascadedOverflow === 'hidden') { return false; } // If the container's overflow is not hidden and it has zero size, consider the // container to have non-zero dimensions if a child node has non-zero dimensions. return Array.from(element.childNodes).some((childNode) => { if (childNode.nodeType === Node.TEXT_NODE) { return true; } if (nodeIsElement(childNode)) { return elementSubtreeHasNonZeroDimensions(childNode); } return false; }); } function elementOverflowsContainer(element) { let cascadedOverflow = cascadedStylePropertyForElement(element, 'overflow'); if (cascadedOverflow !== 'hidden') { return false; } // FIXME: this needs to take into account the scroll position of the element, // the display modes of it and its ancestors, and the container it overflows. // See Selenium's bot.dom.getOverflowState atom for an exhaustive list of edge cases. return true; } function isElementSubtreeHiddenByOverflow(element) { if (!element) { return false; } if (!elementOverflowsContainer(element)) { return false; } if (!element.childNodes.length) { return false; } // This element's subtree is hidden by overflow if all child subtrees are as well. return Array.from(element.childNodes).every((childNode) => { // Returns true if the child node is overflowed or otherwise hidden. // Base case: not an element, has zero size, scrolled out, or doesn't overflow container. // Visibility of text nodes is controlled by parent if (childNode.nodeType === Node.TEXT_NODE) { return false; } if (!nodeIsElement(childNode)) { return true; } if (!elementSubtreeHasNonZeroDimensions(childNode)) { return true; } // Recurse. return isElementSubtreeHiddenByOverflow(childNode); }); } // walk up the tree testing for a shadow root function isElementInsideShadowRoot(element) { if (!element) { return false; } if (element.parentNode && element.parentNode.host) { return true; } return isElementInsideShadowRoot(element.parentNode); } // This is a partial reimplementation of Selenium's "element is displayed" algorithm. // When the W3C specification's algorithm stabilizes, we should implement that. // If this command is misdirected to the wrong document (and is NOT inside a shadow root), treat it as not shown. if (!isElementInsideShadowRoot(element) && !document.contains(element)) { return false; } // Special cases for specific tag names. switch (element.tagName.toUpperCase()) { case 'BODY': return true; case 'SCRIPT': case 'NOSCRIPT': return false; case 'OPTGROUP': case 'OPTION': { // Option/optgroup are considered shown if the containing <select> is shown. let enclosingSelectElement = enclosingNodeOrSelfMatchingPredicate(element, (e) => e.tagName.toUpperCase() === 'SELECT'); return isElementDisplayed(enclosingSelectElement); } case 'INPUT': // <input type="hidden"> is considered not shown. if (element.type === 'hidden') { return false; } break; // case 'MAP': // FIXME: Selenium has special handling for <map> elements. We don't do anything now. default: break; } if (cascadedStylePropertyForElement(element, 'visibility') !== 'visible') { return false; } let hasAncestorWithZeroOpacity = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { return Number(cascadedStylePropertyForElement(e, 'opacity')) === 0; }); let hasAncestorWithDisplayNone = !!enclosingElementOrSelfMatchingPredicate(element, (e) => { return cascadedStylePropertyForElement(e, 'display') === 'none'; }); if (hasAncestorWithZeroOpacity || hasAncestorWithDisplayNone) { return false; } if (!elementSubtreeHasNonZeroDimensions(element)) { return false; } if (isElementSubtreeHiddenByOverflow(element)) { return false; } return true; } webdriver/lib/WebDriverHasInputDevices.php 0000644 00000000511 15021223212 0014647 0 ustar 00 <?php namespace Facebook\WebDriver; /** * Interface implemented by each driver that allows access to the input devices. */ interface WebDriverHasInputDevices { /** * @return WebDriverKeyboard */ public function getKeyboard(); /** * @return WebDriverMouse */ public function getMouse(); } webdriver/lib/WebDriverDispatcher.php 0000644 00000002665 15021223212 0013713 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Support\Events\EventFiringWebDriver; class WebDriverDispatcher { /** * @var array */ protected $listeners = []; /** * @var EventFiringWebDriver */ protected $driver; /** * this is needed so that EventFiringWebElement can pass the driver to the * exception handling * * @return $this */ public function setDefaultDriver(EventFiringWebDriver $driver) { $this->driver = $driver; return $this; } /** * @return null|EventFiringWebDriver */ public function getDefaultDriver() { return $this->driver; } /** * @return $this */ public function register(WebDriverEventListener $listener) { $this->listeners[] = $listener; return $this; } /** * @return $this */ public function unregister(WebDriverEventListener $listener) { $key = array_search($listener, $this->listeners, true); if ($key !== false) { unset($this->listeners[$key]); } return $this; } /** * @param mixed $method * @param mixed $arguments * @return $this */ public function dispatch($method, $arguments) { foreach ($this->listeners as $listener) { call_user_func_array([$listener, $method], $arguments); } return $this; } } webdriver/lib/WebDriverExpectedCondition.php 0000644 00000050553 15021223212 0015234 0 ustar 00 <?php namespace Facebook\WebDriver; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\NoSuchAlertException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\NoSuchFrameException; use Facebook\WebDriver\Exception\StaleElementReferenceException; /** * Canned ExpectedConditions which are generally useful within webdriver tests. * * @see WebDriverWait */ class WebDriverExpectedCondition { /** * A callable function to be executed by WebDriverWait. It should return * a truthy value, mostly boolean or a WebDriverElement, on success. * @var callable */ private $apply; protected function __construct(callable $apply) { $this->apply = $apply; } /** * @return callable A callable function to be executed by WebDriverWait */ public function getApply() { return $this->apply; } /** * An expectation for checking the title of a page. * * @param string $title The expected title, which must be an exact match. * @return static Condition returns whether current page title equals given string. */ public static function titleIs($title) { return new static( function (WebDriver $driver) use ($title) { return $title === $driver->getTitle(); } ); } /** * An expectation for checking substring of a page Title. * * @param string $title The expected substring of Title. * @return static Condition returns whether current page title contains given string. */ public static function titleContains($title) { return new static( function (WebDriver $driver) use ($title) { return mb_strpos($driver->getTitle(), $title) !== false; } ); } /** * An expectation for checking current page title matches the given regular expression. * * @param string $titleRegexp The regular expression to test against. * @return static Condition returns whether current page title matches the regular expression. */ public static function titleMatches($titleRegexp) { return new static( function (WebDriver $driver) use ($titleRegexp) { return (bool) preg_match($titleRegexp, $driver->getTitle()); } ); } /** * An expectation for checking the URL of a page. * * @param string $url The expected URL, which must be an exact match. * @return static Condition returns whether current URL equals given one. */ public static function urlIs($url) { return new static( function (WebDriver $driver) use ($url) { return $url === $driver->getCurrentURL(); } ); } /** * An expectation for checking substring of the URL of a page. * * @param string $url The expected substring of the URL * @return static Condition returns whether current URL contains given string. */ public static function urlContains($url) { return new static( function (WebDriver $driver) use ($url) { return mb_strpos($driver->getCurrentURL(), $url) !== false; } ); } /** * An expectation for checking current page URL matches the given regular expression. * * @param string $urlRegexp The regular expression to test against. * @return static Condition returns whether current URL matches the regular expression. */ public static function urlMatches($urlRegexp) { return new static( function (WebDriver $driver) use ($urlRegexp) { return (bool) preg_match($urlRegexp, $driver->getCurrentURL()); } ); } /** * An expectation for checking that an element is present on the DOM of a page. * This does not necessarily mean that the element is visible. * * @param WebDriverBy $by The locator used to find the element. * @return static Condition returns the WebDriverElement which is located. */ public static function presenceOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { return $driver->findElement($by); } catch (NoSuchElementException $e) { return false; } } ); } /** * 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 static Condition return an array of WebDriverElement once they are located. */ public static function presenceOfAllElementsLocatedBy(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { $elements = $driver->findElements($by); return count($elements) > 0 ? $elements : null; } ); } /** * An expectation for checking that an element 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 locator used to find the element. * @return static Condition returns the WebDriverElement which is located and visible. */ public static function visibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { $element = $driver->findElement($by); return $element->isDisplayed() ? $element : null; } catch (StaleElementReferenceException $e) { return null; } } ); } /** * 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 static Condition returns the array of WebDriverElement 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 (StaleElementReferenceException $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. * * @param WebDriverElement $element The element to be checked. * @return static Condition returns the same WebDriverElement once it is visible. */ public static function visibilityOf(WebDriverElement $element) { return new static( function () use ($element) { return $element->isDisplayed() ? $element : null; } ); } /** * An expectation for checking if the given text is present in the specified element. * To check exact text match use elementTextIs() condition. * * @codeCoverageIgnore * @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 static Condition returns whether the text is present in the element. */ public static function textToBePresentInElement(WebDriverBy $by, $text) { return self::elementTextContains($by, $text); } /** * An expectation for checking if the given text is present in the specified element. * To check exact text match use elementTextIs() condition. * * @param WebDriverBy $by The locator used to find the element. * @param string $text The text to be presented in the element. * @return static Condition returns whether the partial text is present in the element. */ public static function elementTextContains(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { $element_text = $driver->findElement($by)->getText(); return mb_strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } /** * An expectation for checking if the given text exactly equals the text in specified element. * To check only partial substring of the text use elementTextContains() condition. * * @param WebDriverBy $by The locator used to find the element. * @param string $text The expected text of the element. * @return static Condition returns whether the element has text value equal to given one. */ public static function elementTextIs(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return $driver->findElement($by)->getText() == $text; } catch (StaleElementReferenceException $e) { return null; } } ); } /** * An expectation for checking if the given regular expression matches the text in specified element. * * @param WebDriverBy $by The locator used to find the element. * @param string $regexp The regular expression to test against. * @return static Condition returns whether the element has text value equal to given one. */ public static function elementTextMatches(WebDriverBy $by, $regexp) { return new static( function (WebDriver $driver) use ($by, $regexp) { try { return (bool) preg_match($regexp, $driver->findElement($by)->getText()); } catch (StaleElementReferenceException $e) { return null; } } ); } /** * 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 static 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 static 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) { try { $element_text = $driver->findElement($by)->getAttribute('value'); return mb_strpos($element_text, $text) !== false; } catch (StaleElementReferenceException $e) { return null; } } ); } /** * Expectation for checking if iFrame exists. If iFrame exists switches driver's focus to the iFrame. * * @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 static Condition returns object focused on new frame when frame is found, false otherwise. */ public static function frameToBeAvailableAndSwitchToIt($frame_locator) { return new static( function (WebDriver $driver) use ($frame_locator) { try { return $driver->switchTo()->frame($frame_locator); } catch (NoSuchFrameException $e) { return false; } } ); } /** * 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 static Condition returns whether no visible element located. */ public static function invisibilityOfElementLocated(WebDriverBy $by) { return new static( function (WebDriver $driver) use ($by) { try { return !$driver->findElement($by)->isDisplayed(); } catch (NoSuchElementException|StaleElementReferenceException $e) { return true; } } ); } /** * 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 string $text The text of the element. * @return static Condition returns whether the text is found in the element located. */ public static function invisibilityOfElementWithText(WebDriverBy $by, $text) { return new static( function (WebDriver $driver) use ($by, $text) { try { return !($driver->findElement($by)->getText() === $text); } catch (NoSuchElementException|StaleElementReferenceException $e) { return true; } } ); } /** * 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 static Condition return the WebDriverElement once it is located, visible and clickable. */ public static function elementToBeClickable(WebDriverBy $by) { $visibility_of_element_located = self::visibilityOfElementLocated($by); return new static( function (WebDriver $driver) use ($visibility_of_element_located) { $element = call_user_func( $visibility_of_element_located->getApply(), $driver ); try { if ($element !== null && $element->isEnabled()) { return $element; } return null; } catch (StaleElementReferenceException $e) { return null; } } ); } /** * Wait until an element is no longer attached to the DOM. * * @param WebDriverElement $element The element to wait for. * @return static Condition returns whether the element is still attached to the DOM. */ public static function stalenessOf(WebDriverElement $element) { return new static( function () use ($element) { try { $element->isEnabled(); return false; } catch (StaleElementReferenceException $e) { return true; } } ); } /** * Wrapper for a condition, which allows for elements to update by redrawing. * * This works around the problem of conditions which have two parts: find an element and then check for some * condition on it. For these conditions it is possible that an element is located and then subsequently it is * redrawn on the client. When this happens a StaleElementReferenceException is thrown when the second part of * the condition is checked. * * @param WebDriverExpectedCondition $condition The condition wrapped. * @return static Condition returns the return value of the getApply() of the given condition. */ public static function refreshed(self $condition) { return new static( function (WebDriver $driver) use ($condition) { try { return call_user_func($condition->getApply(), $driver); } catch (StaleElementReferenceException $e) { return null; } } ); } /** * An expectation for checking if the given element is selected. * * @param mixed $element_or_by Either the element or the locator. * @return static Condition returns whether the element is selected. */ public static function elementToBeSelected($element_or_by) { return self::elementSelectionStateToBe( $element_or_by, true ); } /** * An expectation for checking if the given element is selected. * * @param mixed $element_or_by Either the element or the locator. * @param bool $selected The required state. * @return static Condition returns whether the element is selected. */ public static function elementSelectionStateToBe($element_or_by, $selected) { if ($element_or_by instanceof WebDriverElement) { return new static( function () use ($element_or_by, $selected) { return $element_or_by->isSelected() === $selected; } ); } if ($element_or_by instanceof WebDriverBy) { return new static( function (WebDriver $driver) use ($element_or_by, $selected) { try { $element = $driver->findElement($element_or_by); return $element->isSelected() === $selected; } catch (StaleElementReferenceException $e) { return null; } } ); } throw LogicException::forError('Instance of either WebDriverElement or WebDriverBy must be given'); } /** * An expectation for whether an alert() box is present. * * @return static Condition returns WebDriverAlert if alert() is present, null otherwise. */ public static function alertIsPresent() { return new static( function (WebDriver $driver) { try { // Unlike the Java code, we get a WebDriverAlert object regardless // of whether there is an alert. Calling getText() will throw // an exception if it is not really there. $alert = $driver->switchTo()->alert(); $alert->getText(); return $alert; } catch (NoSuchAlertException $e) { return null; } } ); } /** * An expectation checking the number of opened windows. * * @param int $expectedNumberOfWindows * @return static */ public static function numberOfWindowsToBe($expectedNumberOfWindows) { return new static( function (WebDriver $driver) use ($expectedNumberOfWindows) { return count($driver->getWindowHandles()) == $expectedNumberOfWindows; } ); } /** * An expectation with the logical opposite condition of the given condition. * * @param WebDriverExpectedCondition $condition The condition to be negated. * @return mixed The negation of the result of the given condition. */ public static function not(self $condition) { return new static( function (WebDriver $driver) use ($condition) { $result = call_user_func($condition->getApply(), $driver); return !$result; } ); } } webdriver/lib/Firefox/FirefoxProfile.php 0000644 00000022233 15021223212 0014331 0 ustar 00 <?php namespace Facebook\WebDriver\Firefox; use Facebook\WebDriver\Exception\Internal\IOException; use Facebook\WebDriver\Exception\Internal\LogicException; use Facebook\WebDriver\Exception\Internal\RuntimeException; use FilesystemIterator; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ZipArchive; class FirefoxProfile { /** * @var array */ private $preferences = []; /** * @var array */ private $extensions = []; /** * @var array */ private $extensions_datas = []; /** * @var string */ private $rdf_file; /** * @param string $extension The path to the xpi extension. * @return FirefoxProfile */ public function addExtension($extension) { $this->extensions[] = $extension; return $this; } /** * @param string $extension_datas The path to the folder containing the datas to add to the extension * @return FirefoxProfile */ public function addExtensionDatas($extension_datas) { if (!is_dir($extension_datas)) { return null; } $this->extensions_datas[basename($extension_datas)] = $extension_datas; return $this; } /** * @param string $rdf_file The path to the rdf file * @return FirefoxProfile */ public function setRdfFile($rdf_file) { if (!is_file($rdf_file)) { return null; } $this->rdf_file = $rdf_file; return $this; } /** * @param string $key * @param string|bool|int $value * @throws LogicException * @return FirefoxProfile */ public function setPreference($key, $value) { if (is_string($value)) { $value = sprintf('"%s"', $value); } else { if (is_int($value)) { $value = sprintf('%d', $value); } else { if (is_bool($value)) { $value = $value ? 'true' : 'false'; } else { throw LogicException::forError( 'The value of the preference should be either a string, int or bool.' ); } } } $this->preferences[$key] = $value; return $this; } /** * @param mixed $key * @return mixed */ public function getPreference($key) { if (array_key_exists($key, $this->preferences)) { return $this->preferences[$key]; } return null; } /** * @return string */ public function encode() { $temp_dir = $this->createTempDirectory('WebDriverFirefoxProfile'); if (isset($this->rdf_file)) { copy($this->rdf_file, $temp_dir . DIRECTORY_SEPARATOR . 'mimeTypes.rdf'); } foreach ($this->extensions as $extension) { $this->installExtension($extension, $temp_dir); } foreach ($this->extensions_datas as $dirname => $extension_datas) { mkdir($temp_dir . DIRECTORY_SEPARATOR . $dirname); $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($extension_datas, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($iterator as $item) { $target_dir = $temp_dir . DIRECTORY_SEPARATOR . $dirname . DIRECTORY_SEPARATOR . $iterator->getSubPathName(); if ($item->isDir()) { mkdir($target_dir); } else { copy($item, $target_dir); } } } $content = ''; foreach ($this->preferences as $key => $value) { $content .= sprintf("user_pref(\"%s\", %s);\n", $key, $value); } file_put_contents($temp_dir . '/user.js', $content); // Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle. $temp_zip = sys_get_temp_dir() . '/' . uniqid('WebDriverFirefoxProfileZip', false); $zip = new ZipArchive(); $zip->open($temp_zip, ZipArchive::CREATE); $dir = new RecursiveDirectoryIterator($temp_dir); $files = new RecursiveIteratorIterator($dir); $dir_prefix = preg_replace( '#\\\\#', '\\\\\\\\', $temp_dir . DIRECTORY_SEPARATOR ); foreach ($files as $name => $object) { if (is_dir($name)) { continue; } $path = preg_replace("#^{$dir_prefix}#", '', $name); $zip->addFile($name, $path); } $zip->close(); $profile = base64_encode(file_get_contents($temp_zip)); // clean up $this->deleteDirectory($temp_dir); unlink($temp_zip); return $profile; } /** * @param string $extension The path to the extension. * @param string $profileDir The path to the profile directory. * @throws IOException */ private function installExtension($extension, $profileDir) { $extensionCommonName = $this->parseExtensionName($extension); // install extension to profile directory $extensionDir = $profileDir . '/extensions/'; if (!is_dir($extensionDir) && !mkdir($extensionDir, 0777, true) && !is_dir($extensionDir)) { throw IOException::forFileError( 'Cannot install Firefox extension - cannot create directory', $extensionDir ); } if (!copy($extension, $extensionDir . $extensionCommonName . '.xpi')) { throw IOException::forFileError( 'Cannot install Firefox extension - cannot copy file', $extension ); } } /** * @param string $prefix Prefix of the temp directory. * * @throws IOException * @return string The path to the temp directory created. */ private function createTempDirectory($prefix = '') { $temp_dir = tempnam(sys_get_temp_dir(), $prefix); if (file_exists($temp_dir)) { unlink($temp_dir); mkdir($temp_dir); if (!is_dir($temp_dir)) { throw IOException::forFileError( 'Cannot install Firefox extension - cannot create directory', $temp_dir ); } } return $temp_dir; } /** * @param string $directory The path to the directory. */ private function deleteDirectory($directory) { $dir = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); $paths = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::CHILD_FIRST); foreach ($paths as $path) { if ($path->isDir() && !$path->isLink()) { rmdir($path->getPathname()); } else { unlink($path->getPathname()); } } rmdir($directory); } /** * @param string $xpi The path to the .xpi extension. * @param string $target_dir The path to the unzip directory. * * @throws IOException * @return FirefoxProfile */ private function extractTo($xpi, $target_dir) { $zip = new ZipArchive(); if (file_exists($xpi)) { if ($zip->open($xpi)) { $zip->extractTo($target_dir); $zip->close(); } else { throw IOException::forFileError('Failed to open the firefox extension.', $xpi); } } else { throw IOException::forFileError('Firefox extension doesn\'t exist.', $xpi); } return $this; } private function parseExtensionName($extensionPath) { $temp_dir = $this->createTempDirectory(); $this->extractTo($extensionPath, $temp_dir); $mozillaRsaPath = $temp_dir . '/META-INF/mozilla.rsa'; $mozillaRsaBinaryData = file_get_contents($mozillaRsaPath); $mozillaRsaHex = bin2hex($mozillaRsaBinaryData); //We need to find the plugin id. This is the second occurrence of object identifier "2.5.4.3 commonName". //That is marker "2.5.4.3 commonName" in hex: $objectIdentifierHexMarker = '0603550403'; $firstMarkerPosInHex = strpos($mozillaRsaHex, $objectIdentifierHexMarker); // phpcs:ignore $secondMarkerPosInHexString = strpos($mozillaRsaHex, $objectIdentifierHexMarker, $firstMarkerPosInHex + 2); // phpcs:ignore if ($secondMarkerPosInHexString === false) { throw RuntimeException::forError('Cannot install extension. Cannot fetch extension commonName'); } // phpcs:ignore $commonNameStringPositionInBinary = ($secondMarkerPosInHexString + strlen($objectIdentifierHexMarker)) / 2; $commonNameStringLength = ord($mozillaRsaBinaryData[$commonNameStringPositionInBinary + 1]); // phpcs:ignore $extensionCommonName = substr( $mozillaRsaBinaryData, $commonNameStringPositionInBinary + 2, $commonNameStringLength ); $this->deleteDirectory($temp_dir); return $extensionCommonName; } } webdriver/lib/Firefox/FirefoxOptions.php 0000644 00000007444 15021223212 0014373 0 ustar 00 <?php namespace Facebook\WebDriver\Firefox; use Facebook\WebDriver\Exception\Internal\LogicException; use ReturnTypeWillChange; /** * Class to manage Firefox-specific capabilities * * @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions */ class FirefoxOptions implements \JsonSerializable { /** @var string The key of FirefoxOptions in desired capabilities */ public const CAPABILITY = 'moz:firefoxOptions'; /** @var string */ public const OPTION_ARGS = 'args'; /** @var string */ public const OPTION_PREFS = 'prefs'; /** @var string */ public const OPTION_PROFILE = 'profile'; /** @var array */ private $options = []; /** @var array */ private $arguments = []; /** @var array */ private $preferences = []; /** @var FirefoxProfile */ private $profile; public function __construct() { // Set default preferences: // disable the "Reader View" help tooltip, which can hide elements in the window.document $this->setPreference(FirefoxPreferences::READER_PARSE_ON_LOAD_ENABLED, false); // disable JSON viewer and let JSON be rendered as raw data $this->setPreference(FirefoxPreferences::DEVTOOLS_JSONVIEW, false); } /** * Directly set firefoxOptions. * Use `addArguments` to add command line arguments and `setPreference` to set Firefox about:config entry. * * @param string $name * @param mixed $value * @return self */ public function setOption($name, $value) { if ($name === self::OPTION_PREFS) { throw LogicException::forError('Use setPreference() method to set Firefox preferences'); } if ($name === self::OPTION_ARGS) { throw LogicException::forError('Use addArguments() method to add Firefox arguments'); } if ($name === self::OPTION_PROFILE) { throw LogicException::forError('Use setProfile() method to set Firefox profile'); } $this->options[$name] = $value; return $this; } /** * Command line arguments to pass to the Firefox binary. * These must include the leading dash (-) where required, e.g. ['-headless']. * * @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#args * @param string[] $arguments * @return self */ public function addArguments(array $arguments) { $this->arguments = array_merge($this->arguments, $arguments); return $this; } /** * Set Firefox preference (about:config entry). * * @see http://kb.mozillazine.org/About:config_entries * @see https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#prefs * @param string $name * @param string|bool|int $value * @return self */ public function setPreference($name, $value) { $this->preferences[$name] = $value; return $this; } /** * @see https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefox-profile * @return self */ public function setProfile(FirefoxProfile $profile) { $this->profile = $profile; return $this; } /** * @return array */ public function toArray() { $array = $this->options; if (!empty($this->arguments)) { $array[self::OPTION_ARGS] = $this->arguments; } if (!empty($this->preferences)) { $array[self::OPTION_PREFS] = $this->preferences; } if (!empty($this->profile)) { $array[self::OPTION_PROFILE] = $this->profile->encode(); } return $array; } #[ReturnTypeWillChange] public function jsonSerialize() { return new \ArrayObject($this->toArray()); } } webdriver/lib/Firefox/FirefoxDriver.php 0000644 00000004712 15021223212 0014166 0 ustar 00 <?php namespace Facebook\WebDriver\Firefox; use Facebook\WebDriver\Local\LocalWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\Service\DriverCommandExecutor; use Facebook\WebDriver\Remote\WebDriverCommand; class FirefoxDriver extends LocalWebDriver { /** * @deprecated Pass Firefox Profile using FirefoxOptions: * $firefoxOptions = new FirefoxOptions(); * $firefoxOptions->setProfile($profile->encode()); * $capabilities = DesiredCapabilities::firefox(); * $capabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); */ public const PROFILE = 'firefox_profile'; /** * Creates a new FirefoxDriver using default configuration. * This includes starting a new geckodriver process each time this method is called. However this may be * unnecessary overhead - instead, you can start the process once using FirefoxDriverService and pass * this instance to startUsingDriverService() method. * * @return static */ public static function start(DesiredCapabilities $capabilities = null) { $service = FirefoxDriverService::createDefaultService(); return static::startUsingDriverService($service, $capabilities); } /** * Creates a new FirefoxDriver using given FirefoxDriverService. * This is usable when you for example don't want to start new geckodriver process for each individual test * and want to reuse the already started geckodriver, which will lower the overhead associated with spinning up * a new process. * * @return static */ public static function startUsingDriverService( FirefoxDriverService $service, DesiredCapabilities $capabilities = null ) { if ($capabilities === null) { $capabilities = DesiredCapabilities::firefox(); } $executor = new DriverCommandExecutor($service); $newSessionCommand = WebDriverCommand::newSession( [ 'capabilities' => [ 'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()], ], ] ); $response = $executor->execute($newSessionCommand); $returnedCapabilities = DesiredCapabilities::createFromW3cCapabilities($response->getValue()['capabilities']); $sessionId = $response->getSessionID(); return new static($executor, $sessionId, $returnedCapabilities, true); } } webdriver/lib/Firefox/FirefoxPreferences.php 0000644 00000001511 15021223212 0015166 0 ustar 00 <?php namespace Facebook\WebDriver\Firefox; /** * Constants of common Firefox profile preferences (about:config values). * @see http://kb.mozillazine.org/Firefox_:_FAQs_:_About:config_Entries * * @codeCoverageIgnore */ class FirefoxPreferences { /** @var string Port WebDriver uses to communicate with Firefox instance */ public const WEBDRIVER_FIREFOX_PORT = 'webdriver_firefox_port'; /** @var string Should the reader view (FF 38+) be enabled? */ public const READER_PARSE_ON_LOAD_ENABLED = 'reader.parse-on-load.enabled'; /** @var string Browser homepage */ public const BROWSER_STARTUP_HOMEPAGE = 'browser.startup.homepage'; /** @var string Should the Devtools JSON view be enabled? */ public const DEVTOOLS_JSONVIEW = 'devtools.jsonview.enabled'; private function __construct() { } } webdriver/lib/Firefox/FirefoxDriverService.php 0000644 00000001717 15021223212 0015511 0 ustar 00 <?php namespace Facebook\WebDriver\Firefox; use Facebook\WebDriver\Remote\Service\DriverService; class FirefoxDriverService extends DriverService { /** * @var string Name of the environment variable storing the path to the driver binary */ public const WEBDRIVER_FIREFOX_DRIVER = 'WEBDRIVER_FIREFOX_DRIVER'; /** * @var string Default executable used when no other is provided * @internal */ public const DEFAULT_EXECUTABLE = 'geckodriver'; /** * @return static */ public static function createDefaultService() { $pathToExecutable = getenv(static::WEBDRIVER_FIREFOX_DRIVER); if ($pathToExecutable === false || $pathToExecutable === '') { $pathToExecutable = static::DEFAULT_EXECUTABLE; } $port = 9515; // TODO: Get another free port if the default port is used. $args = ['-p=' . $port]; return new static($pathToExecutable, $port, $args); } } webdriver/CHANGELOG.md 0000644 00000035635 15021223212 0010350 0 ustar 00 # Changelog This project versioning adheres to [Semantic Versioning](http://semver.org/). ## Unreleased ## 1.15.1 - 2023-10-20 - Update `symfony/process` dependency to support upcoming Symfony 7. ## 1.15.0 - 2023-08-29 ### Changed - Capability key `ChromeOptions::CAPABILITY_W3C` used to set ChromeOptions is now deprecated in favor of `ChromeOptions::CAPABILITY`, which now also contains the W3C compatible value (`goog:chromeOptions`). - ChromeOptions are now passed to the driver always as a W3C compatible key `goog:chromeOptions`, even in the deprecated OSS JsonWire payload (as ChromeDriver [supports](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1786) this since 2017). - Improve Safari compatibility for `<select multilpe>` element. - Remove no longer needed compatibility layer with old Symfony. - Docs: Document exception throwing in findElement. ### Fixed - Handle unexpected response when getting element(s) by throwing an exception, not triggering fatal error. ## 1.14.0 - 2023-02-09 ### Added - `PhpWebDriverExceptionInterface` as a common interface to identify all exceptions thrown in php-webdriver. ### Changed - Require PHP ^7.3. - Capabilities must be either explicitly provided or retrievable from Selenium Grid when resuing session with `createBySessionID()`. - Throw `UnexpectedResponseException` instead of `UnknownErrorException` in `findElement()` and `findElements()` methods. - Throw custom php-webdriver exceptions instead of native PHP SPL exceptions. - Do not mix internal non-W3C WebDriver exceptions, separate them into own namespaces. ## 1.13.1 - 2022-10-11 ### Fixed - Do not fail when using `isDisplayed()` and capabilities are missing in WebDriver instance. (Happens when driver instance was created using `RemoteWebDriver::createBySessionID()`.) ## 1.13.0 - 2022-10-03 ### Added - Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method. - `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile. - Element `isDisplayed()` can now be used even for browsers not supporting native API endpoint (like Safari), thanks to javascript atom workaround. ### Changed - Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data. - Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile. - Deprecate `getAllSessions()` method of `RemoteWebDriver` (which is not part of W3C WebDriver). - Increase default request timeout to 3 minutes (instead of 30 seconds). ### Fixed - Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands. ## 1.12.1 - 2022-05-03 ### Fixed - Improper PHP documentation for `getAttribute()` and `getDomProperty()`. - Unsafe use of `static::` when accessing private property in `DesiredCapabilities`. - PHP 8.1 deprecations in the `Cookie` class. ### Changed - Docs: Extend `findElement()`/`findElements()` method documentation to better explain XPath behavior. - Add `@return` and `@param` type annotations to Cookie class to avoid deprecations in PHP 8.1. ## 1.12.0 - 2021-10-14 ### Added - `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode. - `WebDriverCommand::newSession()` constructor to create new session command without violating typehints. ### Changed - Allow installation of Symfony 6 components. ### Fixed - PHP 8.1 compatibility. ## 1.11.1 - 2021-05-21 ### Fixed - `RemoteWebElement::getLocationOnScreenOnceScrolledIntoView()` was missing polyfill implementation for W3C mode and not working in eg. Safari. ## 1.11.0 - 2021-05-03 ### Added - `FirefoxOptions` class to simplify passing Firefox capabilities. Usage is covered [in our wiki](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefoxoptions). - `FirefoxDriver` to easy local start of Firefox instance without a need to start the `geckodriver` process manually. [See wiki](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#start-directly-using-firefoxdriver-class) for usage examples. - Method `ChromeDriver::startUsingDriverService()` to be used for creating ChromeDriver instance with custom service. ### Fixed - Driver capabilities received from the browser when creating now session were not set to the instance of ChromeDriver (when ChromeDriver::start() was used). ### Changed - Deprecate `ChromeDriver::startSession`. However, the method was supposed to be used only internally. - KeyDown and KeyUp actions will throw an exception when not used with modifier keys. ## 1.10.0 - 2021-02-25 ### Added - Support for sending Chrome DevTools Protocol commands (see details in [wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome#chrome-devtools-protocol-cdp)). - Option to specify type of new window (window or tab) when using `$driver->switchTo()->newWindow()`. ### Fixed - Actually start ChromeDriver in W3C mode if it is supported by the browser driver. Until now, when it was initialized using `ChromeDriver::start()`, it has always been unintentionally started in OSS mode. - ChromeOptions were ignored when passed to DesiredCapabilities as `ChromeOptions::CAPABILITY_W3C`. - Clicking a block element inside `<a>` element in Firefox (workaround for GeckoDriver bug [1374283](https://bugzilla.mozilla.org/show_bug.cgi?id=1374283)). ### Changed - Throw `DriverServerDiedException` on local driver process terminating unexpectedly and provide full details of original exception to improve debugging. - Do not require `WEBDRIVER_CHROME_DRIVER` environment variable to be set if `chromedriver` binary is already available via system PATH. - Mark PhantomJS deprecated, as it is no longer developed and maintained. - Deprecate `RemoteWebDriver::newWindow()` in favor of `$driver->switchTo()->newWindow()`. - Don't escape slashes in CURL exception message to improve readability. ## 1.9.0 - 2020-11-19 ### Added - Support of SameSite cookie property. - Command `RemoteWebDriver::newWindow()` for W3C mode to open new top-level browsing context (aka window). - PHP 8.0 support. ## 1.8.3 - 2020-10-06 ### Fixed - Make `alertIsPresent()` condition working in W3C mode. - `RemoteWebDriver::create()` cannot be used without providing the second parameter (which is in fact optional). - `ChromeDriver::start()` starts in inconsistent state mixing W3C/OSS mode. - Modifier keys are not released when sending NULL key in GeckoDriver (workaround for GeckoDriver bug [1494661](https://bugzilla.mozilla.org/show_bug.cgi?id=1494661)). - Do not set unnecessary `binary` value of `goog:chromeOptions` while keep the object in proper data type required by ChromeDriver. ## 1.8.2 - 2020-03-04 ### Changed - Reimplement element `equals()` method to be working in W3C mode. - New instance of `RemoteWebDriver` created via `createBySessionID()` by default expects W3C mode. This could be disabled using fifth parameter of `createBySessionID()`. - Disable JSON viewer in Firefox to let JSON response be rendered as-is. ### Fixed - Properly read fifth parameter whether W3C compliant instance should be created when using `createBySessionID()`. - Creating of Firefox profile with libzip 1.6+ (eg. on Mac OS Catalina). ## 1.8.1 - 2020-02-17 ### Fixed - Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.) - Use relative offset when moving mouse pointer in W3C WebDriver mode. ## 1.8.0 - 2020-02-10 ### Added - Experimental W3C WebDriver protocol support. The protocol will be used automatically when remote end (like Geckodriver, newer Chromedriver etc.) supports it. - `getStatus()` method of `RemoteWebDriver` to get information about remote-end readiness to create new sessions. - `takeElementScreenshot()` method of `RemoteWebElement` to do the obvious - take screenshot of the particular element. - Support for sending custom commands via `executeCustomCommand()`. See [wiki](https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands) for more information. ### Changed - The repository was migrated to [`php-webdriver/php-webdriver`](https://github.com/php-webdriver/php-webdriver/). - The Packagist package was renamed to [`php-webdriver/webdriver`](https://packagist.org/packages/php-webdriver/webdriver) and the original [`facebook/webdriver`](https://packagist.org/packages/facebook/webdriver) was marked as abandoned. - 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. - Rename environment variable used to pass path to ChromeDriver executable from `webdriver.chrome.driver` to `WEBDRIVER_CHROME_DRIVER`. However the old one also still works to keep backward compatibility - If subdirectories in a path to screenshot destination does not exists (using `takeScreenshot()` or `takeElementScreenshot()` methods), they are automatically created. - When zip archive cannot be created during file upload, throw an exception instead of silently returning false. - `WebDriverNavigation` and `EventFiringWebDriverNavigation` now both implement new `WebDriverNavigationInterface`. ### Fixed - `WebDriverExpectedCondition::presenceOfElementLocated()` works correctly when used within `WebDriverExpectedCondition::not()`. - Improper behavior of Microsoft Edge when retrieving all cookies via `getCookies()` (it was causing fatal error when there were no cookies). - Avoid "path is not canonical" error when uploading file to Chromedriver. ## 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. ### 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. - Update PHPDoc for functions that return static instances of a class. ### 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. - Allow installation of Symfony 4 components. ### Added - Add a `visibilityOfAnyElementsLocated()` method to `WebDriverExpectedCondition`. ## 1.4.1 - 2017-04-28 ### Fixed - Do not throw notice `Constant CURLOPT_CONNECTTIMEOUT_MS already defined`. ## 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. - `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()`. ## 1.3.0 - 2017-01-13 ### Added - Added `getCapabilities()` method of `RemoteWebDriver`, to retrieve actual capabilities acknowledged by the remote driver on startup. - Added option to pass required capabilities when creating `RemoteWebDriver`. (So far only desired capabilities were supported.) - Added new expected conditions: - `urlIs` - current URL exactly equals given value - `urlContains` - current URL contains given text - `urlMatches` - current URL matches regular expression - `titleMatches` - current page title matches regular expression - `elementTextIs` - text in element exactly equals given text - `elementTextContains` (as an alias for `textToBePresentInElement`) - text in element contains given text - `elementTextMatches` - text in element matches regular expression - `numberOfWindowsToBe` - number of opened windows equals given number - Possibility to select option of `<select>` by its partial text (using `selectByVisiblePartialText()`). - `XPathEscaper` helper class to quote XPaths containing both single and double quotes. - `WebDriverSelectInterface`, to allow implementation of custom select-like components, eg. those not built around and actual select tag. ### Changed - `Symfony\Process` is used to start local WebDriver processes (when browsers are run directly, without Selenium server) to workaround some PHP bugs and improve portability. - Clarified meaning of selenium server URL variable in methods of `RemoteWebDriver` class. - Deprecated `setSessionID()` and `setCommandExecutor()` methods of `RemoteWebDriver` class; these values should be immutable and thus passed only via constructor. - Deprecated `WebDriverExpectedCondition::textToBePresentInElement()` in favor of `elementTextContains()`. - Throw an exception when attempting to deselect options of non-multiselect (it already didn't have any effect, but was silently ignored). - Optimize performance of `(de)selectByIndex()` and `getAllSelectedOptions()` methods of `WebDriverSelect` when used with non-multiple select element. ### Fixed - XPath escaping in `select*()` and `deselect*()` methods of `WebDriverSelect`. ## 1.2.0 - 2016-10-14 - Added initial support of remote Microsoft Edge browser (but starting local EdgeDriver is still not supported). - Utilize late static binding to make eg. `WebDriverBy` and `DesiredCapabilities` classes easily extensible. - PHP version at least 5.5 is required. - Fixed incompatibility with Appium, caused by redundant params present in requests to Selenium server. ## 1.1.3 - 2016-08-10 - Fixed FirefoxProfile to support installation of extensions with custom namespace prefix in their manifest file. - Comply codestyle with [PSR-2](http://www.php-fig.org/psr/psr-2/). ## 1.1.2 - 2016-06-04 - Added ext-curl to composer.json. - Added CHANGELOG.md. - Added CONTRIBUTING.md with information and rules for contributors. ## 1.1.1 - 2015-12-31 - Fixed strict standards error in `ChromeDriver`. - Added unit tests for `WebDriverCommand` and `DesiredCapabilities`. - Fixed retrieving temporary path name in `FirefoxDriver` when `open_basedir` restriction is in effect. ## 1.1.0 - 2015-12-08 - FirefoxProfile improved - added possibility to set RDF file and to add datas for extensions. - Fixed setting 0 second timeout of `WebDriverWait`. webdriver/LICENSE.md 0000644 00000002250 15021223212 0010126 0 ustar 00 MIT License Copyright (c) 2004-2020 Facebook Copyright (c) 2020-present [open-source contributors](https://github.com/php-webdriver/php-webdriver/graphs/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. webdriver/README.md 0000644 00000024624 15021223212 0010012 0 ustar 00 # php-webdriver – Selenium WebDriver bindings for PHP [](https://packagist.org/packages/php-webdriver/webdriver) [](https://github.com/php-webdriver/php-webdriver/actions) [](https://saucelabs.com/u/php-webdriver) [](https://packagist.org/packages/php-webdriver/webdriver) ## 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, 3.x and 4.x. The library supports modern [W3C WebDriver](https://w3c.github.io/webdriver/) protocol, as well as legacy [JsonWireProtocol](https://www.selenium.dev/documentation/legacy/json_wire_protocol/). The concepts of this library are very similar to the "official" Java, JavaScript, .NET, Python and Ruby libraries which are developed as part of the [Selenium project](https://github.com/SeleniumHQ/selenium/). ## Installation Installation is possible using [Composer](https://getcomposer.org/). If you don't already use Composer, you can download the `composer.phar` binary: curl -sS https://getcomposer.org/installer | php Then install the library: php composer.phar require php-webdriver/webdriver ## Upgrade from version <1.8.0 Starting from version 1.8.0, the project has been renamed from `facebook/php-webdriver` to `php-webdriver/webdriver`. In order to receive the new version and future updates, **you need to rename it in your composer.json**: ```diff "require": { - "facebook/webdriver": "(version you use)", + "php-webdriver/webdriver": "(version you use)", } ``` and run `composer update`. ## Getting started ### 1. Start server (aka. remote end) To control a browser, you need to start a *remote end* (server), which will listen to the commands sent from this library and will execute them in the respective browser. This could be Selenium standalone server, but for local development, you can send them directly to so-called "browser driver" like Chromedriver or Geckodriver. #### a) Chromedriver 📙 Below you will find a simple example. Make sure to read our wiki for [more information on Chrome/Chromedriver](https://github.com/php-webdriver/php-webdriver/wiki/Chrome). Install the latest Chrome and [Chromedriver](https://sites.google.com/chromium.org/driver/downloads). Make sure to have a compatible version of Chromedriver and Chrome! Run `chromedriver` binary, you can pass `port` argument, so that it listens on port 4444: ```sh chromedriver --port=4444 ``` #### b) Geckodriver 📙 Below you will find a simple example. Make sure to read our wiki for [more information on Firefox/Geckodriver](https://github.com/php-webdriver/php-webdriver/wiki/Firefox). Install the latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases). Make sure to have a compatible version of Geckodriver and Firefox! Run `geckodriver` binary (it start to listen on port 4444 by default): ```sh geckodriver ``` #### c) Selenium standalone server Selenium server can be useful when you need to execute multiple tests at once, when you run tests in several different browsers (like on your CI server), or when you need to distribute tests amongst several machines in grid mode (where one Selenium server acts as a hub, and others connect to it as nodes). Selenium server then act like a proxy and takes care of distributing commands to the respective nodes. The latest version can be found on the [Selenium download page](https://www.selenium.dev/downloads/). 📙 You can find [further Selenium server information](https://github.com/php-webdriver/php-webdriver/wiki/Selenium-server) in our wiki. #### d) Docker Selenium server could also be started inside Docker container - see [docker-selenium project](https://github.com/SeleniumHQ/docker-selenium). ### 2. Create a Browser Session When creating a browser session, be sure to pass the url of your running server. For example: ```php // Chromedriver (if started using --port=4444 as above) $serverUrl = 'http://localhost:4444'; // Geckodriver $serverUrl = 'http://localhost:4444'; // selenium-server-standalone-#.jar (version 2.x or 3.x) $serverUrl = 'http://localhost:4444/wd/hub'; // selenium-server-standalone-#.jar (version 4.x) $serverUrl = 'http://localhost:4444'; ``` Now you can start browser of your choice: ```php use Facebook\WebDriver\Remote\RemoteWebDriver; // Chrome $driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::chrome()); // Firefox $driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::firefox()); // Microsoft Edge $driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::microsoftEdge()); ``` ### 3. Customize Desired Capabilities Desired capabilities define properties of the browser you are about to start. They can be customized: ```php use Facebook\WebDriver\Firefox\FirefoxOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; $desiredCapabilities = DesiredCapabilities::firefox(); // Disable accepting SSL certificates $desiredCapabilities->setCapability('acceptSslCerts', false); // Add arguments via FirefoxOptions to start headless firefox $firefoxOptions = new FirefoxOptions(); $firefoxOptions->addArguments(['-headless']); $desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions); $driver = RemoteWebDriver::create($serverUrl, $desiredCapabilities); ``` Capabilities can also be used to [📙 configure a proxy server](https://github.com/php-webdriver/php-webdriver/wiki/HowTo-Work-with-proxy) which the browser should use. To configure browser-specific capabilities, you may use [📙 ChromeOptions](https://github.com/php-webdriver/php-webdriver/wiki/Chrome#chromeoptions) or [📙 FirefoxOptions](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefoxoptions). * See [legacy JsonWire protocol](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities) documentation or [W3C WebDriver specification](https://w3c.github.io/webdriver/#capabilities) for more details. ### 4. Control your browser ```php // Go to URL $driver->get('https://en.wikipedia.org/wiki/Selenium_(software)'); // Find search element by its id, write 'PHP' inside and submit $driver->findElement(WebDriverBy::id('searchInput')) // find search input element ->sendKeys('PHP') // fill the search box ->submit(); // submit the whole form // Find element of 'History' item in menu by its css selector $historyButton = $driver->findElement( WebDriverBy::cssSelector('#ca-history a') ); // Read text of the element and print it to output echo 'About to click to a button with text: ' . $historyButton->getText(); // Click the element to navigate to revision history page $historyButton->click(); // Make sure to always call quit() at the end to terminate the browser session $driver->quit(); ``` See [example.php](example.php) for full example scenario. Visit our GitHub wiki for [📙 php-webdriver command reference](https://github.com/php-webdriver/php-webdriver/wiki/Example-command-reference) and further examples. **NOTE:** Above snippets are not intended to be a working example by simply copy-pasting. See [example.php](example.php) for a working example. ## Changelog For latest changes see [CHANGELOG.md](CHANGELOG.md) file. ## More information Some basic usage example is provided in [example.php](example.php) file. How-tos are provided right here in [📙 our GitHub wiki](https://github.com/php-webdriver/php-webdriver/wiki). If you don't use IDE, you may use [API documentation of php-webdriver](https://php-webdriver.github.io/php-webdriver/latest/). You may also want to check out the Selenium project [docs](https://selenium.dev/documentation/en/) and [wiki](https://github.com/SeleniumHQ/selenium/wiki). ## Testing framework integration To take advantage of automatized testing you may want to integrate php-webdriver to your testing framework. There are some projects already providing this: - [Symfony Panther](https://github.com/symfony/panther) uses php-webdriver and integrates with PHPUnit using `PantherTestCase` - [Laravel Dusk](https://laravel.com/docs/dusk) is another project using php-webdriver, could be used for testing via `DuskTestCase` - [Steward](https://github.com/lmc-eu/steward) integrates php-webdriver directly to [PHPUnit](https://phpunit.de/), and provides parallelization - [Codeception](https://codeception.com/) testing framework provides BDD-layer on top of php-webdriver in its [WebDriver module](https://codeception.com/docs/modules/WebDriver) - You can also check out this [blogpost](https://codeception.com/11-12-2013/working-with-phpunit-and-selenium-webdriver.html) + [demo project](https://github.com/DavertMik/php-webdriver-demo), describing simple [PHPUnit](https://phpunit.de/) integration ## Support We have a great community willing to help you! ❓ Do you have a **question, idea or some general feedback**? Visit our [Discussions](https://github.com/php-webdriver/php-webdriver/discussions) page. (Alternatively, you can [look for many answered questions also on StackOverflow](https://stackoverflow.com/questions/tagged/php+selenium-webdriver)). 🐛 Something isn't working, and you want to **report a bug**? [Submit it here](https://github.com/php-webdriver/php-webdriver/issues/new) as a new issue. 📙 Looking for a **how-to** or **reference documentation**? See [our wiki](https://github.com/php-webdriver/php-webdriver/wiki). ## Contributing ❤️ We love to have your help to make php-webdriver better. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more information about contributing and developing php-webdriver. Php-webdriver is community project - if you want to join the effort with maintaining and developing this library, the best is to look on [issues marked with "help wanted"](https://github.com/php-webdriver/php-webdriver/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) label. Let us know in the issue comments if you want to contribute and if you want any guidance, and we will be delighted to help you to prepare your pull request.
| ver. 1.4 |
Github
|
.
| PHP 8.1.29 | Генерация страницы: 0.1 |
proxy
|
phpinfo
|
Настройка