đã tạo được test case của createUser

This commit is contained in:
2026-04-07 11:22:54 +07:00
parent 9d0a872e91
commit 8a7ffe6212
235 changed files with 30524 additions and 9502 deletions
@@ -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
View File
@@ -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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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()
)
);
}
}
@@ -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()
)
);
}
}
@@ -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
{
}
@@ -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
{
}
@@ -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 isnt 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 its 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
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* The target for mouse interaction is not in the browsers 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 contexts
* 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
{
}
@@ -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
{
}
@@ -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
{
}
@@ -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 cookies value could not be satisfied.
*/
class UnableToSetCookieException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* A modal dialog was open, blocking this operation.
*/
class UnexpectedAlertOpenException extends WebDriverException
{
}
@@ -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
{
}
@@ -0,0 +1,7 @@
<?php
namespace Facebook\WebDriver\Exception;
class UnrecognizedExceptionException extends WebDriverException
{
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -0,0 +1,12 @@
<?php
namespace Facebook\WebDriver\Interactions\Internal;
class WebDriverKeyDownAction extends WebDriverSingleKeyAction
{
public function perform()
{
$this->focusOnElement();
$this->keyboard->pressKey($this->key);
}
}
@@ -0,0 +1,12 @@
<?php
namespace Facebook\WebDriver\Interactions\Internal;
class WebDriverKeyUpAction extends WebDriverSingleKeyAction
{
public function perform()
{
$this->focusOnElement();
$this->keyboard->releaseKey($this->key);
}
}
@@ -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());
}
}
}
@@ -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);
}
}
@@ -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());
}
}
@@ -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
);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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
);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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();
}
}
}
@@ -0,0 +1,175 @@
<?php
namespace Facebook\WebDriver\Interactions;
use Facebook\WebDriver\Interactions\Touch\WebDriverDoubleTapAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverDownAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverFlickAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverFlickFromElementAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverLongPressAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverMoveAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverScrollAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverScrollFromElementAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverTapAction;
use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen;
use Facebook\WebDriver\WebDriver;
use Facebook\WebDriver\WebDriverElement;
use Facebook\WebDriver\WebDriverUpAction;
/**
* WebDriver action builder for touch events
*/
class WebDriverTouchActions extends WebDriverActions
{
/**
* @var WebDriverTouchScreen
*/
protected $touchScreen;
public function __construct(WebDriver $driver)
{
parent::__construct($driver);
$this->touchScreen = $driver->getTouch();
}
/**
* @return WebDriverTouchActions
*/
public function tap(WebDriverElement $element)
{
$this->action->addAction(
new WebDriverTapAction($this->touchScreen, $element)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function down($x, $y)
{
$this->action->addAction(
new WebDriverDownAction($this->touchScreen, $x, $y)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function up($x, $y)
{
$this->action->addAction(
new WebDriverUpAction($this->touchScreen, $x, $y)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function move($x, $y)
{
$this->action->addAction(
new WebDriverMoveAction($this->touchScreen, $x, $y)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function scroll($x, $y)
{
$this->action->addAction(
new WebDriverScrollAction($this->touchScreen, $x, $y)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function scrollFromElement(WebDriverElement $element, $x, $y)
{
$this->action->addAction(
new WebDriverScrollFromElementAction($this->touchScreen, $element, $x, $y)
);
return $this;
}
/**
* @return WebDriverTouchActions
*/
public function doubleTap(WebDriverElement $element)
{
$this->action->addAction(
new WebDriverDoubleTapAction($this->touchScreen, $element)
);
return $this;
}
/**
* @return WebDriverTouchActions
*/
public function longPress(WebDriverElement $element)
{
$this->action->addAction(
new WebDriverLongPressAction($this->touchScreen, $element)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @return WebDriverTouchActions
*/
public function flick($x, $y)
{
$this->action->addAction(
new WebDriverFlickAction($this->touchScreen, $x, $y)
);
return $this;
}
/**
* @param int $x
* @param int $y
* @param int $speed
* @return WebDriverTouchActions
*/
public function flickFromElement(WebDriverElement $element, $x, $y, $speed)
{
$this->action->addAction(
new WebDriverFlickFromElementAction(
$this->touchScreen,
$element,
$x,
$y,
$speed
)
);
return $this;
}
}
@@ -0,0 +1,16 @@
<?php
namespace Facebook\WebDriver\Internal;
use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
/**
* Interface representing basic mouse operations.
*/
interface WebDriverLocatable
{
/**
* @return WebDriverCoordinates
*/
public function getCoordinates();
}
@@ -0,0 +1,35 @@
<?php
namespace Facebook\WebDriver;
/**
* WebDriver interface implemented by drivers that support JavaScript.
*/
interface JavaScriptExecutor
{
/**
* Inject a snippet of JavaScript into the page for execution in the context
* of the currently selected frame. The executed script is assumed to be
* synchronous and the result of evaluating the script will be returned.
*
* @param string $script The script to inject.
* @param array $arguments The arguments of the script.
* @return mixed The return value of the script.
*/
public function executeScript($script, array $arguments = []);
/**
* Inject a snippet of JavaScript into the page for asynchronous execution in
* the context of the currently selected frame.
*
* The driver will pass a callback as the last argument to the snippet, and
* block until the callback is invoked.
*
* @see WebDriverExecuteAsyncScriptTestCase
*
* @param string $script The script to inject.
* @param array $arguments The arguments of the script.
* @return mixed The value passed by the script to the callback.
*/
public function executeAsyncScript($script, array $arguments = []);
}
@@ -0,0 +1,37 @@
<?php
namespace Facebook\WebDriver\Local;
use Facebook\WebDriver\Exception\Internal\LogicException;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* @codeCoverageIgnore
* @todo Break inheritance from RemoteWebDriver in next major version. (Composition over inheritance!)
*/
abstract class LocalWebDriver extends RemoteWebDriver
{
// @todo Remove in next major version (should not be inherited)
public static function create(
$selenium_server_url = 'http://localhost:4444/wd/hub',
$desired_capabilities = null,
$connection_timeout_in_ms = null,
$request_timeout_in_ms = null,
$http_proxy = null,
$http_proxy_port = null,
?DesiredCapabilities $required_capabilities = null
) {
throw LogicException::forError('Use start() method to start local WebDriver.');
}
// @todo Remove in next major version (should not be inherited)
public static function createBySessionID(
$session_id,
$selenium_server_url = 'http://localhost:4444/wd/hub',
$connection_timeout_in_ms = null,
$request_timeout_in_ms = null
) {
throw LogicException::forError('Use start() method to start local WebDriver.');
}
}

Some files were not shown because too many files have changed in this diff Show More