đã tạo được test case của createUser
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\LogicException;
|
||||
use Facebook\WebDriver\Exception\WebDriverException;
|
||||
|
||||
class CustomWebDriverCommand extends WebDriverCommand
|
||||
{
|
||||
public const METHOD_GET = 'GET';
|
||||
public const METHOD_POST = 'POST';
|
||||
|
||||
/** @var string */
|
||||
private $customUrl;
|
||||
/** @var string */
|
||||
private $customMethod;
|
||||
|
||||
/**
|
||||
* @param string $session_id
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
*/
|
||||
public function __construct($session_id, $url, $method, array $parameters)
|
||||
{
|
||||
$this->setCustomRequestParameters($url, $method);
|
||||
|
||||
parent::__construct($session_id, DriverCommand::CUSTOM_COMMAND, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WebDriverException
|
||||
* @return string
|
||||
*/
|
||||
public function getCustomUrl()
|
||||
{
|
||||
if ($this->customUrl === null) {
|
||||
throw LogicException::forError('URL of custom command is not set');
|
||||
}
|
||||
|
||||
return $this->customUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WebDriverException
|
||||
* @return string
|
||||
*/
|
||||
public function getCustomMethod()
|
||||
{
|
||||
if ($this->customMethod === null) {
|
||||
throw LogicException::forError('Method of custom command is not set');
|
||||
}
|
||||
|
||||
return $this->customMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $custom_url
|
||||
* @param string $custom_method
|
||||
* @throws WebDriverException
|
||||
*/
|
||||
protected function setCustomRequestParameters($custom_url, $custom_method)
|
||||
{
|
||||
$allowedMethods = [static::METHOD_GET, static::METHOD_POST];
|
||||
if (!in_array($custom_method, $allowedMethods, true)) {
|
||||
throw LogicException::forError(
|
||||
sprintf(
|
||||
'Invalid custom method "%s", must be one of [%s]',
|
||||
$custom_method,
|
||||
implode(', ', $allowedMethods)
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->customMethod = $custom_method;
|
||||
|
||||
if (mb_strpos($custom_url, '/') !== 0) {
|
||||
throw LogicException::forError(
|
||||
sprintf('URL of custom command has to start with / but is "%s"', $custom_url)
|
||||
);
|
||||
}
|
||||
$this->customUrl = $custom_url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||
use Facebook\WebDriver\Exception\UnsupportedOperationException;
|
||||
use Facebook\WebDriver\Firefox\FirefoxDriver;
|
||||
use Facebook\WebDriver\Firefox\FirefoxOptions;
|
||||
use Facebook\WebDriver\Firefox\FirefoxProfile;
|
||||
use Facebook\WebDriver\WebDriverCapabilities;
|
||||
use Facebook\WebDriver\WebDriverPlatform;
|
||||
|
||||
class DesiredCapabilities implements WebDriverCapabilities
|
||||
{
|
||||
/** @var array */
|
||||
private $capabilities;
|
||||
|
||||
/** @var array */
|
||||
private static $ossToW3c = [
|
||||
WebDriverCapabilityType::PLATFORM => 'platformName',
|
||||
WebDriverCapabilityType::VERSION => 'browserVersion',
|
||||
WebDriverCapabilityType::ACCEPT_SSL_CERTS => 'acceptInsecureCerts',
|
||||
];
|
||||
|
||||
public function __construct(array $capabilities = [])
|
||||
{
|
||||
$this->capabilities = $capabilities;
|
||||
}
|
||||
|
||||
public static function createFromW3cCapabilities(array $capabilities = [])
|
||||
{
|
||||
$w3cToOss = array_flip(self::$ossToW3c);
|
||||
|
||||
foreach ($w3cToOss as $w3cCapability => $ossCapability) {
|
||||
// Copy W3C capabilities to OSS ones
|
||||
if (array_key_exists($w3cCapability, $capabilities)) {
|
||||
$capabilities[$ossCapability] = $capabilities[$w3cCapability];
|
||||
}
|
||||
}
|
||||
|
||||
return new self($capabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The name of the browser.
|
||||
*/
|
||||
public function getBrowserName()
|
||||
{
|
||||
return $this->get(WebDriverCapabilityType::BROWSER_NAME, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $browser_name
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
public function setBrowserName($browser_name)
|
||||
{
|
||||
$this->set(WebDriverCapabilityType::BROWSER_NAME, $browser_name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The version of the browser.
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->get(WebDriverCapabilityType::VERSION, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $version
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
public function setVersion($version)
|
||||
{
|
||||
$this->set(WebDriverCapabilityType::VERSION, $version);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return mixed The value of a capability.
|
||||
*/
|
||||
public function getCapability($name)
|
||||
{
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
public function setCapability($name, $value)
|
||||
{
|
||||
// When setting 'moz:firefoxOptions' from an array and not from instance of FirefoxOptions, we must merge
|
||||
// it with default FirefoxOptions to keep previous behavior (where the default preferences were added
|
||||
// using FirefoxProfile, thus not overwritten by adding 'moz:firefoxOptions')
|
||||
// TODO: remove in next major version, once FirefoxOptions are only accepted as object instance and not as array
|
||||
if ($name === FirefoxOptions::CAPABILITY && is_array($value)) {
|
||||
$defaultOptions = (new FirefoxOptions())->toArray();
|
||||
$value = array_merge($defaultOptions, $value);
|
||||
}
|
||||
|
||||
$this->set($name, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The name of the platform.
|
||||
*/
|
||||
public function getPlatform()
|
||||
{
|
||||
return $this->get(WebDriverCapabilityType::PLATFORM, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $platform
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
public function setPlatform($platform)
|
||||
{
|
||||
$this->set(WebDriverCapabilityType::PLATFORM, $platform);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $capability_name
|
||||
* @return bool Whether the value is not null and not false.
|
||||
*/
|
||||
public function is($capability_name)
|
||||
{
|
||||
return (bool) $this->get($capability_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Remove in next major release (BC)
|
||||
* @deprecated All browsers are always JS enabled except HtmlUnit and it's not meaningful to disable JS execution.
|
||||
* @return bool Whether javascript is enabled.
|
||||
*/
|
||||
public function isJavascriptEnabled()
|
||||
{
|
||||
return $this->get(WebDriverCapabilityType::JAVASCRIPT_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a htmlUnit-only option.
|
||||
*
|
||||
* @param bool $enabled
|
||||
* @throws UnsupportedOperationException
|
||||
* @return DesiredCapabilities
|
||||
* @see https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities
|
||||
*/
|
||||
public function setJavascriptEnabled($enabled)
|
||||
{
|
||||
$browser = $this->getBrowserName();
|
||||
if ($browser && $browser !== WebDriverBrowserType::HTMLUNIT) {
|
||||
throw new UnsupportedOperationException(
|
||||
'isJavascriptEnabled() is a htmlunit-only option. ' .
|
||||
'See https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#read-write-capabilities.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->set(WebDriverCapabilityType::JAVASCRIPT_ENABLED, $enabled);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Remove side-effects - not change eg. ChromeOptions::CAPABILITY from instance of ChromeOptions to an array
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
if (isset($this->capabilities[ChromeOptions::CAPABILITY]) &&
|
||||
$this->capabilities[ChromeOptions::CAPABILITY] instanceof ChromeOptions
|
||||
) {
|
||||
$this->capabilities[ChromeOptions::CAPABILITY] =
|
||||
$this->capabilities[ChromeOptions::CAPABILITY]->toArray();
|
||||
}
|
||||
|
||||
if (isset($this->capabilities[FirefoxOptions::CAPABILITY]) &&
|
||||
$this->capabilities[FirefoxOptions::CAPABILITY] instanceof FirefoxOptions
|
||||
) {
|
||||
$this->capabilities[FirefoxOptions::CAPABILITY] =
|
||||
$this->capabilities[FirefoxOptions::CAPABILITY]->toArray();
|
||||
}
|
||||
|
||||
if (isset($this->capabilities[FirefoxDriver::PROFILE]) &&
|
||||
$this->capabilities[FirefoxDriver::PROFILE] instanceof FirefoxProfile
|
||||
) {
|
||||
$this->capabilities[FirefoxDriver::PROFILE] =
|
||||
$this->capabilities[FirefoxDriver::PROFILE]->encode();
|
||||
}
|
||||
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toW3cCompatibleArray()
|
||||
{
|
||||
$allowedW3cCapabilities = [
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
'acceptInsecureCerts',
|
||||
'pageLoadStrategy',
|
||||
'proxy',
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'strictFileInteractability',
|
||||
'unhandledPromptBehavior',
|
||||
];
|
||||
|
||||
$ossCapabilities = $this->toArray();
|
||||
$w3cCapabilities = [];
|
||||
|
||||
foreach ($ossCapabilities as $capabilityKey => $capabilityValue) {
|
||||
// Copy already W3C compatible capabilities
|
||||
if (in_array($capabilityKey, $allowedW3cCapabilities, true)) {
|
||||
$w3cCapabilities[$capabilityKey] = $capabilityValue;
|
||||
}
|
||||
|
||||
// Convert capabilities with changed name
|
||||
if (array_key_exists($capabilityKey, self::$ossToW3c)) {
|
||||
if ($capabilityKey === WebDriverCapabilityType::PLATFORM) {
|
||||
$w3cCapabilities[self::$ossToW3c[$capabilityKey]] = mb_strtolower($capabilityValue);
|
||||
|
||||
// Remove platformName if it is set to "any"
|
||||
if ($w3cCapabilities[self::$ossToW3c[$capabilityKey]] === 'any') {
|
||||
unset($w3cCapabilities[self::$ossToW3c[$capabilityKey]]);
|
||||
}
|
||||
} else {
|
||||
$w3cCapabilities[self::$ossToW3c[$capabilityKey]] = $capabilityValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy vendor extensions
|
||||
if (mb_strpos($capabilityKey, ':') !== false) {
|
||||
$w3cCapabilities[$capabilityKey] = $capabilityValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ChromeOptions
|
||||
if (array_key_exists(ChromeOptions::CAPABILITY, $ossCapabilities)) {
|
||||
$w3cCapabilities[ChromeOptions::CAPABILITY] = $ossCapabilities[ChromeOptions::CAPABILITY];
|
||||
}
|
||||
|
||||
// Convert Firefox profile
|
||||
if (array_key_exists(FirefoxDriver::PROFILE, $ossCapabilities)) {
|
||||
// Convert profile only if not already set in moz:firefoxOptions
|
||||
if (!array_key_exists(FirefoxOptions::CAPABILITY, $ossCapabilities)
|
||||
|| !array_key_exists('profile', $ossCapabilities[FirefoxOptions::CAPABILITY])) {
|
||||
$w3cCapabilities[FirefoxOptions::CAPABILITY]['profile'] = $ossCapabilities[FirefoxDriver::PROFILE];
|
||||
}
|
||||
}
|
||||
|
||||
return $w3cCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function android()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::ANDROID,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANDROID,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function chrome()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::CHROME,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function firefox()
|
||||
{
|
||||
$caps = new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::FIREFOX,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
|
||||
$caps->setCapability(FirefoxOptions::CAPABILITY, new FirefoxOptions()); // to add default options
|
||||
|
||||
return $caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function htmlUnit()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function htmlUnitWithJS()
|
||||
{
|
||||
$caps = new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::HTMLUNIT,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
|
||||
return $caps->setJavascriptEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function internetExplorer()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IE,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function microsoftEdge()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::MICROSOFT_EDGE,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::WINDOWS,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function iphone()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPHONE,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function ipad()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::IPAD,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::MAC,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function opera()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::OPERA,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function safari()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::SAFARI,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated PhantomJS is no longer developed and its support will be removed in next major version.
|
||||
* Use headless Chrome or Firefox instead.
|
||||
* @return static
|
||||
*/
|
||||
public static function phantomjs()
|
||||
{
|
||||
return new static([
|
||||
WebDriverCapabilityType::BROWSER_NAME => WebDriverBrowserType::PHANTOMJS,
|
||||
WebDriverCapabilityType::PLATFORM => WebDriverPlatform::ANY,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
private function set($key, $value)
|
||||
{
|
||||
$this->capabilities[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
private function get($key, $default = null)
|
||||
{
|
||||
return $this->capabilities[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
/**
|
||||
* This list of command defined in the WebDriver json wire protocol.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class DriverCommand
|
||||
{
|
||||
public const GET_ALL_SESSIONS = 'getAllSessions';
|
||||
public const GET_CAPABILITIES = 'getCapabilities';
|
||||
public const NEW_SESSION = 'newSession';
|
||||
public const STATUS = 'status';
|
||||
public const CLOSE = 'close';
|
||||
public const QUIT = 'quit';
|
||||
public const GET = 'get';
|
||||
public const GO_BACK = 'goBack';
|
||||
public const GO_FORWARD = 'goForward';
|
||||
public const REFRESH = 'refresh';
|
||||
public const ADD_COOKIE = 'addCookie';
|
||||
public const GET_ALL_COOKIES = 'getCookies';
|
||||
public const DELETE_COOKIE = 'deleteCookie';
|
||||
public const DELETE_ALL_COOKIES = 'deleteAllCookies';
|
||||
public const FIND_ELEMENT = 'findElement';
|
||||
public const FIND_ELEMENTS = 'findElements';
|
||||
public const FIND_CHILD_ELEMENT = 'findChildElement';
|
||||
public const FIND_CHILD_ELEMENTS = 'findChildElements';
|
||||
public const CLEAR_ELEMENT = 'clearElement';
|
||||
public const CLICK_ELEMENT = 'clickElement';
|
||||
public const SEND_KEYS_TO_ELEMENT = 'sendKeysToElement';
|
||||
public const SEND_KEYS_TO_ACTIVE_ELEMENT = 'sendKeysToActiveElement';
|
||||
public const SUBMIT_ELEMENT = 'submitElement';
|
||||
public const UPLOAD_FILE = 'uploadFile';
|
||||
public const GET_CURRENT_WINDOW_HANDLE = 'getCurrentWindowHandle';
|
||||
public const GET_WINDOW_HANDLES = 'getWindowHandles';
|
||||
public const GET_CURRENT_CONTEXT_HANDLE = 'getCurrentContextHandle';
|
||||
public const GET_CONTEXT_HANDLES = 'getContextHandles';
|
||||
// Switching between to window/frame/iframe
|
||||
public const SWITCH_TO_WINDOW = 'switchToWindow';
|
||||
public const SWITCH_TO_CONTEXT = 'switchToContext';
|
||||
public const SWITCH_TO_FRAME = 'switchToFrame';
|
||||
public const SWITCH_TO_PARENT_FRAME = 'switchToParentFrame';
|
||||
public const GET_ACTIVE_ELEMENT = 'getActiveElement';
|
||||
// Information of the page
|
||||
public const GET_CURRENT_URL = 'getCurrentUrl';
|
||||
public const GET_PAGE_SOURCE = 'getPageSource';
|
||||
public const GET_TITLE = 'getTitle';
|
||||
// Javascript API
|
||||
public const EXECUTE_SCRIPT = 'executeScript';
|
||||
public const EXECUTE_ASYNC_SCRIPT = 'executeAsyncScript';
|
||||
// API getting information from an element.
|
||||
public const GET_ELEMENT_TEXT = 'getElementText';
|
||||
public const GET_ELEMENT_TAG_NAME = 'getElementTagName';
|
||||
public const IS_ELEMENT_SELECTED = 'isElementSelected';
|
||||
public const IS_ELEMENT_ENABLED = 'isElementEnabled';
|
||||
public const IS_ELEMENT_DISPLAYED = 'isElementDisplayed';
|
||||
public const GET_ELEMENT_LOCATION = 'getElementLocation';
|
||||
public const GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = 'getElementLocationOnceScrolledIntoView';
|
||||
public const GET_ELEMENT_SIZE = 'getElementSize';
|
||||
public const GET_ELEMENT_ATTRIBUTE = 'getElementAttribute';
|
||||
public const GET_ELEMENT_VALUE_OF_CSS_PROPERTY = 'getElementValueOfCssProperty';
|
||||
public const ELEMENT_EQUALS = 'elementEquals';
|
||||
public const SCREENSHOT = 'screenshot';
|
||||
// Alert API
|
||||
public const ACCEPT_ALERT = 'acceptAlert';
|
||||
public const DISMISS_ALERT = 'dismissAlert';
|
||||
public const GET_ALERT_TEXT = 'getAlertText';
|
||||
public const SET_ALERT_VALUE = 'setAlertValue';
|
||||
// Timeout API
|
||||
public const SET_TIMEOUT = 'setTimeout';
|
||||
public const IMPLICITLY_WAIT = 'implicitlyWait';
|
||||
public const SET_SCRIPT_TIMEOUT = 'setScriptTimeout';
|
||||
/** @deprecated */
|
||||
public const EXECUTE_SQL = 'executeSQL';
|
||||
public const GET_LOCATION = 'getLocation';
|
||||
public const SET_LOCATION = 'setLocation';
|
||||
public const GET_APP_CACHE = 'getAppCache';
|
||||
public const GET_APP_CACHE_STATUS = 'getStatus';
|
||||
public const CLEAR_APP_CACHE = 'clearAppCache';
|
||||
public const IS_BROWSER_ONLINE = 'isBrowserOnline';
|
||||
public const SET_BROWSER_ONLINE = 'setBrowserOnline';
|
||||
// Local storage
|
||||
public const GET_LOCAL_STORAGE_ITEM = 'getLocalStorageItem';
|
||||
public const GET_LOCAL_STORAGE_KEYS = 'getLocalStorageKeys';
|
||||
public const SET_LOCAL_STORAGE_ITEM = 'setLocalStorageItem';
|
||||
public const REMOVE_LOCAL_STORAGE_ITEM = 'removeLocalStorageItem';
|
||||
public const CLEAR_LOCAL_STORAGE = 'clearLocalStorage';
|
||||
public const GET_LOCAL_STORAGE_SIZE = 'getLocalStorageSize';
|
||||
// Session storage
|
||||
public const GET_SESSION_STORAGE_ITEM = 'getSessionStorageItem';
|
||||
public const GET_SESSION_STORAGE_KEYS = 'getSessionStorageKey';
|
||||
public const SET_SESSION_STORAGE_ITEM = 'setSessionStorageItem';
|
||||
public const REMOVE_SESSION_STORAGE_ITEM = 'removeSessionStorageItem';
|
||||
public const CLEAR_SESSION_STORAGE = 'clearSessionStorage';
|
||||
public const GET_SESSION_STORAGE_SIZE = 'getSessionStorageSize';
|
||||
// Screen orientation
|
||||
public const SET_SCREEN_ORIENTATION = 'setScreenOrientation';
|
||||
public const GET_SCREEN_ORIENTATION = 'getScreenOrientation';
|
||||
// These belong to the Advanced user interactions - an element is optional for these commands.
|
||||
public const CLICK = 'mouseClick';
|
||||
public const DOUBLE_CLICK = 'mouseDoubleClick';
|
||||
public const MOUSE_DOWN = 'mouseButtonDown';
|
||||
public const MOUSE_UP = 'mouseButtonUp';
|
||||
public const MOVE_TO = 'mouseMoveTo';
|
||||
// Those allow interactions with the Input Methods installed on the system.
|
||||
public const IME_GET_AVAILABLE_ENGINES = 'imeGetAvailableEngines';
|
||||
public const IME_GET_ACTIVE_ENGINE = 'imeGetActiveEngine';
|
||||
public const IME_IS_ACTIVATED = 'imeIsActivated';
|
||||
public const IME_DEACTIVATE = 'imeDeactivate';
|
||||
public const IME_ACTIVATE_ENGINE = 'imeActivateEngine';
|
||||
// These belong to the Advanced Touch API
|
||||
public const TOUCH_SINGLE_TAP = 'touchSingleTap';
|
||||
public const TOUCH_DOWN = 'touchDown';
|
||||
public const TOUCH_UP = 'touchUp';
|
||||
public const TOUCH_MOVE = 'touchMove';
|
||||
public const TOUCH_SCROLL = 'touchScroll';
|
||||
public const TOUCH_DOUBLE_TAP = 'touchDoubleTap';
|
||||
public const TOUCH_LONG_PRESS = 'touchLongPress';
|
||||
public const TOUCH_FLICK = 'touchFlick';
|
||||
// Window API (beta)
|
||||
public const SET_WINDOW_SIZE = 'setWindowSize';
|
||||
public const SET_WINDOW_POSITION = 'setWindowPosition';
|
||||
public const GET_WINDOW_SIZE = 'getWindowSize';
|
||||
public const GET_WINDOW_POSITION = 'getWindowPosition';
|
||||
public const MAXIMIZE_WINDOW = 'maximizeWindow';
|
||||
public const FULLSCREEN_WINDOW = 'fullscreenWindow';
|
||||
// Logging API
|
||||
public const GET_AVAILABLE_LOG_TYPES = 'getAvailableLogTypes';
|
||||
public const GET_LOG = 'getLog';
|
||||
public const GET_SESSION_LOGS = 'getSessionLogs';
|
||||
// Mobile API
|
||||
public const GET_NETWORK_CONNECTION = 'getNetworkConnection';
|
||||
public const SET_NETWORK_CONNECTION = 'setNetworkConnection';
|
||||
// Custom command
|
||||
public const CUSTOM_COMMAND = 'customCommand';
|
||||
|
||||
// W3C specific
|
||||
public const ACTIONS = 'actions';
|
||||
public const GET_ELEMENT_PROPERTY = 'getElementProperty';
|
||||
public const GET_NAMED_COOKIE = 'getNamedCookie';
|
||||
public const NEW_WINDOW = 'newWindow';
|
||||
public const TAKE_ELEMENT_SCREENSHOT = 'takeElementScreenshot';
|
||||
public const MINIMIZE_WINDOW = 'minimizeWindow';
|
||||
public const GET_ELEMENT_SHADOW_ROOT = 'getElementShadowRoot';
|
||||
public const FIND_ELEMENT_FROM_SHADOW_ROOT = 'findElementFromShadowRoot';
|
||||
public const FIND_ELEMENTS_FROM_SHADOW_ROOT = 'findElementsFromShadowRoot';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
interface ExecuteMethod
|
||||
{
|
||||
/**
|
||||
* @param string $command_name
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function execute($command_name, array $parameters = []);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
interface FileDetector
|
||||
{
|
||||
/**
|
||||
* Try to detect whether the given $file is a file or not. Return the path
|
||||
* of the file. Otherwise, return null.
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return null|string The path of the file.
|
||||
*/
|
||||
public function getLocalFile($file);
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\LogicException;
|
||||
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
|
||||
use Facebook\WebDriver\Exception\Internal\WebDriverCurlException;
|
||||
use Facebook\WebDriver\Exception\WebDriverException;
|
||||
use Facebook\WebDriver\WebDriverCommandExecutor;
|
||||
|
||||
/**
|
||||
* Command executor talking to the standalone server via HTTP.
|
||||
*/
|
||||
class HttpCommandExecutor implements WebDriverCommandExecutor
|
||||
{
|
||||
public const DEFAULT_HTTP_HEADERS = [
|
||||
'Content-Type: application/json;charset=UTF-8',
|
||||
'Accept: application/json',
|
||||
];
|
||||
|
||||
/**
|
||||
* @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#command-reference
|
||||
*/
|
||||
protected static $commands = [
|
||||
DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/accept_alert'],
|
||||
DriverCommand::ADD_COOKIE => ['method' => 'POST', 'url' => '/session/:sessionId/cookie'],
|
||||
DriverCommand::CLEAR_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/clear'],
|
||||
DriverCommand::CLICK_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/click'],
|
||||
DriverCommand::CLOSE => ['method' => 'DELETE', 'url' => '/session/:sessionId/window'],
|
||||
DriverCommand::DELETE_ALL_COOKIES => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie'],
|
||||
DriverCommand::DELETE_COOKIE => ['method' => 'DELETE', 'url' => '/session/:sessionId/cookie/:name'],
|
||||
DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/dismiss_alert'],
|
||||
DriverCommand::ELEMENT_EQUALS => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/equals/:other'],
|
||||
DriverCommand::FIND_CHILD_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/element'],
|
||||
DriverCommand::FIND_CHILD_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/elements'],
|
||||
DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute'],
|
||||
DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute_async'],
|
||||
DriverCommand::FIND_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element'],
|
||||
DriverCommand::FIND_ELEMENTS => ['method' => 'POST', 'url' => '/session/:sessionId/elements'],
|
||||
DriverCommand::SWITCH_TO_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame'],
|
||||
DriverCommand::SWITCH_TO_PARENT_FRAME => ['method' => 'POST', 'url' => '/session/:sessionId/frame/parent'],
|
||||
DriverCommand::SWITCH_TO_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window'],
|
||||
DriverCommand::GET => ['method' => 'POST', 'url' => '/session/:sessionId/url'],
|
||||
DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/active'],
|
||||
DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert_text'],
|
||||
DriverCommand::GET_ALL_COOKIES => ['method' => 'GET', 'url' => '/session/:sessionId/cookie'],
|
||||
DriverCommand::GET_NAMED_COOKIE => ['method' => 'GET', 'url' => '/session/:sessionId/cookie/:name'],
|
||||
DriverCommand::GET_ALL_SESSIONS => ['method' => 'GET', 'url' => '/sessions'],
|
||||
DriverCommand::GET_AVAILABLE_LOG_TYPES => ['method' => 'GET', 'url' => '/session/:sessionId/log/types'],
|
||||
DriverCommand::GET_CURRENT_URL => ['method' => 'GET', 'url' => '/session/:sessionId/url'],
|
||||
DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window_handle'],
|
||||
DriverCommand::GET_ELEMENT_ATTRIBUTE => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/attribute/:name',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/css/:propertyName',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_LOCATION => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/location',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/location_in_view',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/size'],
|
||||
DriverCommand::GET_ELEMENT_TAG_NAME => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/name'],
|
||||
DriverCommand::GET_ELEMENT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/text'],
|
||||
DriverCommand::GET_LOG => ['method' => 'POST', 'url' => '/session/:sessionId/log'],
|
||||
DriverCommand::GET_PAGE_SOURCE => ['method' => 'GET', 'url' => '/session/:sessionId/source'],
|
||||
DriverCommand::GET_SCREEN_ORIENTATION => ['method' => 'GET', 'url' => '/session/:sessionId/orientation'],
|
||||
DriverCommand::GET_CAPABILITIES => ['method' => 'GET', 'url' => '/session/:sessionId'],
|
||||
DriverCommand::GET_TITLE => ['method' => 'GET', 'url' => '/session/:sessionId/title'],
|
||||
DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window_handles'],
|
||||
DriverCommand::GET_WINDOW_POSITION => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/window/:windowHandle/position',
|
||||
],
|
||||
DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/:windowHandle/size'],
|
||||
DriverCommand::GO_BACK => ['method' => 'POST', 'url' => '/session/:sessionId/back'],
|
||||
DriverCommand::GO_FORWARD => ['method' => 'POST', 'url' => '/session/:sessionId/forward'],
|
||||
DriverCommand::IS_ELEMENT_DISPLAYED => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/displayed',
|
||||
],
|
||||
DriverCommand::IS_ELEMENT_ENABLED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/enabled'],
|
||||
DriverCommand::IS_ELEMENT_SELECTED => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/selected'],
|
||||
DriverCommand::MAXIMIZE_WINDOW => [
|
||||
'method' => 'POST',
|
||||
'url' => '/session/:sessionId/window/:windowHandle/maximize',
|
||||
],
|
||||
DriverCommand::MOUSE_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/buttondown'],
|
||||
DriverCommand::MOUSE_UP => ['method' => 'POST', 'url' => '/session/:sessionId/buttonup'],
|
||||
DriverCommand::CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/click'],
|
||||
DriverCommand::DOUBLE_CLICK => ['method' => 'POST', 'url' => '/session/:sessionId/doubleclick'],
|
||||
DriverCommand::MOVE_TO => ['method' => 'POST', 'url' => '/session/:sessionId/moveto'],
|
||||
DriverCommand::NEW_SESSION => ['method' => 'POST', 'url' => '/session'],
|
||||
DriverCommand::QUIT => ['method' => 'DELETE', 'url' => '/session/:sessionId'],
|
||||
DriverCommand::REFRESH => ['method' => 'POST', 'url' => '/session/:sessionId/refresh'],
|
||||
DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/file'], // undocumented
|
||||
DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/keys'],
|
||||
DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert_text'],
|
||||
DriverCommand::SEND_KEYS_TO_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/value'],
|
||||
DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/implicit_wait'],
|
||||
DriverCommand::SET_SCREEN_ORIENTATION => ['method' => 'POST', 'url' => '/session/:sessionId/orientation'],
|
||||
DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'],
|
||||
DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts/async_script'],
|
||||
DriverCommand::SET_WINDOW_POSITION => [
|
||||
'method' => 'POST',
|
||||
'url' => '/session/:sessionId/window/:windowHandle/position',
|
||||
],
|
||||
DriverCommand::SET_WINDOW_SIZE => [
|
||||
'method' => 'POST',
|
||||
'url' => '/session/:sessionId/window/:windowHandle/size',
|
||||
],
|
||||
DriverCommand::STATUS => ['method' => 'GET', 'url' => '/status'],
|
||||
DriverCommand::SUBMIT_ELEMENT => ['method' => 'POST', 'url' => '/session/:sessionId/element/:id/submit'],
|
||||
DriverCommand::SCREENSHOT => ['method' => 'GET', 'url' => '/session/:sessionId/screenshot'],
|
||||
DriverCommand::TAKE_ELEMENT_SCREENSHOT => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/screenshot',
|
||||
],
|
||||
DriverCommand::TOUCH_SINGLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/click'],
|
||||
DriverCommand::TOUCH_DOWN => ['method' => 'POST', 'url' => '/session/:sessionId/touch/down'],
|
||||
DriverCommand::TOUCH_DOUBLE_TAP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/doubleclick'],
|
||||
DriverCommand::TOUCH_FLICK => ['method' => 'POST', 'url' => '/session/:sessionId/touch/flick'],
|
||||
DriverCommand::TOUCH_LONG_PRESS => ['method' => 'POST', 'url' => '/session/:sessionId/touch/longclick'],
|
||||
DriverCommand::TOUCH_MOVE => ['method' => 'POST', 'url' => '/session/:sessionId/touch/move'],
|
||||
DriverCommand::TOUCH_SCROLL => ['method' => 'POST', 'url' => '/session/:sessionId/touch/scroll'],
|
||||
DriverCommand::TOUCH_UP => ['method' => 'POST', 'url' => '/session/:sessionId/touch/up'],
|
||||
DriverCommand::CUSTOM_COMMAND => [],
|
||||
];
|
||||
/**
|
||||
* @var array Will be merged with $commands
|
||||
*/
|
||||
protected static $w3cCompliantCommands = [
|
||||
DriverCommand::ACCEPT_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/accept'],
|
||||
DriverCommand::ACTIONS => ['method' => 'POST', 'url' => '/session/:sessionId/actions'],
|
||||
DriverCommand::DISMISS_ALERT => ['method' => 'POST', 'url' => '/session/:sessionId/alert/dismiss'],
|
||||
DriverCommand::EXECUTE_ASYNC_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/async'],
|
||||
DriverCommand::EXECUTE_SCRIPT => ['method' => 'POST', 'url' => '/session/:sessionId/execute/sync'],
|
||||
DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT => [
|
||||
'method' => 'POST',
|
||||
'url' => '/session/:sessionId/shadow/:id/element',
|
||||
],
|
||||
DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT => [
|
||||
'method' => 'POST',
|
||||
'url' => '/session/:sessionId/shadow/:id/elements',
|
||||
],
|
||||
DriverCommand::FULLSCREEN_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/fullscreen'],
|
||||
DriverCommand::GET_ACTIVE_ELEMENT => ['method' => 'GET', 'url' => '/session/:sessionId/element/active'],
|
||||
DriverCommand::GET_ALERT_TEXT => ['method' => 'GET', 'url' => '/session/:sessionId/alert/text'],
|
||||
DriverCommand::GET_CURRENT_WINDOW_HANDLE => ['method' => 'GET', 'url' => '/session/:sessionId/window'],
|
||||
DriverCommand::GET_ELEMENT_LOCATION => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'],
|
||||
DriverCommand::GET_ELEMENT_PROPERTY => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/property/:name',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_SHADOW_ROOT => [
|
||||
'method' => 'GET',
|
||||
'url' => '/session/:sessionId/element/:id/shadow',
|
||||
],
|
||||
DriverCommand::GET_ELEMENT_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/element/:id/rect'],
|
||||
DriverCommand::GET_WINDOW_HANDLES => ['method' => 'GET', 'url' => '/session/:sessionId/window/handles'],
|
||||
DriverCommand::GET_WINDOW_POSITION => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'],
|
||||
DriverCommand::GET_WINDOW_SIZE => ['method' => 'GET', 'url' => '/session/:sessionId/window/rect'],
|
||||
DriverCommand::IMPLICITLY_WAIT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'],
|
||||
DriverCommand::MAXIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/maximize'],
|
||||
DriverCommand::MINIMIZE_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/minimize'],
|
||||
DriverCommand::NEW_WINDOW => ['method' => 'POST', 'url' => '/session/:sessionId/window/new'],
|
||||
DriverCommand::SET_ALERT_VALUE => ['method' => 'POST', 'url' => '/session/:sessionId/alert/text'],
|
||||
DriverCommand::SET_SCRIPT_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'],
|
||||
DriverCommand::SET_TIMEOUT => ['method' => 'POST', 'url' => '/session/:sessionId/timeouts'],
|
||||
DriverCommand::SET_WINDOW_SIZE => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'],
|
||||
DriverCommand::SET_WINDOW_POSITION => ['method' => 'POST', 'url' => '/session/:sessionId/window/rect'],
|
||||
// Selenium extension of W3C protocol
|
||||
DriverCommand::UPLOAD_FILE => ['method' => 'POST', 'url' => '/session/:sessionId/se/file'],
|
||||
];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $url;
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $curl;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isW3cCompliant = true;
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string|null $http_proxy
|
||||
* @param int|null $http_proxy_port
|
||||
*/
|
||||
public function __construct($url, $http_proxy = null, $http_proxy_port = null)
|
||||
{
|
||||
self::$w3cCompliantCommands = array_merge(self::$commands, self::$w3cCompliantCommands);
|
||||
|
||||
$this->url = $url;
|
||||
$this->curl = curl_init();
|
||||
|
||||
if (!empty($http_proxy)) {
|
||||
curl_setopt($this->curl, CURLOPT_PROXY, $http_proxy);
|
||||
if ($http_proxy_port !== null) {
|
||||
curl_setopt($this->curl, CURLOPT_PROXYPORT, $http_proxy_port);
|
||||
}
|
||||
}
|
||||
|
||||
// Get credentials from $url (if any)
|
||||
$matches = null;
|
||||
if (preg_match("/^(https?:\/\/)(.*):(.*)@(.*?)/U", $url, $matches)) {
|
||||
$this->url = $matches[1] . $matches[4];
|
||||
$auth_creds = $matches[2] . ':' . $matches[3];
|
||||
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
curl_setopt($this->curl, CURLOPT_USERPWD, $auth_creds);
|
||||
}
|
||||
|
||||
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS);
|
||||
|
||||
$this->setConnectionTimeout(30 * 1000); // 30 seconds
|
||||
$this->setRequestTimeout(180 * 1000); // 3 minutes
|
||||
}
|
||||
|
||||
public function disableW3cCompliance()
|
||||
{
|
||||
$this->isW3cCompliant = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timeout for the connect phase
|
||||
*
|
||||
* @param int $timeout_in_ms Timeout in milliseconds
|
||||
* @return HttpCommandExecutor
|
||||
*/
|
||||
public function setConnectionTimeout($timeout_in_ms)
|
||||
{
|
||||
// There is a PHP bug in some versions which didn't define the constant.
|
||||
curl_setopt(
|
||||
$this->curl,
|
||||
/* CURLOPT_CONNECTTIMEOUT_MS */
|
||||
156,
|
||||
$timeout_in_ms
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum time of a request
|
||||
*
|
||||
* @param int $timeout_in_ms Timeout in milliseconds
|
||||
* @return HttpCommandExecutor
|
||||
*/
|
||||
public function setRequestTimeout($timeout_in_ms)
|
||||
{
|
||||
// There is a PHP bug in some versions (at least for PHP 5.3.3) which
|
||||
// didn't define the constant.
|
||||
curl_setopt(
|
||||
$this->curl,
|
||||
/* CURLOPT_TIMEOUT_MS */
|
||||
155,
|
||||
$timeout_in_ms
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function execute(WebDriverCommand $command)
|
||||
{
|
||||
$http_options = $this->getCommandHttpOptions($command);
|
||||
$http_method = $http_options['method'];
|
||||
$url = $http_options['url'];
|
||||
|
||||
$sessionID = $command->getSessionID();
|
||||
$url = str_replace(':sessionId', $sessionID ?? '', $url);
|
||||
$params = $command->getParameters();
|
||||
foreach ($params as $name => $value) {
|
||||
if ($name[0] === ':') {
|
||||
$url = str_replace($name, $value, $url);
|
||||
unset($params[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($params) && !empty($params) && $http_method !== 'POST') {
|
||||
throw LogicException::forInvalidHttpMethod($url, $http_method, $params);
|
||||
}
|
||||
|
||||
curl_setopt($this->curl, CURLOPT_URL, $this->url . $url);
|
||||
|
||||
// https://github.com/facebook/php-webdriver/issues/173
|
||||
if ($command->getName() === DriverCommand::NEW_SESSION) {
|
||||
curl_setopt($this->curl, CURLOPT_POST, 1);
|
||||
} else {
|
||||
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $http_method);
|
||||
}
|
||||
|
||||
if (in_array($http_method, ['POST', 'PUT'], true)) {
|
||||
// Disable sending 'Expect: 100-Continue' header, as it is causing issues with eg. squid proxy
|
||||
// https://tools.ietf.org/html/rfc7231#section-5.1.1
|
||||
curl_setopt($this->curl, CURLOPT_HTTPHEADER, array_merge(static::DEFAULT_HTTP_HEADERS, ['Expect:']));
|
||||
} else {
|
||||
curl_setopt($this->curl, CURLOPT_HTTPHEADER, static::DEFAULT_HTTP_HEADERS);
|
||||
}
|
||||
|
||||
$encoded_params = null;
|
||||
|
||||
if ($http_method === 'POST') {
|
||||
if (is_array($params) && !empty($params)) {
|
||||
$encoded_params = json_encode($params);
|
||||
} elseif ($this->isW3cCompliant) {
|
||||
// POST body must be valid JSON in W3C, even if empty: https://www.w3.org/TR/webdriver/#processing-model
|
||||
$encoded_params = '{}';
|
||||
}
|
||||
}
|
||||
|
||||
curl_setopt($this->curl, CURLOPT_POSTFIELDS, $encoded_params);
|
||||
|
||||
$raw_results = trim(curl_exec($this->curl));
|
||||
|
||||
if ($error = curl_error($this->curl)) {
|
||||
throw WebDriverCurlException::forCurlError($http_method, $url, $error, is_array($params) ? $params : null);
|
||||
}
|
||||
|
||||
$results = json_decode($raw_results, true);
|
||||
|
||||
if ($results === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw UnexpectedResponseException::forJsonDecodingError(json_last_error(), $raw_results);
|
||||
}
|
||||
|
||||
$value = null;
|
||||
if (is_array($results) && array_key_exists('value', $results)) {
|
||||
$value = $results['value'];
|
||||
}
|
||||
|
||||
$message = null;
|
||||
if (is_array($value) && array_key_exists('message', $value)) {
|
||||
$message = $value['message'];
|
||||
}
|
||||
|
||||
$sessionId = null;
|
||||
if (is_array($value) && array_key_exists('sessionId', $value)) {
|
||||
// W3C's WebDriver
|
||||
$sessionId = $value['sessionId'];
|
||||
} elseif (is_array($results) && array_key_exists('sessionId', $results)) {
|
||||
// Legacy JsonWire
|
||||
$sessionId = $results['sessionId'];
|
||||
}
|
||||
|
||||
// @see https://w3c.github.io/webdriver/#errors
|
||||
if (isset($value['error'])) {
|
||||
// W3C's WebDriver
|
||||
WebDriverException::throwException($value['error'], $message, $results);
|
||||
}
|
||||
|
||||
$status = $results['status'] ?? 0;
|
||||
if ($status !== 0) {
|
||||
// Legacy JsonWire
|
||||
WebDriverException::throwException($status, $message, $results);
|
||||
}
|
||||
|
||||
$response = new WebDriverResponse($sessionId);
|
||||
|
||||
return $response
|
||||
->setStatus($status)
|
||||
->setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddressOfRemoteServer()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getCommandHttpOptions(WebDriverCommand $command)
|
||||
{
|
||||
$commandName = $command->getName();
|
||||
if (!isset(self::$commands[$commandName])) {
|
||||
if ($this->isW3cCompliant && !isset(self::$w3cCompliantCommands[$commandName])) {
|
||||
throw LogicException::forError($command->getName() . ' is not a valid command.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isW3cCompliant) {
|
||||
$raw = self::$w3cCompliantCommands[$command->getName()];
|
||||
} else {
|
||||
$raw = self::$commands[$command->getName()];
|
||||
}
|
||||
|
||||
if ($command instanceof CustomWebDriverCommand) {
|
||||
$url = $command->getCustomUrl();
|
||||
$method = $command->getCustomMethod();
|
||||
} else {
|
||||
$url = $raw['url'];
|
||||
$method = $raw['method'];
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $url,
|
||||
'method' => $method,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
|
||||
/**
|
||||
* Compatibility layer between W3C's WebDriver and the legacy JsonWire protocol.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class JsonWireCompat
|
||||
{
|
||||
/**
|
||||
* Element identifier defined in the W3C's WebDriver protocol.
|
||||
*
|
||||
* @see https://w3c.github.io/webdriver/#elements
|
||||
*/
|
||||
public const WEB_DRIVER_ELEMENT_IDENTIFIER = 'element-6066-11e4-a52e-4f735466cecf';
|
||||
|
||||
/**
|
||||
* @param mixed $rawElement Value is validated to by an array, exception is thrown otherwise
|
||||
* @throws UnexpectedResponseException When value of other type than array is given
|
||||
*/
|
||||
public static function getElement($rawElement)
|
||||
{
|
||||
// The method intentionally accept mixed, so that assertion of the rawElement format could be done on one place
|
||||
if (!is_array($rawElement)) {
|
||||
throw UnexpectedResponseException::forElementNotArray($rawElement);
|
||||
}
|
||||
|
||||
if (array_key_exists(self::WEB_DRIVER_ELEMENT_IDENTIFIER, $rawElement)) {
|
||||
// W3C's WebDriver
|
||||
return $rawElement[self::WEB_DRIVER_ELEMENT_IDENTIFIER];
|
||||
}
|
||||
|
||||
// Legacy JsonWire
|
||||
return $rawElement['ELEMENT'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isW3cCompliant
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getUsing(WebDriverBy $by, $isW3cCompliant)
|
||||
{
|
||||
$mechanism = $by->getMechanism();
|
||||
$value = $by->getValue();
|
||||
|
||||
if ($isW3cCompliant) {
|
||||
switch ($mechanism) {
|
||||
// Convert to CSS selectors
|
||||
case 'class name':
|
||||
$mechanism = 'css selector';
|
||||
$value = sprintf('.%s', self::escapeSelector($value));
|
||||
break;
|
||||
case 'id':
|
||||
$mechanism = 'css selector';
|
||||
$value = sprintf('#%s', self::escapeSelector($value));
|
||||
break;
|
||||
case 'name':
|
||||
$mechanism = 'css selector';
|
||||
$value = sprintf('[name=\'%s\']', self::escapeSelector($value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ['using' => $mechanism, 'value' => $value];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a CSS selector.
|
||||
*
|
||||
* Code adapted from the Zend Escaper project.
|
||||
*
|
||||
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
|
||||
* @see https://github.com/zendframework/zend-escaper/blob/master/src/Escaper.php
|
||||
*
|
||||
* @param string $selector
|
||||
* @return string
|
||||
*/
|
||||
private static function escapeSelector($selector)
|
||||
{
|
||||
return preg_replace_callback('/[^a-z0-9]/iSu', function ($matches) {
|
||||
$chr = $matches[0];
|
||||
if (mb_strlen($chr) === 1) {
|
||||
$ord = ord($chr);
|
||||
} else {
|
||||
$chr = mb_convert_encoding($chr, 'UTF-32BE', 'UTF-8');
|
||||
$ord = hexdec(bin2hex($chr));
|
||||
}
|
||||
|
||||
return sprintf('\\%X ', $ord);
|
||||
}, $selector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
class LocalFileDetector implements FileDetector
|
||||
{
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getLocalFile($file)
|
||||
{
|
||||
if (is_file($file)) {
|
||||
return realpath($file);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
class RemoteExecuteMethod implements ExecuteMethod
|
||||
{
|
||||
/**
|
||||
* @var RemoteWebDriver
|
||||
*/
|
||||
private $driver;
|
||||
|
||||
public function __construct(RemoteWebDriver $driver)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $command_name
|
||||
* @return mixed
|
||||
*/
|
||||
public function execute($command_name, array $parameters = [])
|
||||
{
|
||||
return $this->driver->execute($command_name, $parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\WebDriver;
|
||||
use Facebook\WebDriver\WebDriverKeyboard;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
|
||||
/**
|
||||
* Execute keyboard commands for RemoteWebDriver.
|
||||
*/
|
||||
class RemoteKeyboard implements WebDriverKeyboard
|
||||
{
|
||||
/** @var RemoteExecuteMethod */
|
||||
private $executor;
|
||||
/** @var WebDriver */
|
||||
private $driver;
|
||||
/** @var bool */
|
||||
private $isW3cCompliant;
|
||||
|
||||
/**
|
||||
* @param bool $isW3cCompliant
|
||||
*/
|
||||
public function __construct(RemoteExecuteMethod $executor, WebDriver $driver, $isW3cCompliant = false)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->driver = $driver;
|
||||
$this->isW3cCompliant = $isW3cCompliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send keys to active element
|
||||
* @param string|array $keys
|
||||
* @return $this
|
||||
*/
|
||||
public function sendKeys($keys)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$activeElement = $this->driver->switchTo()->activeElement();
|
||||
$activeElement->sendKeys($keys);
|
||||
} else {
|
||||
$this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [
|
||||
'value' => WebDriverKeys::encode($keys),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Press a modifier key
|
||||
*
|
||||
* @see WebDriverKeys
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function pressKey($key)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'key',
|
||||
'id' => 'keyboard',
|
||||
'actions' => [['type' => 'keyDown', 'value' => $key]],
|
||||
],
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
$this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [
|
||||
'value' => [(string) $key],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a modifier key
|
||||
*
|
||||
* @see WebDriverKeys
|
||||
* @param string $key
|
||||
* @return $this
|
||||
*/
|
||||
public function releaseKey($key)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'key',
|
||||
'id' => 'keyboard',
|
||||
'actions' => [['type' => 'keyUp', 'value' => $key]],
|
||||
],
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
$this->executor->execute(DriverCommand::SEND_KEYS_TO_ACTIVE_ELEMENT, [
|
||||
'value' => [(string) $key],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
|
||||
use Facebook\WebDriver\WebDriverMouse;
|
||||
|
||||
/**
|
||||
* Execute mouse commands for RemoteWebDriver.
|
||||
*/
|
||||
class RemoteMouse implements WebDriverMouse
|
||||
{
|
||||
/** @internal */
|
||||
public const BUTTON_LEFT = 0;
|
||||
/** @internal */
|
||||
public const BUTTON_MIDDLE = 1;
|
||||
/** @internal */
|
||||
public const BUTTON_RIGHT = 2;
|
||||
|
||||
/**
|
||||
* @var RemoteExecuteMethod
|
||||
*/
|
||||
private $executor;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isW3cCompliant;
|
||||
|
||||
/**
|
||||
* @param bool $isW3cCompliant
|
||||
*/
|
||||
public function __construct(RemoteExecuteMethod $executor, $isW3cCompliant = false)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->isW3cCompliant = $isW3cCompliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function click(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$moveAction = $where ? [$this->createMoveAction($where)] : [];
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => array_merge($moveAction, $this->createClickActions()),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->moveIfNeeded($where);
|
||||
$this->executor->execute(DriverCommand::CLICK, [
|
||||
'button' => self::BUTTON_LEFT,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function contextClick(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$moveAction = $where ? [$this->createMoveAction($where)] : [];
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => array_merge($moveAction, [
|
||||
[
|
||||
'type' => 'pointerDown',
|
||||
'button' => self::BUTTON_RIGHT,
|
||||
],
|
||||
[
|
||||
'type' => 'pointerUp',
|
||||
'button' => self::BUTTON_RIGHT,
|
||||
],
|
||||
]),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->moveIfNeeded($where);
|
||||
$this->executor->execute(DriverCommand::CLICK, [
|
||||
'button' => self::BUTTON_RIGHT,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function doubleClick(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$clickActions = $this->createClickActions();
|
||||
$moveAction = $where === null ? [] : [$this->createMoveAction($where)];
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => array_merge($moveAction, $clickActions, $clickActions),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->moveIfNeeded($where);
|
||||
$this->executor->execute(DriverCommand::DOUBLE_CLICK);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function mouseDown(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => [
|
||||
$this->createMoveAction($where),
|
||||
[
|
||||
'type' => 'pointerDown',
|
||||
'button' => self::BUTTON_LEFT,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->moveIfNeeded($where);
|
||||
$this->executor->execute(DriverCommand::MOUSE_DOWN);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $x_offset
|
||||
* @param int|null $y_offset
|
||||
*
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function mouseMove(
|
||||
?WebDriverCoordinates $where = null,
|
||||
$x_offset = null,
|
||||
$y_offset = null
|
||||
) {
|
||||
if ($this->isW3cCompliant) {
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => [$this->createMoveAction($where, $x_offset, $y_offset)],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
if ($where !== null) {
|
||||
$params['element'] = $where->getAuxiliary();
|
||||
}
|
||||
if ($x_offset !== null) {
|
||||
$params['xoffset'] = $x_offset;
|
||||
}
|
||||
if ($y_offset !== null) {
|
||||
$params['yoffset'] = $y_offset;
|
||||
}
|
||||
|
||||
$this->executor->execute(DriverCommand::MOVE_TO, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function mouseUp(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$moveAction = $where ? [$this->createMoveAction($where)] : [];
|
||||
|
||||
$this->executor->execute(DriverCommand::ACTIONS, [
|
||||
'actions' => [
|
||||
[
|
||||
'type' => 'pointer',
|
||||
'id' => 'mouse',
|
||||
'parameters' => ['pointerType' => 'mouse'],
|
||||
'actions' => array_merge($moveAction, [
|
||||
[
|
||||
'type' => 'pointerUp',
|
||||
'button' => self::BUTTON_LEFT,
|
||||
],
|
||||
]),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->moveIfNeeded($where);
|
||||
$this->executor->execute(DriverCommand::MOUSE_UP);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function moveIfNeeded(?WebDriverCoordinates $where = null)
|
||||
{
|
||||
if ($where) {
|
||||
$this->mouseMove($where);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $x_offset
|
||||
* @param int|null $y_offset
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function createMoveAction(
|
||||
?WebDriverCoordinates $where = null,
|
||||
$x_offset = null,
|
||||
$y_offset = null
|
||||
) {
|
||||
$move_action = [
|
||||
'type' => 'pointerMove',
|
||||
'duration' => 100, // to simulate human delay
|
||||
'x' => $x_offset ?? 0,
|
||||
'y' => $y_offset ?? 0,
|
||||
];
|
||||
|
||||
if ($where !== null) {
|
||||
$move_action['origin'] = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $where->getAuxiliary()];
|
||||
} else {
|
||||
$move_action['origin'] = 'pointer';
|
||||
}
|
||||
|
||||
return $move_action;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function createClickActions()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'type' => 'pointerDown',
|
||||
'button' => self::BUTTON_LEFT,
|
||||
],
|
||||
[
|
||||
'type' => 'pointerUp',
|
||||
'button' => self::BUTTON_LEFT,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
/**
|
||||
* Represents status of remote end
|
||||
*
|
||||
* @see https://www.w3.org/TR/webdriver/#status
|
||||
*/
|
||||
class RemoteStatus
|
||||
{
|
||||
/** @var bool */
|
||||
protected $isReady;
|
||||
/** @var string */
|
||||
protected $message;
|
||||
/** @var array */
|
||||
protected $meta = [];
|
||||
|
||||
/**
|
||||
* @param bool $isReady
|
||||
* @param string $message
|
||||
*/
|
||||
protected function __construct($isReady, $message, array $meta = [])
|
||||
{
|
||||
$this->isReady = (bool) $isReady;
|
||||
$this->message = (string) $message;
|
||||
|
||||
$this->setMeta($meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteStatus
|
||||
*/
|
||||
public static function createFromResponse(array $responseBody)
|
||||
{
|
||||
$object = new static($responseBody['ready'], $responseBody['message'], $responseBody);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* The remote end's readiness state.
|
||||
* False if an attempt to create a session at the current time would fail.
|
||||
* However, the value true does not guarantee that a New Session command will succeed.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReady()
|
||||
{
|
||||
return $this->isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation-defined string explaining the remote end's readiness state.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Arbitrary meta information specific to remote-end implementation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMeta()
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
protected function setMeta(array $meta)
|
||||
{
|
||||
unset($meta['ready'], $meta['message']);
|
||||
|
||||
$this->meta = $meta;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\LogicException;
|
||||
use Facebook\WebDriver\WebDriverAlert;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverTargetLocator;
|
||||
|
||||
/**
|
||||
* Used to locate a given frame or window for RemoteWebDriver.
|
||||
*/
|
||||
class RemoteTargetLocator implements WebDriverTargetLocator
|
||||
{
|
||||
/** @var RemoteExecuteMethod */
|
||||
protected $executor;
|
||||
/** @var RemoteWebDriver */
|
||||
protected $driver;
|
||||
/** @var bool */
|
||||
protected $isW3cCompliant;
|
||||
|
||||
public function __construct(RemoteExecuteMethod $executor, RemoteWebDriver $driver, $isW3cCompliant = false)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->driver = $driver;
|
||||
$this->isW3cCompliant = $isW3cCompliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteWebDriver
|
||||
*/
|
||||
public function defaultContent()
|
||||
{
|
||||
$params = ['id' => null];
|
||||
$this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params);
|
||||
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebDriverElement|null|int|string $frame The WebDriverElement, the id or the name of the frame.
|
||||
* When null, switch to the current top-level browsing context When int, switch to the WindowProxy identified
|
||||
* by the value. When an Element, switch to that Element.
|
||||
* @return RemoteWebDriver
|
||||
*/
|
||||
public function frame($frame)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
if ($frame instanceof WebDriverElement) {
|
||||
$id = [JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $frame->getID()];
|
||||
} elseif ($frame === null) {
|
||||
$id = null;
|
||||
} elseif (is_int($frame)) {
|
||||
$id = $frame;
|
||||
} else {
|
||||
throw LogicException::forError(
|
||||
'In W3C compliance mode frame must be either instance of WebDriverElement, integer or null'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ($frame instanceof WebDriverElement) {
|
||||
$id = ['ELEMENT' => $frame->getID()];
|
||||
} elseif ($frame === null) {
|
||||
$id = null;
|
||||
} elseif (is_int($frame)) {
|
||||
$id = $frame;
|
||||
} else {
|
||||
$id = (string) $frame;
|
||||
}
|
||||
}
|
||||
|
||||
$params = ['id' => $id];
|
||||
$this->executor->execute(DriverCommand::SWITCH_TO_FRAME, $params);
|
||||
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the parent iframe.
|
||||
*
|
||||
* @return RemoteWebDriver This driver focused on the parent frame
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
$this->executor->execute(DriverCommand::SWITCH_TO_PARENT_FRAME, []);
|
||||
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $handle The handle of the window to be focused on.
|
||||
* @return RemoteWebDriver
|
||||
*/
|
||||
public function window($handle)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$params = ['handle' => (string) $handle];
|
||||
} else {
|
||||
$params = ['name' => (string) $handle];
|
||||
}
|
||||
|
||||
$this->executor->execute(DriverCommand::SWITCH_TO_WINDOW, $params);
|
||||
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new browser window and switches the focus for future commands of this driver to the new window.
|
||||
*
|
||||
* @see https://w3c.github.io/webdriver/#new-window
|
||||
* @param string $windowType The type of a new browser window that should be created. One of [tab, window].
|
||||
* The created window is not guaranteed to be of the requested type; if the driver does not support the requested
|
||||
* type, a new browser window will be created of whatever type the driver does support.
|
||||
* @throws LogicException
|
||||
* @return RemoteWebDriver This driver focused on the given window
|
||||
*/
|
||||
public function newWindow($windowType = self::WINDOW_TYPE_TAB)
|
||||
{
|
||||
if ($windowType !== self::WINDOW_TYPE_TAB && $windowType !== self::WINDOW_TYPE_WINDOW) {
|
||||
throw LogicException::forError('Window type must by either "tab" or "window"');
|
||||
}
|
||||
|
||||
if (!$this->isW3cCompliant) {
|
||||
throw LogicException::forError('New window is only supported in W3C mode');
|
||||
}
|
||||
|
||||
$response = $this->executor->execute(DriverCommand::NEW_WINDOW, ['type' => $windowType]);
|
||||
|
||||
$this->window($response['handle']);
|
||||
|
||||
return $this->driver;
|
||||
}
|
||||
|
||||
public function alert()
|
||||
{
|
||||
return new WebDriverAlert($this->executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteWebElement
|
||||
*/
|
||||
public function activeElement()
|
||||
{
|
||||
$response = $this->driver->execute(DriverCommand::GET_ACTIVE_ELEMENT, []);
|
||||
$method = new RemoteExecuteMethod($this->driver);
|
||||
|
||||
return new RemoteWebElement($method, JsonWireCompat::getElement($response), $this->isW3cCompliant);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Interactions\Touch\WebDriverTouchScreen;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
|
||||
/**
|
||||
* Execute touch commands for RemoteWebDriver.
|
||||
*/
|
||||
class RemoteTouchScreen implements WebDriverTouchScreen
|
||||
{
|
||||
/**
|
||||
* @var RemoteExecuteMethod
|
||||
*/
|
||||
private $executor;
|
||||
|
||||
public function __construct(RemoteExecuteMethod $executor)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function tap(WebDriverElement $element)
|
||||
{
|
||||
$this->executor->execute(
|
||||
DriverCommand::TOUCH_SINGLE_TAP,
|
||||
['element' => $element->getID()]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function doubleTap(WebDriverElement $element)
|
||||
{
|
||||
$this->executor->execute(
|
||||
DriverCommand::TOUCH_DOUBLE_TAP,
|
||||
['element' => $element->getID()]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function down($x, $y)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_DOWN, [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xspeed
|
||||
* @param int $yspeed
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function flick($xspeed, $yspeed)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_FLICK, [
|
||||
'xspeed' => $xspeed,
|
||||
'yspeed' => $yspeed,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xoffset
|
||||
* @param int $yoffset
|
||||
* @param int $speed
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function flickFromElement(WebDriverElement $element, $xoffset, $yoffset, $speed)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_FLICK, [
|
||||
'xoffset' => $xoffset,
|
||||
'yoffset' => $yoffset,
|
||||
'element' => $element->getID(),
|
||||
'speed' => $speed,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function longPress(WebDriverElement $element)
|
||||
{
|
||||
$this->executor->execute(
|
||||
DriverCommand::TOUCH_LONG_PRESS,
|
||||
['element' => $element->getID()]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function move($x, $y)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_MOVE, [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xoffset
|
||||
* @param int $yoffset
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function scroll($xoffset, $yoffset)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_SCROLL, [
|
||||
'xoffset' => $xoffset,
|
||||
'yoffset' => $yoffset,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $xoffset
|
||||
* @param int $yoffset
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function scrollFromElement(WebDriverElement $element, $xoffset, $yoffset)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_SCROLL, [
|
||||
'element' => $element->getID(),
|
||||
'xoffset' => $xoffset,
|
||||
'yoffset' => $yoffset,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
*
|
||||
* @return RemoteTouchScreen The instance.
|
||||
*/
|
||||
public function up($x, $y)
|
||||
{
|
||||
$this->executor->execute(DriverCommand::TOUCH_UP, [
|
||||
'x' => $x,
|
||||
'y' => $y,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\JavaScriptExecutor;
|
||||
use Facebook\WebDriver\Support\IsElementDisplayedAtom;
|
||||
use Facebook\WebDriver\Support\ScreenshotHelper;
|
||||
use Facebook\WebDriver\WebDriver;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverCapabilities;
|
||||
use Facebook\WebDriver\WebDriverCommandExecutor;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverHasInputDevices;
|
||||
use Facebook\WebDriver\WebDriverNavigation;
|
||||
use Facebook\WebDriver\WebDriverOptions;
|
||||
use Facebook\WebDriver\WebDriverWait;
|
||||
|
||||
class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInputDevices
|
||||
{
|
||||
/**
|
||||
* @var HttpCommandExecutor|null
|
||||
*/
|
||||
protected $executor;
|
||||
/**
|
||||
* @var WebDriverCapabilities|null
|
||||
*/
|
||||
protected $capabilities;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionID;
|
||||
/**
|
||||
* @var RemoteMouse
|
||||
*/
|
||||
protected $mouse;
|
||||
/**
|
||||
* @var RemoteKeyboard
|
||||
*/
|
||||
protected $keyboard;
|
||||
/**
|
||||
* @var RemoteTouchScreen
|
||||
*/
|
||||
protected $touch;
|
||||
/**
|
||||
* @var RemoteExecuteMethod
|
||||
*/
|
||||
protected $executeMethod;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isW3cCompliant;
|
||||
|
||||
/**
|
||||
* @param string $sessionId
|
||||
* @param bool $isW3cCompliant false to use the legacy JsonWire protocol, true for the W3C WebDriver spec
|
||||
*/
|
||||
protected function __construct(
|
||||
HttpCommandExecutor $commandExecutor,
|
||||
$sessionId,
|
||||
WebDriverCapabilities $capabilities,
|
||||
$isW3cCompliant = false
|
||||
) {
|
||||
$this->executor = $commandExecutor;
|
||||
$this->sessionID = $sessionId;
|
||||
$this->isW3cCompliant = $isW3cCompliant;
|
||||
$this->capabilities = $capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the RemoteWebDriver by a desired capabilities.
|
||||
*
|
||||
* @param string $selenium_server_url The url of the remote Selenium WebDriver server
|
||||
* @param DesiredCapabilities|array $desired_capabilities The desired capabilities
|
||||
* @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server
|
||||
* @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server
|
||||
* @param string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through
|
||||
* @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through
|
||||
* @param DesiredCapabilities $required_capabilities The required capabilities
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function create(
|
||||
$selenium_server_url = 'http://localhost:4444/wd/hub',
|
||||
$desired_capabilities = null,
|
||||
$connection_timeout_in_ms = null,
|
||||
$request_timeout_in_ms = null,
|
||||
$http_proxy = null,
|
||||
$http_proxy_port = null,
|
||||
?DesiredCapabilities $required_capabilities = null
|
||||
) {
|
||||
$selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url);
|
||||
|
||||
$desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities);
|
||||
|
||||
$executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port);
|
||||
if ($connection_timeout_in_ms !== null) {
|
||||
$executor->setConnectionTimeout($connection_timeout_in_ms);
|
||||
}
|
||||
if ($request_timeout_in_ms !== null) {
|
||||
$executor->setRequestTimeout($request_timeout_in_ms);
|
||||
}
|
||||
|
||||
// W3C
|
||||
$parameters = [
|
||||
'capabilities' => [
|
||||
'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()],
|
||||
],
|
||||
];
|
||||
|
||||
if ($required_capabilities !== null && !empty($required_capabilities->toArray())) {
|
||||
$parameters['capabilities']['alwaysMatch'] = (object) $required_capabilities->toW3cCompatibleArray();
|
||||
}
|
||||
|
||||
// Legacy protocol
|
||||
if ($required_capabilities !== null) {
|
||||
// TODO: Selenium (as of v3.0.1) does accept requiredCapabilities only as a property of desiredCapabilities.
|
||||
// This has changed with the W3C WebDriver spec, but is the only way how to pass these
|
||||
// values with the legacy protocol.
|
||||
$desired_capabilities->setCapability('requiredCapabilities', (object) $required_capabilities->toArray());
|
||||
}
|
||||
|
||||
$parameters['desiredCapabilities'] = (object) $desired_capabilities->toArray();
|
||||
|
||||
$command = WebDriverCommand::newSession($parameters);
|
||||
|
||||
$response = $executor->execute($command);
|
||||
|
||||
return static::createFromResponse($response, $executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Experimental] Construct the RemoteWebDriver by an existing session.
|
||||
*
|
||||
* This constructor can boost the performance by reusing the same browser for the whole test suite. On the other
|
||||
* hand, because the browser is not pristine, this may lead to flaky and dependent tests. So carefully
|
||||
* consider the tradeoffs.
|
||||
*
|
||||
* To create the instance, we need to know Capabilities of the previously created session. You can either
|
||||
* pass them in $existingCapabilities parameter, or we will attempt to receive them from the Selenium Grid server.
|
||||
* However, if Capabilities were not provided and the attempt to get them was not successful,
|
||||
* exception will be thrown.
|
||||
*
|
||||
* @param string $session_id The existing session id
|
||||
* @param string $selenium_server_url The url of the remote Selenium WebDriver server
|
||||
* @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server
|
||||
* @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server
|
||||
* @param bool $isW3cCompliant True to use W3C WebDriver (default), false to use the legacy JsonWire protocol
|
||||
* @param WebDriverCapabilities|null $existingCapabilities Provide capabilities of the existing previously created
|
||||
* session. If not provided, we will attempt to read them, but this will only work when using Selenium Grid.
|
||||
* @return static
|
||||
*/
|
||||
public static function createBySessionID(
|
||||
$session_id,
|
||||
$selenium_server_url = 'http://localhost:4444/wd/hub',
|
||||
$connection_timeout_in_ms = null,
|
||||
$request_timeout_in_ms = null
|
||||
) {
|
||||
// BC layer to not break the method signature
|
||||
$isW3cCompliant = func_num_args() > 4 ? func_get_arg(4) : true;
|
||||
$existingCapabilities = func_num_args() > 5 ? func_get_arg(5) : null;
|
||||
|
||||
$executor = new HttpCommandExecutor($selenium_server_url, null, null);
|
||||
if ($connection_timeout_in_ms !== null) {
|
||||
$executor->setConnectionTimeout($connection_timeout_in_ms);
|
||||
}
|
||||
if ($request_timeout_in_ms !== null) {
|
||||
$executor->setRequestTimeout($request_timeout_in_ms);
|
||||
}
|
||||
|
||||
if (!$isW3cCompliant) {
|
||||
$executor->disableW3cCompliance();
|
||||
}
|
||||
|
||||
// if capabilities were not provided, attempt to read them from the Selenium Grid API
|
||||
if ($existingCapabilities === null) {
|
||||
$existingCapabilities = self::readExistingCapabilitiesFromSeleniumGrid($session_id, $executor);
|
||||
}
|
||||
|
||||
return new static($executor, $session_id, $existingCapabilities, $isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current window.
|
||||
*
|
||||
* @return RemoteWebDriver The current instance.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->execute(DriverCommand::CLOSE, []);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new top-level browsing context.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* @deprecated Use $driver->switchTo()->newWindow()
|
||||
* @return WebDriver The current instance.
|
||||
*/
|
||||
public function newWindow()
|
||||
{
|
||||
return $this->switchTo()->newWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first WebDriverElement using the given mechanism.
|
||||
*
|
||||
* @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found.
|
||||
* @see WebDriverBy
|
||||
*/
|
||||
public function findElement(WebDriverBy $by)
|
||||
{
|
||||
$raw_element = $this->execute(
|
||||
DriverCommand::FIND_ELEMENT,
|
||||
JsonWireCompat::getUsing($by, $this->isW3cCompliant)
|
||||
);
|
||||
|
||||
return $this->newElement(JsonWireCompat::getElement($raw_element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all WebDriverElements within the current page using the given mechanism.
|
||||
*
|
||||
* @return RemoteWebElement[] A list of all WebDriverElements, or an empty array if nothing matches
|
||||
* @see WebDriverBy
|
||||
*/
|
||||
public function findElements(WebDriverBy $by)
|
||||
{
|
||||
$raw_elements = $this->execute(
|
||||
DriverCommand::FIND_ELEMENTS,
|
||||
JsonWireCompat::getUsing($by, $this->isW3cCompliant)
|
||||
);
|
||||
|
||||
if (!is_array($raw_elements)) {
|
||||
throw UnexpectedResponseException::forError('Server response to findElements command is not an array');
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
foreach ($raw_elements as $raw_element) {
|
||||
$elements[] = $this->newElement(JsonWireCompat::getElement($raw_element));
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new web page in the current browser window.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return RemoteWebDriver The current instance.
|
||||
*/
|
||||
public function get($url)
|
||||
{
|
||||
$params = ['url' => (string) $url];
|
||||
$this->execute(DriverCommand::GET, $params);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representing the current URL that the browser is looking at.
|
||||
*
|
||||
* @return string The current URL.
|
||||
*/
|
||||
public function getCurrentURL()
|
||||
{
|
||||
return $this->execute(DriverCommand::GET_CURRENT_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of the last loaded page.
|
||||
*
|
||||
* @return string The current page source.
|
||||
*/
|
||||
public function getPageSource()
|
||||
{
|
||||
return $this->execute(DriverCommand::GET_PAGE_SOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of the current page.
|
||||
*
|
||||
* @return string The title of the current page.
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->execute(DriverCommand::GET_TITLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an opaque handle to this window that uniquely identifies it within this driver instance.
|
||||
*
|
||||
* @return string The current window handle.
|
||||
*/
|
||||
public function getWindowHandle()
|
||||
{
|
||||
return $this->execute(
|
||||
DriverCommand::GET_CURRENT_WINDOW_HANDLE,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all window handles available to the current session.
|
||||
*
|
||||
* Note: Do not use `end($driver->getWindowHandles())` to find the last open window, for proper solution see:
|
||||
* https://github.com/php-webdriver/php-webdriver/wiki/Alert,-tabs,-frames,-iframes#switch-to-the-new-window
|
||||
*
|
||||
* @return array An array of string containing all available window handles.
|
||||
*/
|
||||
public function getWindowHandles()
|
||||
{
|
||||
return $this->execute(DriverCommand::GET_WINDOW_HANDLES, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits this driver, closing every associated window.
|
||||
*/
|
||||
public function quit()
|
||||
{
|
||||
$this->execute(DriverCommand::QUIT);
|
||||
$this->executor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame.
|
||||
* The executed script is assumed to be synchronous and the result of evaluating the script will be returned.
|
||||
*
|
||||
* @param string $script The script to inject.
|
||||
* @param array $arguments The arguments of the script.
|
||||
* @return mixed The return value of the script.
|
||||
*/
|
||||
public function executeScript($script, array $arguments = [])
|
||||
{
|
||||
$params = [
|
||||
'script' => $script,
|
||||
'args' => $this->prepareScriptArguments($arguments),
|
||||
];
|
||||
|
||||
return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject a snippet of JavaScript into the page for asynchronous execution in the context of the currently selected
|
||||
* frame.
|
||||
*
|
||||
* The driver will pass a callback as the last argument to the snippet, and block until the callback is invoked.
|
||||
*
|
||||
* You may need to define script timeout using `setScriptTimeout()` method of `WebDriverTimeouts` first.
|
||||
*
|
||||
* @param string $script The script to inject.
|
||||
* @param array $arguments The arguments of the script.
|
||||
* @return mixed The value passed by the script to the callback.
|
||||
*/
|
||||
public function executeAsyncScript($script, array $arguments = [])
|
||||
{
|
||||
$params = [
|
||||
'script' => $script,
|
||||
'args' => $this->prepareScriptArguments($arguments),
|
||||
];
|
||||
|
||||
return $this->execute(
|
||||
DriverCommand::EXECUTE_ASYNC_SCRIPT,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a screenshot of the current page.
|
||||
*
|
||||
* @param string $save_as The path of the screenshot to be saved.
|
||||
* @return string The screenshot in PNG format.
|
||||
*/
|
||||
public function takeScreenshot($save_as = null)
|
||||
{
|
||||
return (new ScreenshotHelper($this->getExecuteMethod()))->takePageScreenshot($save_as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Status returns information about whether a remote end is in a state in which it can create new sessions.
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
$response = $this->execute(DriverCommand::STATUS);
|
||||
|
||||
return RemoteStatus::createFromResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new WebDriverWait by the current WebDriver instance.
|
||||
* Sample usage:
|
||||
*
|
||||
* ```
|
||||
* $driver->wait(20, 1000)->until(
|
||||
* WebDriverExpectedCondition::titleIs('WebDriver Page')
|
||||
* );
|
||||
* ```
|
||||
* @param int $timeout_in_second
|
||||
* @param int $interval_in_millisecond
|
||||
*
|
||||
* @return WebDriverWait
|
||||
*/
|
||||
public function wait($timeout_in_second = 30, $interval_in_millisecond = 250)
|
||||
{
|
||||
return new WebDriverWait(
|
||||
$this,
|
||||
$timeout_in_second,
|
||||
$interval_in_millisecond
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstraction for managing stuff you would do in a browser menu. For example, adding and deleting cookies.
|
||||
*
|
||||
* @return WebDriverOptions
|
||||
*/
|
||||
public function manage()
|
||||
{
|
||||
return new WebDriverOptions($this->getExecuteMethod(), $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstraction allowing the driver to access the browser's history and to navigate to a given URL.
|
||||
*
|
||||
* @return WebDriverNavigation
|
||||
* @see WebDriverNavigation
|
||||
*/
|
||||
public function navigate()
|
||||
{
|
||||
return new WebDriverNavigation($this->getExecuteMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different window or frame.
|
||||
*
|
||||
* @return RemoteTargetLocator
|
||||
* @see RemoteTargetLocator
|
||||
*/
|
||||
public function switchTo()
|
||||
{
|
||||
return new RemoteTargetLocator($this->getExecuteMethod(), $this, $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteMouse
|
||||
*/
|
||||
public function getMouse()
|
||||
{
|
||||
if (!$this->mouse) {
|
||||
$this->mouse = new RemoteMouse($this->getExecuteMethod(), $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
return $this->mouse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteKeyboard
|
||||
*/
|
||||
public function getKeyboard()
|
||||
{
|
||||
if (!$this->keyboard) {
|
||||
$this->keyboard = new RemoteKeyboard($this->getExecuteMethod(), $this, $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
return $this->keyboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteTouchScreen
|
||||
*/
|
||||
public function getTouch()
|
||||
{
|
||||
if (!$this->touch) {
|
||||
$this->touch = new RemoteTouchScreen($this->getExecuteMethod());
|
||||
}
|
||||
|
||||
return $this->touch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new action builder.
|
||||
*
|
||||
* @return WebDriverActions
|
||||
*/
|
||||
public function action()
|
||||
{
|
||||
return new WebDriverActions($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the command executor of this RemoteWebdriver
|
||||
*
|
||||
* @deprecated To be removed in the future. Executor should be passed in the constructor.
|
||||
* @internal
|
||||
* @codeCoverageIgnore
|
||||
* @param WebDriverCommandExecutor $executor Despite the typehint, it have be an instance of HttpCommandExecutor.
|
||||
* @return RemoteWebDriver
|
||||
*/
|
||||
public function setCommandExecutor(WebDriverCommandExecutor $executor)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command executor of this RemoteWebdriver
|
||||
*
|
||||
* @return HttpCommandExecutor
|
||||
*/
|
||||
public function getCommandExecutor()
|
||||
{
|
||||
return $this->executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the session id of the RemoteWebDriver.
|
||||
*
|
||||
* @deprecated To be removed in the future. Session ID should be passed in the constructor.
|
||||
* @internal
|
||||
* @codeCoverageIgnore
|
||||
* @param string $session_id
|
||||
* @return RemoteWebDriver
|
||||
*/
|
||||
public function setSessionID($session_id)
|
||||
{
|
||||
$this->sessionID = $session_id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current selenium sessionID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionID()
|
||||
{
|
||||
return $this->sessionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get capabilities of the RemoteWebDriver.
|
||||
*
|
||||
* @return WebDriverCapabilities|null
|
||||
*/
|
||||
public function getCapabilities()
|
||||
{
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the currently active sessions.
|
||||
*
|
||||
* @deprecated Removed in W3C WebDriver.
|
||||
* @param string $selenium_server_url The url of the remote Selenium WebDriver server
|
||||
* @param int $timeout_in_ms
|
||||
* @return array
|
||||
*/
|
||||
public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000)
|
||||
{
|
||||
$executor = new HttpCommandExecutor($selenium_server_url, null, null);
|
||||
$executor->setConnectionTimeout($timeout_in_ms);
|
||||
|
||||
$command = new WebDriverCommand(
|
||||
null,
|
||||
DriverCommand::GET_ALL_SESSIONS,
|
||||
[]
|
||||
);
|
||||
|
||||
return $executor->execute($command)->getValue();
|
||||
}
|
||||
|
||||
public function execute($command_name, $params = [])
|
||||
{
|
||||
// As we so far only use atom for IS_ELEMENT_DISPLAYED, this condition is hardcoded here. In case more atoms
|
||||
// are used, this should be rewritten and separated from this class (e.g. to some abstract matcher logic).
|
||||
if ($command_name === DriverCommand::IS_ELEMENT_DISPLAYED
|
||||
&& (
|
||||
// When capabilities are missing in php-webdriver 1.13.x, always fallback to use the atom
|
||||
$this->getCapabilities() === null
|
||||
// If capabilities are present, use the atom only if condition matches
|
||||
|| IsElementDisplayedAtom::match($this->getCapabilities()->getBrowserName())
|
||||
)
|
||||
) {
|
||||
return (new IsElementDisplayedAtom($this))->execute($params);
|
||||
}
|
||||
|
||||
$command = new WebDriverCommand(
|
||||
$this->sessionID,
|
||||
$command_name,
|
||||
$params
|
||||
);
|
||||
|
||||
if ($this->executor) {
|
||||
$response = $this->executor->execute($command);
|
||||
|
||||
return $response->getValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute custom commands on remote end.
|
||||
* For example vendor-specific commands or other commands not implemented by php-webdriver.
|
||||
*
|
||||
* @see https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands
|
||||
* @param string $endpointUrl
|
||||
* @param string $method
|
||||
* @param array $params
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function executeCustomCommand($endpointUrl, $method = 'GET', $params = [])
|
||||
{
|
||||
$command = new CustomWebDriverCommand(
|
||||
$this->sessionID,
|
||||
$endpointUrl,
|
||||
$method,
|
||||
$params
|
||||
);
|
||||
|
||||
if ($this->executor) {
|
||||
$response = $this->executor->execute($command);
|
||||
|
||||
return $response->getValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @return bool
|
||||
*/
|
||||
public function isW3cCompliant()
|
||||
{
|
||||
return $this->isW3cCompliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create instance based on response to NEW_SESSION command.
|
||||
* Also detect W3C/OSS dialect and setup the driver/executor accordingly.
|
||||
*
|
||||
* @internal
|
||||
* @return static
|
||||
*/
|
||||
protected static function createFromResponse(WebDriverResponse $response, HttpCommandExecutor $commandExecutor)
|
||||
{
|
||||
$responseValue = $response->getValue();
|
||||
|
||||
if (!$isW3cCompliant = isset($responseValue['capabilities'])) {
|
||||
$commandExecutor->disableW3cCompliance();
|
||||
}
|
||||
|
||||
if ($isW3cCompliant) {
|
||||
$returnedCapabilities = DesiredCapabilities::createFromW3cCapabilities($responseValue['capabilities']);
|
||||
} else {
|
||||
$returnedCapabilities = new DesiredCapabilities($responseValue);
|
||||
}
|
||||
|
||||
return new static($commandExecutor, $response->getSessionID(), $returnedCapabilities, $isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare arguments for JavaScript injection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareScriptArguments(array $arguments)
|
||||
{
|
||||
$args = [];
|
||||
foreach ($arguments as $key => $value) {
|
||||
if ($value instanceof WebDriverElement) {
|
||||
$args[$key] = [
|
||||
$this->isW3cCompliant ?
|
||||
JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER
|
||||
: 'ELEMENT' => $value->getID(),
|
||||
];
|
||||
} else {
|
||||
if (is_array($value)) {
|
||||
$value = $this->prepareScriptArguments($value);
|
||||
}
|
||||
$args[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteExecuteMethod
|
||||
*/
|
||||
protected function getExecuteMethod()
|
||||
{
|
||||
if (!$this->executeMethod) {
|
||||
$this->executeMethod = new RemoteExecuteMethod($this);
|
||||
}
|
||||
|
||||
return $this->executeMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the WebDriverElement with the given id.
|
||||
*
|
||||
* @param string $id The id of the element to be created.
|
||||
* @return RemoteWebElement
|
||||
*/
|
||||
protected function newElement($id)
|
||||
{
|
||||
return new RemoteWebElement($this->getExecuteMethod(), $id, $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast legacy types (array or null) to DesiredCapabilities object. To be removed in future when instance of
|
||||
* DesiredCapabilities will be required.
|
||||
*
|
||||
* @param array|DesiredCapabilities|null $desired_capabilities
|
||||
* @return DesiredCapabilities
|
||||
*/
|
||||
protected static function castToDesiredCapabilitiesObject($desired_capabilities = null)
|
||||
{
|
||||
if ($desired_capabilities === null) {
|
||||
return new DesiredCapabilities();
|
||||
}
|
||||
|
||||
if (is_array($desired_capabilities)) {
|
||||
return new DesiredCapabilities($desired_capabilities);
|
||||
}
|
||||
|
||||
return $desired_capabilities;
|
||||
}
|
||||
|
||||
protected static function readExistingCapabilitiesFromSeleniumGrid(
|
||||
string $session_id,
|
||||
HttpCommandExecutor $executor
|
||||
): DesiredCapabilities {
|
||||
$getCapabilitiesCommand = new CustomWebDriverCommand($session_id, '/se/grid/session/:sessionId', 'GET', []);
|
||||
|
||||
try {
|
||||
$capabilitiesResponse = $executor->execute($getCapabilitiesCommand);
|
||||
|
||||
$existingCapabilities = DesiredCapabilities::createFromW3cCapabilities(
|
||||
$capabilitiesResponse->getValue()['capabilities']
|
||||
);
|
||||
if ($existingCapabilities === null) {
|
||||
throw UnexpectedResponseException::forError('Empty capabilities received');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw UnexpectedResponseException::forCapabilitiesRetrievalError($e);
|
||||
}
|
||||
|
||||
return $existingCapabilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\ElementNotInteractableException;
|
||||
use Facebook\WebDriver\Exception\Internal\IOException;
|
||||
use Facebook\WebDriver\Exception\Internal\LogicException;
|
||||
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
|
||||
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
|
||||
use Facebook\WebDriver\Exception\UnsupportedOperationException;
|
||||
use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
|
||||
use Facebook\WebDriver\Internal\WebDriverLocatable;
|
||||
use Facebook\WebDriver\Support\ScreenshotHelper;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverDimension;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
use Facebook\WebDriver\WebDriverPoint;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Represents an HTML element.
|
||||
*/
|
||||
class RemoteWebElement implements WebDriverElement, WebDriverLocatable
|
||||
{
|
||||
/**
|
||||
* @var RemoteExecuteMethod
|
||||
*/
|
||||
protected $executor;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
/**
|
||||
* @var FileDetector
|
||||
*/
|
||||
protected $fileDetector;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isW3cCompliant;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param bool $isW3cCompliant
|
||||
*/
|
||||
public function __construct(RemoteExecuteMethod $executor, $id, $isW3cCompliant = false)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->id = $id;
|
||||
$this->fileDetector = new UselessFileDetector();
|
||||
$this->isW3cCompliant = $isW3cCompliant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear content editable or resettable element
|
||||
*
|
||||
* @return $this The current instance.
|
||||
*/
|
||||
public function clear()
|
||||
{
|
||||
$this->executor->execute(
|
||||
DriverCommand::CLEAR_ELEMENT,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click this element.
|
||||
*
|
||||
* @return $this The current instance.
|
||||
*/
|
||||
public function click()
|
||||
{
|
||||
try {
|
||||
$this->executor->execute(
|
||||
DriverCommand::CLICK_ELEMENT,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
} catch (ElementNotInteractableException $e) {
|
||||
// An issue with geckodriver (https://github.com/mozilla/geckodriver/issues/653) prevents clicking on a link
|
||||
// if the first child is a block-level element.
|
||||
// The workaround in this case is to click on a child element.
|
||||
$this->clickChildElement($e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first WebDriverElement within this element using the given mechanism.
|
||||
*
|
||||
* When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will
|
||||
* search the entire document from the root, not just the children (relative context) of this current node.
|
||||
* Use ".//" to limit your search to the children of this element.
|
||||
*
|
||||
* @return static NoSuchElementException is thrown in HttpCommandExecutor if no element is found.
|
||||
* @see WebDriverBy
|
||||
*/
|
||||
public function findElement(WebDriverBy $by)
|
||||
{
|
||||
$params = JsonWireCompat::getUsing($by, $this->isW3cCompliant);
|
||||
$params[':id'] = $this->id;
|
||||
|
||||
$raw_element = $this->executor->execute(
|
||||
DriverCommand::FIND_CHILD_ELEMENT,
|
||||
$params
|
||||
);
|
||||
|
||||
return $this->newElement(JsonWireCompat::getElement($raw_element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all WebDriverElements within this element using the given mechanism.
|
||||
*
|
||||
* When using xpath be aware that webdriver follows standard conventions: a search prefixed with "//" will
|
||||
* search the entire document from the root, not just the children (relative context) of this current node.
|
||||
* Use ".//" to limit your search to the children of this element.
|
||||
*
|
||||
* @return static[] A list of all WebDriverElements, or an empty
|
||||
* array if nothing matches
|
||||
* @see WebDriverBy
|
||||
*/
|
||||
public function findElements(WebDriverBy $by)
|
||||
{
|
||||
$params = JsonWireCompat::getUsing($by, $this->isW3cCompliant);
|
||||
$params[':id'] = $this->id;
|
||||
$raw_elements = $this->executor->execute(
|
||||
DriverCommand::FIND_CHILD_ELEMENTS,
|
||||
$params
|
||||
);
|
||||
|
||||
if (!is_array($raw_elements)) {
|
||||
throw UnexpectedResponseException::forError('Server response to findChildElements command is not an array');
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
foreach ($raw_elements as $raw_element) {
|
||||
$elements[] = $this->newElement(JsonWireCompat::getElement($raw_element));
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the given attribute of the element.
|
||||
* Attribute is meant what is declared in the HTML markup of the element.
|
||||
* To read a value of a IDL "JavaScript" property (like `innerHTML`), use `getDomProperty()` method.
|
||||
*
|
||||
* @param string $attribute_name The name of the attribute.
|
||||
* @return string|true|null The value of the attribute. If this is boolean attribute, return true if the element
|
||||
* has it, otherwise return null.
|
||||
*/
|
||||
public function getAttribute($attribute_name)
|
||||
{
|
||||
$params = [
|
||||
':name' => $attribute_name,
|
||||
':id' => $this->id,
|
||||
];
|
||||
|
||||
if ($this->isW3cCompliant && ($attribute_name === 'value' || $attribute_name === 'index')) {
|
||||
$value = $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params);
|
||||
|
||||
if ($value === true) {
|
||||
return 'true';
|
||||
}
|
||||
|
||||
if ($value === false) {
|
||||
return 'false';
|
||||
}
|
||||
|
||||
if ($value !== null) {
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->executor->execute(DriverCommand::GET_ELEMENT_ATTRIBUTE, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a IDL JavaScript property of this element (for example `innerHTML`, `tagName` etc.).
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Glossary/IDL
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element#properties
|
||||
* @param string $propertyName
|
||||
* @return mixed|null The property's current value or null if the value is not set or the property does not exist.
|
||||
*/
|
||||
public function getDomProperty($propertyName)
|
||||
{
|
||||
if (!$this->isW3cCompliant) {
|
||||
throw new UnsupportedOperationException('This method is only supported in W3C mode');
|
||||
}
|
||||
|
||||
$params = [
|
||||
':name' => $propertyName,
|
||||
':id' => $this->id,
|
||||
];
|
||||
|
||||
return $this->executor->execute(DriverCommand::GET_ELEMENT_PROPERTY, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of a given CSS property.
|
||||
*
|
||||
* @param string $css_property_name The name of the CSS property.
|
||||
* @return string The value of the CSS property.
|
||||
*/
|
||||
public function getCSSValue($css_property_name)
|
||||
{
|
||||
$params = [
|
||||
':propertyName' => $css_property_name,
|
||||
':id' => $this->id,
|
||||
];
|
||||
|
||||
return $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
|
||||
$params
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of element relative to the top-left corner of the page.
|
||||
*
|
||||
* @return WebDriverPoint The location of the element.
|
||||
*/
|
||||
public function getLocation()
|
||||
{
|
||||
$location = $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_LOCATION,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
|
||||
return new WebDriverPoint($location['x'], $location['y']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try scrolling the element into the view port and return the location of
|
||||
* element relative to the top-left corner of the page afterwards.
|
||||
*
|
||||
* @return WebDriverPoint The location of the element.
|
||||
*/
|
||||
public function getLocationOnScreenOnceScrolledIntoView()
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
$script = <<<JS
|
||||
var e = arguments[0];
|
||||
e.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'nearest' });
|
||||
var rect = e.getBoundingClientRect();
|
||||
return {'x': rect.left, 'y': rect.top};
|
||||
JS;
|
||||
|
||||
$result = $this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [
|
||||
'script' => $script,
|
||||
'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]],
|
||||
]);
|
||||
$location = ['x' => $result['x'], 'y' => $result['y']];
|
||||
} else {
|
||||
$location = $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
}
|
||||
|
||||
return new WebDriverPoint($location['x'], $location['y']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WebDriverCoordinates
|
||||
*/
|
||||
public function getCoordinates()
|
||||
{
|
||||
$element = $this;
|
||||
|
||||
$on_screen = null; // planned but not yet implemented
|
||||
$in_view_port = static function () use ($element) {
|
||||
return $element->getLocationOnScreenOnceScrolledIntoView();
|
||||
};
|
||||
$on_page = static function () use ($element) {
|
||||
return $element->getLocation();
|
||||
};
|
||||
$auxiliary = $this->getID();
|
||||
|
||||
return new WebDriverCoordinates(
|
||||
$on_screen,
|
||||
$in_view_port,
|
||||
$on_page,
|
||||
$auxiliary
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of element.
|
||||
*
|
||||
* @return WebDriverDimension The dimension of the element.
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
$size = $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_SIZE,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
|
||||
return new WebDriverDimension($size['width'], $size['height']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (lowercase) tag name of this element.
|
||||
*
|
||||
* @return string The tag name.
|
||||
*/
|
||||
public function getTagName()
|
||||
{
|
||||
// Force tag name to be lowercase as expected by JsonWire protocol for Opera driver
|
||||
// until this issue is not resolved :
|
||||
// https://github.com/operasoftware/operadriver/issues/102
|
||||
// Remove it when fixed to be consistent with the protocol.
|
||||
return mb_strtolower($this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_TAG_NAME,
|
||||
[':id' => $this->id]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the visible (i.e. not hidden by CSS) innerText of this element,
|
||||
* including sub-elements, without any leading or trailing whitespace.
|
||||
*
|
||||
* @return string The visible innerText of this element.
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
return $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_TEXT,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this element displayed or not? This method avoids the problem of having
|
||||
* to parse an element's "style" attribute.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisplayed()
|
||||
{
|
||||
return $this->executor->execute(
|
||||
DriverCommand::IS_ELEMENT_DISPLAYED,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the element currently enabled or not? This will generally return true
|
||||
* for everything but disabled input elements.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->executor->execute(
|
||||
DriverCommand::IS_ELEMENT_ENABLED,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this element is selected or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSelected()
|
||||
{
|
||||
return $this->executor->execute(
|
||||
DriverCommand::IS_ELEMENT_SELECTED,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate typing into an element, which may set its value.
|
||||
*
|
||||
* @param mixed $value The data to be typed.
|
||||
* @return static The current instance.
|
||||
*/
|
||||
public function sendKeys($value)
|
||||
{
|
||||
$local_file = $this->fileDetector->getLocalFile($value);
|
||||
|
||||
$params = [];
|
||||
if ($local_file === null) {
|
||||
if ($this->isW3cCompliant) {
|
||||
// Work around the Geckodriver NULL issue by splitting on NULL and calling sendKeys multiple times.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1494661.
|
||||
$encodedValues = explode(WebDriverKeys::NULL, WebDriverKeys::encode($value, true));
|
||||
foreach ($encodedValues as $encodedValue) {
|
||||
$params[] = [
|
||||
'text' => $encodedValue,
|
||||
':id' => $this->id,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$params[] = [
|
||||
'value' => WebDriverKeys::encode($value),
|
||||
':id' => $this->id,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if ($this->isW3cCompliant) {
|
||||
try {
|
||||
// Attempt to upload the file to the remote browser.
|
||||
// This is so far non-W3C compliant method, so it may fail - if so, we just ignore the exception.
|
||||
// @see https://github.com/w3c/webdriver/issues/1355
|
||||
$fileName = $this->upload($local_file);
|
||||
} catch (PhpWebDriverExceptionInterface $e) {
|
||||
$fileName = $local_file;
|
||||
}
|
||||
|
||||
$params[] = [
|
||||
'text' => $fileName,
|
||||
':id' => $this->id,
|
||||
];
|
||||
} else {
|
||||
$params[] = [
|
||||
'value' => WebDriverKeys::encode($this->upload($local_file)),
|
||||
':id' => $this->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($params as $param) {
|
||||
$this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $param);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fileDetector in order to let the RemoteWebElement to know that you are going to upload a file.
|
||||
*
|
||||
* Basically, if you want WebDriver trying to send a file, set the fileDetector
|
||||
* to be LocalFileDetector. Otherwise, keep it UselessFileDetector.
|
||||
*
|
||||
* eg. `$element->setFileDetector(new LocalFileDetector);`
|
||||
*
|
||||
* @return $this
|
||||
* @see FileDetector
|
||||
* @see LocalFileDetector
|
||||
* @see UselessFileDetector
|
||||
*/
|
||||
public function setFileDetector(FileDetector $detector)
|
||||
{
|
||||
$this->fileDetector = $detector;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this current element is a form, or an element within a form, then this will be submitted to the remote server.
|
||||
*
|
||||
* @return $this The current instance.
|
||||
*/
|
||||
public function submit()
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
// Submit method cannot be called directly in case an input of this form is named "submit".
|
||||
// We use this polyfill to trigger 'submit' event using form.dispatchEvent().
|
||||
$submitPolyfill = <<<HTXT
|
||||
var form = arguments[0];
|
||||
while (form.nodeName !== "FORM" && form.parentNode) { // find the parent form of this element
|
||||
form = form.parentNode;
|
||||
}
|
||||
if (!form) {
|
||||
throw Error('Unable to find containing form element');
|
||||
}
|
||||
var event = new Event('submit', {bubbles: true, cancelable: true});
|
||||
if (form.dispatchEvent(event)) {
|
||||
HTMLFormElement.prototype.submit.call(form);
|
||||
}
|
||||
HTXT;
|
||||
$this->executor->execute(DriverCommand::EXECUTE_SCRIPT, [
|
||||
'script' => $submitPolyfill,
|
||||
'args' => [[JsonWireCompat::WEB_DRIVER_ELEMENT_IDENTIFIER => $this->id]],
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->executor->execute(
|
||||
DriverCommand::SUBMIT_ELEMENT,
|
||||
[':id' => $this->id]
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opaque ID of the element.
|
||||
*
|
||||
* @return string The opaque ID.
|
||||
*/
|
||||
public function getID()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a screenshot of a specific element.
|
||||
*
|
||||
* @param string $save_as The path of the screenshot to be saved.
|
||||
* @return string The screenshot in PNG format.
|
||||
*/
|
||||
public function takeElementScreenshot($save_as = null)
|
||||
{
|
||||
return (new ScreenshotHelper($this->executor))->takeElementScreenshot($this->id, $save_as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if two elements IDs refer to the same DOM element.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function equals(WebDriverElement $other)
|
||||
{
|
||||
if ($this->isW3cCompliant) {
|
||||
return $this->getID() === $other->getID();
|
||||
}
|
||||
|
||||
return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [
|
||||
':id' => $this->id,
|
||||
':other' => $other->getID(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get representation of an element's shadow root for accessing the shadow DOM of a web component.
|
||||
*
|
||||
* @return ShadowRoot
|
||||
*/
|
||||
public function getShadowRoot()
|
||||
{
|
||||
if (!$this->isW3cCompliant) {
|
||||
throw new UnsupportedOperationException('This method is only supported in W3C mode');
|
||||
}
|
||||
|
||||
$response = $this->executor->execute(
|
||||
DriverCommand::GET_ELEMENT_SHADOW_ROOT,
|
||||
[
|
||||
':id' => $this->id,
|
||||
]
|
||||
);
|
||||
|
||||
return ShadowRoot::createFromResponse($this->executor, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to click on a child level element.
|
||||
*
|
||||
* This provides a workaround for geckodriver bug 653 whereby a link whose first element is a block-level element
|
||||
* throws an ElementNotInteractableException could not scroll into view exception.
|
||||
*
|
||||
* The workaround provided here attempts to click on a child node of the element.
|
||||
* In case the first child is hidden, other elements are processed until we run out of elements.
|
||||
*
|
||||
* @param ElementNotInteractableException $originalException The exception to throw if unable to click on any child
|
||||
* @see https://github.com/mozilla/geckodriver/issues/653
|
||||
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=1374283
|
||||
*/
|
||||
protected function clickChildElement(ElementNotInteractableException $originalException)
|
||||
{
|
||||
$children = $this->findElements(WebDriverBy::xpath('./*'));
|
||||
foreach ($children as $child) {
|
||||
try {
|
||||
// Note: This does not use $child->click() as this would cause recursion into all children.
|
||||
// Where the element is hidden, all children will also be hidden.
|
||||
$this->executor->execute(
|
||||
DriverCommand::CLICK_ELEMENT,
|
||||
[':id' => $child->id]
|
||||
);
|
||||
|
||||
return;
|
||||
} catch (ElementNotInteractableException $e) {
|
||||
// Ignore the ElementNotInteractableException exception on this node. Try the next child instead.
|
||||
}
|
||||
}
|
||||
|
||||
throw $originalException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the WebDriverElement with $id
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
protected function newElement($id)
|
||||
{
|
||||
return new static($this->executor, $id, $this->isW3cCompliant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a local file to the server
|
||||
*
|
||||
* @param string $local_file
|
||||
*
|
||||
* @throws LogicException
|
||||
* @return string The remote path of the file.
|
||||
*/
|
||||
protected function upload($local_file)
|
||||
{
|
||||
if (!is_file($local_file)) {
|
||||
throw LogicException::forError('You may only upload files: ' . $local_file);
|
||||
}
|
||||
|
||||
$temp_zip_path = $this->createTemporaryZipArchive($local_file);
|
||||
|
||||
$remote_path = $this->executor->execute(
|
||||
DriverCommand::UPLOAD_FILE,
|
||||
['file' => base64_encode(file_get_contents($temp_zip_path))]
|
||||
);
|
||||
|
||||
unlink($temp_zip_path);
|
||||
|
||||
return $remote_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileToZip
|
||||
* @return string
|
||||
*/
|
||||
protected function createTemporaryZipArchive($fileToZip)
|
||||
{
|
||||
// Create a temporary file in the system temp directory.
|
||||
// Intentionally do not use `tempnam()`, as it creates empty file which zip extension may not handle.
|
||||
$tempZipPath = sys_get_temp_dir() . '/' . uniqid('WebDriverZip', false);
|
||||
|
||||
$zip = new ZipArchive();
|
||||
if (($errorCode = $zip->open($tempZipPath, ZipArchive::CREATE)) !== true) {
|
||||
throw IOException::forFileError(sprintf('Error creating zip archive: %s', $errorCode), $tempZipPath);
|
||||
}
|
||||
|
||||
$info = pathinfo($fileToZip);
|
||||
$file_name = $info['basename'];
|
||||
$zip->addFile($fileToZip, $file_name);
|
||||
$zip->close();
|
||||
|
||||
return $tempZipPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote\Service;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\DriverServerDiedException;
|
||||
use Facebook\WebDriver\Exception\WebDriverException;
|
||||
use Facebook\WebDriver\Remote\DriverCommand;
|
||||
use Facebook\WebDriver\Remote\HttpCommandExecutor;
|
||||
use Facebook\WebDriver\Remote\WebDriverCommand;
|
||||
use Facebook\WebDriver\Remote\WebDriverResponse;
|
||||
|
||||
/**
|
||||
* A HttpCommandExecutor that talks to a local driver service instead of a remote server.
|
||||
*/
|
||||
class DriverCommandExecutor extends HttpCommandExecutor
|
||||
{
|
||||
/**
|
||||
* @var DriverService
|
||||
*/
|
||||
private $service;
|
||||
|
||||
public function __construct(DriverService $service)
|
||||
{
|
||||
parent::__construct($service->getURL());
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @throws WebDriverException
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function execute(WebDriverCommand $command)
|
||||
{
|
||||
if ($command->getName() === DriverCommand::NEW_SESSION) {
|
||||
$this->service->start();
|
||||
}
|
||||
|
||||
try {
|
||||
$value = parent::execute($command);
|
||||
if ($command->getName() === DriverCommand::QUIT) {
|
||||
$this->service->stop();
|
||||
}
|
||||
|
||||
return $value;
|
||||
} catch (\Exception $e) {
|
||||
if (!$this->service->isRunning()) {
|
||||
throw new DriverServerDiedException($e);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote\Service;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\IOException;
|
||||
use Facebook\WebDriver\Exception\Internal\RuntimeException;
|
||||
use Facebook\WebDriver\Net\URLChecker;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Start local WebDriver service (when remote WebDriver server is not used).
|
||||
* This will start new process of respective browser driver and take care of its lifecycle.
|
||||
*/
|
||||
class DriverService
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $executable;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $url;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $args;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $environment;
|
||||
|
||||
/**
|
||||
* @var Process|null
|
||||
*/
|
||||
private $process;
|
||||
|
||||
/**
|
||||
* @param string $executable
|
||||
* @param int $port The given port the service should use.
|
||||
* @param array $args
|
||||
* @param array|null $environment Use the system environment if it is null
|
||||
*/
|
||||
public function __construct($executable, $port, $args = [], $environment = null)
|
||||
{
|
||||
$this->setExecutable($executable);
|
||||
$this->url = sprintf('http://localhost:%d', $port);
|
||||
$this->args = $args;
|
||||
$this->environment = $environment ?: $_ENV;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getURL()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DriverService
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
if ($this->process !== null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->process = $this->createProcess();
|
||||
$this->process->start();
|
||||
|
||||
$this->checkWasStarted($this->process);
|
||||
|
||||
$checker = new URLChecker();
|
||||
$checker->waitUntilAvailable(20 * 1000, $this->url . '/status');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DriverService
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->process->stop();
|
||||
$this->process = null;
|
||||
|
||||
$checker = new URLChecker();
|
||||
$checker->waitUntilUnavailable(3 * 1000, $this->url . '/shutdown');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isRunning()
|
||||
{
|
||||
if ($this->process === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->process->isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Has no effect. Will be removed in next major version. Executable is now checked
|
||||
* when calling setExecutable().
|
||||
* @param string $executable
|
||||
* @return string
|
||||
*/
|
||||
protected static function checkExecutable($executable)
|
||||
{
|
||||
return $executable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $executable
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function setExecutable($executable)
|
||||
{
|
||||
if ($this->isExecutable($executable)) {
|
||||
$this->executable = $executable;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw IOException::forFileError(
|
||||
'File is not executable. Make sure the path is correct or use environment variable to specify'
|
||||
. ' location of the executable.',
|
||||
$executable
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Process $process
|
||||
*/
|
||||
protected function checkWasStarted($process)
|
||||
{
|
||||
usleep(10000); // wait 10ms, otherwise the asynchronous process failure may not yet be propagated
|
||||
|
||||
if (!$process->isRunning()) {
|
||||
throw RuntimeException::forDriverError($process);
|
||||
}
|
||||
}
|
||||
|
||||
private function createProcess(): Process
|
||||
{
|
||||
$commandLine = array_merge([$this->executable], $this->args);
|
||||
|
||||
return new Process($commandLine, null, $this->environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether given file is executable directly or using system PATH
|
||||
*/
|
||||
private function isExecutable(string $filename): bool
|
||||
{
|
||||
if (is_executable($filename)) {
|
||||
return true;
|
||||
}
|
||||
if ($filename !== basename($filename)) { // $filename is an absolute path, do no try to search it in PATH
|
||||
return false;
|
||||
}
|
||||
|
||||
$paths = explode(PATH_SEPARATOR, getenv('PATH'));
|
||||
foreach ($paths as $path) {
|
||||
if (is_executable($path . DIRECTORY_SEPARATOR . $filename)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
use Facebook\WebDriver\Exception\Internal\UnexpectedResponseException;
|
||||
use Facebook\WebDriver\Exception\UnknownErrorException;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverElement;
|
||||
use Facebook\WebDriver\WebDriverSearchContext;
|
||||
|
||||
class ShadowRoot implements WebDriverSearchContext
|
||||
{
|
||||
/**
|
||||
* Shadow root identifier defined in the W3CWebDriver protocol.
|
||||
*
|
||||
* @see https://w3c.github.io/webdriver/#shadow-root
|
||||
*/
|
||||
public const SHADOW_ROOT_IDENTIFIER = 'shadow-6066-11e4-a52e-4f735466cecf';
|
||||
|
||||
/**
|
||||
* @var RemoteExecuteMethod
|
||||
*/
|
||||
private $executor;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
public function __construct(RemoteExecuteMethod $executor, $id)
|
||||
{
|
||||
$this->executor = $executor;
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromResponse(RemoteExecuteMethod $executor, array $response)
|
||||
{
|
||||
if (empty($response[self::SHADOW_ROOT_IDENTIFIER])) {
|
||||
throw new UnknownErrorException('Shadow root is missing in server response');
|
||||
}
|
||||
|
||||
return new self($executor, $response[self::SHADOW_ROOT_IDENTIFIER]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RemoteWebElement
|
||||
*/
|
||||
public function findElement(WebDriverBy $locator)
|
||||
{
|
||||
$params = JsonWireCompat::getUsing($locator, true);
|
||||
$params[':id'] = $this->id;
|
||||
|
||||
$rawElement = $this->executor->execute(
|
||||
DriverCommand::FIND_ELEMENT_FROM_SHADOW_ROOT,
|
||||
$params
|
||||
);
|
||||
|
||||
return new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WebDriverElement[]
|
||||
*/
|
||||
public function findElements(WebDriverBy $locator)
|
||||
{
|
||||
$params = JsonWireCompat::getUsing($locator, true);
|
||||
$params[':id'] = $this->id;
|
||||
|
||||
$rawElements = $this->executor->execute(
|
||||
DriverCommand::FIND_ELEMENTS_FROM_SHADOW_ROOT,
|
||||
$params
|
||||
);
|
||||
|
||||
if (!is_array($rawElements)) {
|
||||
throw UnexpectedResponseException::forError(
|
||||
'Server response to findElementsFromShadowRoot command is not an array'
|
||||
);
|
||||
}
|
||||
|
||||
$elements = [];
|
||||
foreach ($rawElements as $rawElement) {
|
||||
$elements[] = new RemoteWebElement($this->executor, JsonWireCompat::getElement($rawElement), true);
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getID()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
class UselessFileDetector implements FileDetector
|
||||
{
|
||||
public function getLocalFile($file)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
/**
|
||||
* All the browsers supported by selenium.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WebDriverBrowserType
|
||||
{
|
||||
public const FIREFOX = 'firefox';
|
||||
public const FIREFOX_PROXY = 'firefoxproxy';
|
||||
public const FIREFOX_CHROME = 'firefoxchrome';
|
||||
public const GOOGLECHROME = 'googlechrome';
|
||||
public const SAFARI = 'safari';
|
||||
public const SAFARI_PROXY = 'safariproxy';
|
||||
public const OPERA = 'opera';
|
||||
public const MICROSOFT_EDGE = 'MicrosoftEdge';
|
||||
public const IEXPLORE = 'iexplore';
|
||||
public const IEXPLORE_PROXY = 'iexploreproxy';
|
||||
public const CHROME = 'chrome';
|
||||
public const KONQUEROR = 'konqueror';
|
||||
public const MOCK = 'mock';
|
||||
public const IE_HTA = 'iehta';
|
||||
public const ANDROID = 'android';
|
||||
public const HTMLUNIT = 'htmlunit';
|
||||
public const IE = 'internet explorer';
|
||||
public const IPHONE = 'iphone';
|
||||
public const IPAD = 'iPad';
|
||||
/**
|
||||
* @deprecated PhantomJS is no longer developed and its support will be removed in next major version.
|
||||
* Use headless Chrome or Firefox instead.
|
||||
*/
|
||||
public const PHANTOMJS = 'phantomjs';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
/**
|
||||
* WebDriverCapabilityType contains all constants defined in the WebDriver Wire Protocol.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class WebDriverCapabilityType
|
||||
{
|
||||
public const BROWSER_NAME = 'browserName';
|
||||
public const VERSION = 'version';
|
||||
public const PLATFORM = 'platform';
|
||||
public const JAVASCRIPT_ENABLED = 'javascriptEnabled';
|
||||
public const TAKES_SCREENSHOT = 'takesScreenshot';
|
||||
public const HANDLES_ALERTS = 'handlesAlerts';
|
||||
public const DATABASE_ENABLED = 'databaseEnabled';
|
||||
public const LOCATION_CONTEXT_ENABLED = 'locationContextEnabled';
|
||||
public const APPLICATION_CACHE_ENABLED = 'applicationCacheEnabled';
|
||||
public const BROWSER_CONNECTION_ENABLED = 'browserConnectionEnabled';
|
||||
public const CSS_SELECTORS_ENABLED = 'cssSelectorsEnabled';
|
||||
public const WEB_STORAGE_ENABLED = 'webStorageEnabled';
|
||||
public const ROTATABLE = 'rotatable';
|
||||
public const ACCEPT_SSL_CERTS = 'acceptSslCerts';
|
||||
public const NATIVE_EVENTS = 'nativeEvents';
|
||||
public const PROXY = 'proxy';
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
class WebDriverCommand
|
||||
{
|
||||
/** @var string|null */
|
||||
protected $sessionID;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var array */
|
||||
protected $parameters;
|
||||
|
||||
/**
|
||||
* @param string $session_id
|
||||
* @param string $name Constant from DriverCommand
|
||||
* @param array $parameters
|
||||
* @todo In 2.0 force parameters to be an array, then remove is_array() checks in HttpCommandExecutor
|
||||
* @todo In 2.0 make constructor private. Use by default static `::create()` with sessionID type string.
|
||||
*/
|
||||
public function __construct($session_id, $name, $parameters)
|
||||
{
|
||||
$this->sessionID = $session_id;
|
||||
$this->name = $name;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function newSession(array $parameters)
|
||||
{
|
||||
// TODO: In 2.0 call empty constructor and assign properties directly.
|
||||
return new self(null, DriverCommand::NEW_SESSION, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null Could be null for newSession command
|
||||
*/
|
||||
public function getSessionID()
|
||||
{
|
||||
return $this->sessionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Facebook\WebDriver\Remote;
|
||||
|
||||
class WebDriverResponse
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $status;
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sessionID;
|
||||
|
||||
/**
|
||||
* @param null|string $session_id
|
||||
*/
|
||||
public function __construct($session_id = null)
|
||||
{
|
||||
$this->sessionID = $session_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|int
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function setStatus($status)
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getSessionID()
|
||||
{
|
||||
return $this->sessionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $session_id
|
||||
* @return WebDriverResponse
|
||||
*/
|
||||
public function setSessionID($session_id)
|
||||
{
|
||||
$this->sessionID = $session_id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user