đã tạo được test case của createUser
This commit is contained in:
+278
@@ -0,0 +1,278 @@
|
||||
# Changelog
|
||||
This project versioning adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.16.0 - 2025-12-29
|
||||
### Fixed
|
||||
- Fix file upload endpoint for Selenium protocol (W3C WebDriver extension).
|
||||
- Docs: Fix incorrect link to driver capabilities docs.
|
||||
|
||||
### Changed
|
||||
- Exceptions thrown by the library are now always extends `\Throwable`, to allow better work with exception methods when catching `\PhpWebDriverExceptionInterface`.
|
||||
- Support Symfony 8.
|
||||
- Timeouts value types now properly supports and allow only `null|int|float` for W3C Webdriver (while keeping only `int|float` allowed for JsonWire protocol).
|
||||
- Tests: Update SauceLabs Connect and use new platform versions.
|
||||
- Tests: Remove `new` flag from headless chrome, as it is already default since Chromedriver 132.
|
||||
|
||||
## 1.15.2 - 2024-11-21
|
||||
### Fixed
|
||||
- PHP 8.4 deprecation notices, especially in nullable type-hints.
|
||||
- Docs: Fix static return types in RemoteWebElement phpDoc.
|
||||
- Tests: Disable chrome 127+ search engine pop-up in tests
|
||||
- Tests: Enable Shadow DOM tests in Geckodriver
|
||||
|
||||
### Added
|
||||
- Tests: Allow running tests in headfull (not headless) mode using `DISABLE_HEADLESS` environment variable.
|
||||
|
||||
### Changed
|
||||
- Docs: Update selenium server host URL in example.
|
||||
|
||||
## 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`.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
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.
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
# 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.
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"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 || ^8.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 || ^7.0 || ^8.0"
|
||||
},
|
||||
"replace": {
|
||||
"facebook/webdriver": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-simplexml": "For Firefox profile creation"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?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/chromium.org/driver/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;
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A command failed because the referenced shadow root is no longer attached to the DOM.
|
||||
*/
|
||||
class DetachedShadowRootException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A command could not be completed because the element is not pointer- or keyboard interactable.
|
||||
*/
|
||||
class ElementNotInteractableException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Use Facebook\WebDriver\Exception\ElementNotInteractableException
|
||||
*/
|
||||
class ElementNotSelectableException extends ElementNotInteractableException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class ElementNotVisibleException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class ExpectedException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class IMEEngineActivationFailedException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class IMENotAvailableException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class IndexOutOfBoundsException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<?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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* The arguments passed to a command are either invalid or malformed.
|
||||
*/
|
||||
class InvalidArgumentException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class InvalidCoordinatesException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* Argument was an invalid selector.
|
||||
*/
|
||||
class InvalidSelectorException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* An error occurred while executing JavaScript supplied by the user.
|
||||
*/
|
||||
class JavascriptErrorException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Use Facebook\WebDriver\Exception\NoSuchAlertException
|
||||
*/
|
||||
class NoAlertOpenException extends NoSuchAlertException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoCollectionException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoScriptResultException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoStringException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoStringLengthException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoStringWrapperException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* An attempt was made to operate on a modal dialog when one was not open.
|
||||
*/
|
||||
class NoSuchAlertException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoSuchCollectionException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Use Facebook\WebDriver\Exception\NoSuchWindowException
|
||||
*/
|
||||
class NoSuchDocumentException extends NoSuchWindowException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NoSuchDriverException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* An element could not be located on the page using the given search parameters.
|
||||
*/
|
||||
class NoSuchElementException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* The element does not have a shadow root.
|
||||
*/
|
||||
class NoSuchShadowRootException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class NullPointerException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* Common interface to identify all exceptions thrown in php-webdriver (both those of WebDriver protocol and internal).
|
||||
*/
|
||||
interface PhpWebDriverExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A script did not complete before its timeout expired.
|
||||
*/
|
||||
class ScriptTimeoutException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A new session could not be created.
|
||||
*/
|
||||
class SessionNotCreatedException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A command failed because the referenced element is no longer attached to the DOM.
|
||||
*/
|
||||
class StaleElementReferenceException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* An operation did not complete before its timeout expired.
|
||||
*/
|
||||
class TimeoutException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A screen capture was made impossible.
|
||||
*/
|
||||
class UnableToCaptureScreenException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A command to set a cookie’s value could not be satisfied.
|
||||
*/
|
||||
class UnableToSetCookieException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A modal dialog was open, blocking this operation.
|
||||
*/
|
||||
class UnexpectedAlertOpenException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Use Facebook\WebDriver\Exception\JavascriptErrorException
|
||||
*/
|
||||
class UnexpectedJavascriptException extends JavascriptErrorException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* A command could not be executed because the remote end is not aware of it.
|
||||
*/
|
||||
class UnknownCommandException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* An unknown error occurred in the remote end while processing the command.
|
||||
*/
|
||||
class UnknownErrorException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Use Facebook\WebDriver\Exception\UnknownErrorException
|
||||
*/
|
||||
class UnknownServerException extends UnknownErrorException
|
||||
{
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
class UnrecognizedExceptionException extends WebDriverException
|
||||
{
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* Indicates that a command that should have executed properly cannot be supported for some reason.
|
||||
*/
|
||||
class UnsupportedOperationException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
|
||||
*/
|
||||
class XPathLookupException extends WebDriverException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Internal;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverClickAction extends WebDriverMouseAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->mouse->click($this->getActionLocation());
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Internal;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverDoubleClickAction extends WebDriverMouseAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->mouse->doubleClick($this->getActionLocation());
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Internal;
|
||||
|
||||
class WebDriverKeyDownAction extends WebDriverSingleKeyAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->focusOnElement();
|
||||
$this->keyboard->pressKey($this->key);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Internal;
|
||||
|
||||
class WebDriverKeyUpAction extends WebDriverSingleKeyAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->focusOnElement();
|
||||
$this->keyboard->releaseKey($this->key);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Internal;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverMouseMoveAction extends WebDriverMouseAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->mouse->mouseMove($this->getActionLocation());
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Touch;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverDoubleTapAction extends WebDriverTouchAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->touchScreen->doubleTap($this->locationProvider);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Touch;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverLongPressAction extends WebDriverTouchAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->touchScreen->longPress($this->locationProvider);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Interactions\Touch;
|
||||
|
||||
use Facebook\WebDriver\WebDriverAction;
|
||||
|
||||
class WebDriverTapAction extends WebDriverTouchAction implements WebDriverAction
|
||||
{
|
||||
public function perform()
|
||||
{
|
||||
$this->touchScreen->tap($this->locationProvider);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?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);
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user