RemoteWebDriver.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. <?php
  2. // Copyright 2004-present Facebook. All Rights Reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. namespace Facebook\WebDriver\Remote;
  16. use Facebook\WebDriver\Interactions\WebDriverActions;
  17. use Facebook\WebDriver\JavaScriptExecutor;
  18. use Facebook\WebDriver\WebDriver;
  19. use Facebook\WebDriver\WebDriverBy;
  20. use Facebook\WebDriver\WebDriverCapabilities;
  21. use Facebook\WebDriver\WebDriverCommandExecutor;
  22. use Facebook\WebDriver\WebDriverElement;
  23. use Facebook\WebDriver\WebDriverHasInputDevices;
  24. use Facebook\WebDriver\WebDriverNavigation;
  25. use Facebook\WebDriver\WebDriverOptions;
  26. use Facebook\WebDriver\WebDriverWait;
  27. class RemoteWebDriver implements WebDriver, JavaScriptExecutor, WebDriverHasInputDevices
  28. {
  29. /**
  30. * @var HttpCommandExecutor|null
  31. */
  32. protected $executor;
  33. /**
  34. * @var WebDriverCapabilities
  35. */
  36. protected $capabilities;
  37. /**
  38. * @var string
  39. */
  40. protected $sessionID;
  41. /**
  42. * @var RemoteMouse
  43. */
  44. protected $mouse;
  45. /**
  46. * @var RemoteKeyboard
  47. */
  48. protected $keyboard;
  49. /**
  50. * @var RemoteTouchScreen
  51. */
  52. protected $touch;
  53. /**
  54. * @var RemoteExecuteMethod
  55. */
  56. protected $executeMethod;
  57. /**
  58. * @param HttpCommandExecutor $commandExecutor
  59. * @param string $sessionId
  60. * @param WebDriverCapabilities|null $capabilities
  61. */
  62. protected function __construct(
  63. HttpCommandExecutor $commandExecutor,
  64. $sessionId,
  65. WebDriverCapabilities $capabilities = null
  66. ) {
  67. $this->executor = $commandExecutor;
  68. $this->sessionID = $sessionId;
  69. if ($capabilities !== null) {
  70. $this->capabilities = $capabilities;
  71. }
  72. }
  73. /**
  74. * Construct the RemoteWebDriver by a desired capabilities.
  75. *
  76. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  77. * @param DesiredCapabilities|array $desired_capabilities The desired capabilities
  78. * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server
  79. * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server
  80. * @param string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through
  81. * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through
  82. * @param DesiredCapabilities $required_capabilities The required capabilities
  83. * @return static
  84. */
  85. public static function create(
  86. $selenium_server_url = 'http://localhost:4444/wd/hub',
  87. $desired_capabilities = null,
  88. $connection_timeout_in_ms = null,
  89. $request_timeout_in_ms = null,
  90. $http_proxy = null,
  91. $http_proxy_port = null,
  92. DesiredCapabilities $required_capabilities = null
  93. ) {
  94. $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url);
  95. $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities);
  96. $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port);
  97. if ($connection_timeout_in_ms !== null) {
  98. $executor->setConnectionTimeout($connection_timeout_in_ms);
  99. }
  100. if ($request_timeout_in_ms !== null) {
  101. $executor->setRequestTimeout($request_timeout_in_ms);
  102. }
  103. if ($required_capabilities !== null) {
  104. // TODO: Selenium (as of v3.0.1) does accept requiredCapabilities only as a property of desiredCapabilities.
  105. // This will probably change in future with the W3C WebDriver spec, but is the only way how to pass these
  106. // values now.
  107. $desired_capabilities->setCapability('requiredCapabilities', $required_capabilities->toArray());
  108. }
  109. $command = new WebDriverCommand(
  110. null,
  111. DriverCommand::NEW_SESSION,
  112. ['desiredCapabilities' => $desired_capabilities->toArray()]
  113. );
  114. $response = $executor->execute($command);
  115. $returnedCapabilities = new DesiredCapabilities($response->getValue());
  116. $driver = new static($executor, $response->getSessionID(), $returnedCapabilities);
  117. return $driver;
  118. }
  119. /**
  120. * [Experimental] Construct the RemoteWebDriver by an existing session.
  121. *
  122. * This constructor can boost the performance a lot by reusing the same browser for the whole test suite.
  123. * You cannot pass the desired capabilities because the session was created before.
  124. *
  125. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  126. * @param string $session_id The existing session id
  127. * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server
  128. * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server
  129. * @return static
  130. */
  131. public static function createBySessionID(
  132. $session_id,
  133. $selenium_server_url = 'http://localhost:4444/wd/hub',
  134. $connection_timeout_in_ms = null,
  135. $request_timeout_in_ms = null
  136. ) {
  137. $executor = new HttpCommandExecutor($selenium_server_url);
  138. if ($connection_timeout_in_ms !== null) {
  139. $executor->setConnectionTimeout($connection_timeout_in_ms);
  140. }
  141. if ($request_timeout_in_ms !== null) {
  142. $executor->setRequestTimeout($request_timeout_in_ms);
  143. }
  144. return new static($executor, $session_id);
  145. }
  146. /**
  147. * Close the current window.
  148. *
  149. * @return RemoteWebDriver The current instance.
  150. */
  151. public function close()
  152. {
  153. $this->execute(DriverCommand::CLOSE, []);
  154. return $this;
  155. }
  156. /**
  157. * Find the first WebDriverElement using the given mechanism.
  158. *
  159. * @param WebDriverBy $by
  160. * @return RemoteWebElement NoSuchElementException is thrown in HttpCommandExecutor if no element is found.
  161. * @see WebDriverBy
  162. */
  163. public function findElement(WebDriverBy $by)
  164. {
  165. $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()];
  166. $raw_element = $this->execute(
  167. DriverCommand::FIND_ELEMENT,
  168. $params
  169. );
  170. return $this->newElement($raw_element['ELEMENT']);
  171. }
  172. /**
  173. * Find all WebDriverElements within the current page using the given mechanism.
  174. *
  175. * @param WebDriverBy $by
  176. * @return RemoteWebElement[] A list of all WebDriverElements, or an empty array if nothing matches
  177. * @see WebDriverBy
  178. */
  179. public function findElements(WebDriverBy $by)
  180. {
  181. $params = ['using' => $by->getMechanism(), 'value' => $by->getValue()];
  182. $raw_elements = $this->execute(
  183. DriverCommand::FIND_ELEMENTS,
  184. $params
  185. );
  186. $elements = [];
  187. foreach ($raw_elements as $raw_element) {
  188. $elements[] = $this->newElement($raw_element['ELEMENT']);
  189. }
  190. return $elements;
  191. }
  192. /**
  193. * Load a new web page in the current browser window.
  194. *
  195. * @param string $url
  196. *
  197. * @return RemoteWebDriver The current instance.
  198. */
  199. public function get($url)
  200. {
  201. $params = ['url' => (string) $url];
  202. $this->execute(DriverCommand::GET, $params);
  203. return $this;
  204. }
  205. /**
  206. * Get a string representing the current URL that the browser is looking at.
  207. *
  208. * @return string The current URL.
  209. */
  210. public function getCurrentURL()
  211. {
  212. return $this->execute(DriverCommand::GET_CURRENT_URL);
  213. }
  214. /**
  215. * Get the source of the last loaded page.
  216. *
  217. * @return string The current page source.
  218. */
  219. public function getPageSource()
  220. {
  221. return $this->execute(DriverCommand::GET_PAGE_SOURCE);
  222. }
  223. /**
  224. * Get the title of the current page.
  225. *
  226. * @return string The title of the current page.
  227. */
  228. public function getTitle()
  229. {
  230. return $this->execute(DriverCommand::GET_TITLE);
  231. }
  232. /**
  233. * Return an opaque handle to this window that uniquely identifies it within this driver instance.
  234. *
  235. * @return string The current window handle.
  236. */
  237. public function getWindowHandle()
  238. {
  239. return $this->execute(
  240. DriverCommand::GET_CURRENT_WINDOW_HANDLE,
  241. []
  242. );
  243. }
  244. /**
  245. * Get all window handles available to the current session.
  246. *
  247. * @return array An array of string containing all available window handles.
  248. */
  249. public function getWindowHandles()
  250. {
  251. return $this->execute(DriverCommand::GET_WINDOW_HANDLES, []);
  252. }
  253. /**
  254. * Quits this driver, closing every associated window.
  255. */
  256. public function quit()
  257. {
  258. $this->execute(DriverCommand::QUIT);
  259. $this->executor = null;
  260. }
  261. /**
  262. * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame.
  263. * The executed script is assumed to be synchronous and the result of evaluating the script will be returned.
  264. *
  265. * @param string $script The script to inject.
  266. * @param array $arguments The arguments of the script.
  267. * @return mixed The return value of the script.
  268. */
  269. public function executeScript($script, array $arguments = [])
  270. {
  271. $params = [
  272. 'script' => $script,
  273. 'args' => $this->prepareScriptArguments($arguments),
  274. ];
  275. return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params);
  276. }
  277. /**
  278. * Inject a snippet of JavaScript into the page for asynchronous execution in the context of the currently selected
  279. * frame.
  280. *
  281. * The driver will pass a callback as the last argument to the snippet, and block until the callback is invoked.
  282. *
  283. * You may need to define script timeout using `setScriptTimeout()` method of `WebDriverTimeouts` first.
  284. *
  285. * @param string $script The script to inject.
  286. * @param array $arguments The arguments of the script.
  287. * @return mixed The value passed by the script to the callback.
  288. */
  289. public function executeAsyncScript($script, array $arguments = [])
  290. {
  291. $params = [
  292. 'script' => $script,
  293. 'args' => $this->prepareScriptArguments($arguments),
  294. ];
  295. return $this->execute(
  296. DriverCommand::EXECUTE_ASYNC_SCRIPT,
  297. $params
  298. );
  299. }
  300. /**
  301. * Take a screenshot of the current page.
  302. *
  303. * @param string $save_as The path of the screenshot to be saved.
  304. * @return string The screenshot in PNG format.
  305. */
  306. public function takeScreenshot($save_as = null)
  307. {
  308. $screenshot = base64_decode(
  309. $this->execute(DriverCommand::SCREENSHOT)
  310. );
  311. if ($save_as) {
  312. file_put_contents($save_as, $screenshot);
  313. }
  314. return $screenshot;
  315. }
  316. /**
  317. * Construct a new WebDriverWait by the current WebDriver instance.
  318. * Sample usage:
  319. *
  320. * ```
  321. * $driver->wait(20, 1000)->until(
  322. * WebDriverExpectedCondition::titleIs('WebDriver Page')
  323. * );
  324. * ```
  325. * @param int $timeout_in_second
  326. * @param int $interval_in_millisecond
  327. *
  328. * @return WebDriverWait
  329. */
  330. public function wait($timeout_in_second = 30, $interval_in_millisecond = 250)
  331. {
  332. return new WebDriverWait(
  333. $this,
  334. $timeout_in_second,
  335. $interval_in_millisecond
  336. );
  337. }
  338. /**
  339. * An abstraction for managing stuff you would do in a browser menu. For example, adding and deleting cookies.
  340. *
  341. * @return WebDriverOptions
  342. */
  343. public function manage()
  344. {
  345. return new WebDriverOptions($this->getExecuteMethod());
  346. }
  347. /**
  348. * An abstraction allowing the driver to access the browser's history and to navigate to a given URL.
  349. *
  350. * @return WebDriverNavigation
  351. * @see WebDriverNavigation
  352. */
  353. public function navigate()
  354. {
  355. return new WebDriverNavigation($this->getExecuteMethod());
  356. }
  357. /**
  358. * Switch to a different window or frame.
  359. *
  360. * @return RemoteTargetLocator
  361. * @see RemoteTargetLocator
  362. */
  363. public function switchTo()
  364. {
  365. return new RemoteTargetLocator($this->getExecuteMethod(), $this);
  366. }
  367. /**
  368. * @return RemoteMouse
  369. */
  370. public function getMouse()
  371. {
  372. if (!$this->mouse) {
  373. $this->mouse = new RemoteMouse($this->getExecuteMethod());
  374. }
  375. return $this->mouse;
  376. }
  377. /**
  378. * @return RemoteKeyboard
  379. */
  380. public function getKeyboard()
  381. {
  382. if (!$this->keyboard) {
  383. $this->keyboard = new RemoteKeyboard($this->getExecuteMethod());
  384. }
  385. return $this->keyboard;
  386. }
  387. /**
  388. * @return RemoteTouchScreen
  389. */
  390. public function getTouch()
  391. {
  392. if (!$this->touch) {
  393. $this->touch = new RemoteTouchScreen($this->getExecuteMethod());
  394. }
  395. return $this->touch;
  396. }
  397. /**
  398. * Construct a new action builder.
  399. *
  400. * @return WebDriverActions
  401. */
  402. public function action()
  403. {
  404. return new WebDriverActions($this);
  405. }
  406. /**
  407. * Set the command executor of this RemoteWebdriver
  408. *
  409. * @deprecated To be removed in the future. Executor should be passed in the constructor.
  410. * @internal
  411. * @codeCoverageIgnore
  412. * @param WebDriverCommandExecutor $executor Despite the typehint, it have be an instance of HttpCommandExecutor.
  413. * @return RemoteWebDriver
  414. */
  415. public function setCommandExecutor(WebDriverCommandExecutor $executor)
  416. {
  417. $this->executor = $executor;
  418. return $this;
  419. }
  420. /**
  421. * Get the command executor of this RemoteWebdriver
  422. *
  423. * @return HttpCommandExecutor
  424. */
  425. public function getCommandExecutor()
  426. {
  427. return $this->executor;
  428. }
  429. /**
  430. * Set the session id of the RemoteWebDriver.
  431. *
  432. * @deprecated To be removed in the future. Session ID should be passed in the constructor.
  433. * @internal
  434. * @codeCoverageIgnore
  435. * @param string $session_id
  436. * @return RemoteWebDriver
  437. */
  438. public function setSessionID($session_id)
  439. {
  440. $this->sessionID = $session_id;
  441. return $this;
  442. }
  443. /**
  444. * Get current selenium sessionID
  445. *
  446. * @return string
  447. */
  448. public function getSessionID()
  449. {
  450. return $this->sessionID;
  451. }
  452. /**
  453. * Get capabilities of the RemoteWebDriver.
  454. *
  455. * @return WebDriverCapabilities
  456. */
  457. public function getCapabilities()
  458. {
  459. return $this->capabilities;
  460. }
  461. /**
  462. * Returns a list of the currently active sessions.
  463. *
  464. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  465. * @param int $timeout_in_ms
  466. * @return array
  467. */
  468. public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000)
  469. {
  470. $executor = new HttpCommandExecutor($selenium_server_url);
  471. $executor->setConnectionTimeout($timeout_in_ms);
  472. $command = new WebDriverCommand(
  473. null,
  474. DriverCommand::GET_ALL_SESSIONS,
  475. []
  476. );
  477. return $executor->execute($command)->getValue();
  478. }
  479. public function execute($command_name, $params = [])
  480. {
  481. $command = new WebDriverCommand(
  482. $this->sessionID,
  483. $command_name,
  484. $params
  485. );
  486. if ($this->executor) {
  487. $response = $this->executor->execute($command);
  488. return $response->getValue();
  489. }
  490. return null;
  491. }
  492. /**
  493. * Prepare arguments for JavaScript injection
  494. *
  495. * @param array $arguments
  496. * @return array
  497. */
  498. protected function prepareScriptArguments(array $arguments)
  499. {
  500. $args = [];
  501. foreach ($arguments as $key => $value) {
  502. if ($value instanceof WebDriverElement) {
  503. $args[$key] = ['ELEMENT' => $value->getID()];
  504. } else {
  505. if (is_array($value)) {
  506. $value = $this->prepareScriptArguments($value);
  507. }
  508. $args[$key] = $value;
  509. }
  510. }
  511. return $args;
  512. }
  513. /**
  514. * @return RemoteExecuteMethod
  515. */
  516. protected function getExecuteMethod()
  517. {
  518. if (!$this->executeMethod) {
  519. $this->executeMethod = new RemoteExecuteMethod($this);
  520. }
  521. return $this->executeMethod;
  522. }
  523. /**
  524. * Return the WebDriverElement with the given id.
  525. *
  526. * @param string $id The id of the element to be created.
  527. * @return RemoteWebElement
  528. */
  529. protected function newElement($id)
  530. {
  531. return new RemoteWebElement($this->getExecuteMethod(), $id);
  532. }
  533. /**
  534. * Cast legacy types (array or null) to DesiredCapabilities object. To be removed in future when instance of
  535. * DesiredCapabilities will be required.
  536. *
  537. * @param array|DesiredCapabilities|null $desired_capabilities
  538. * @return DesiredCapabilities
  539. */
  540. protected static function castToDesiredCapabilitiesObject($desired_capabilities = null)
  541. {
  542. if ($desired_capabilities === null) {
  543. return new DesiredCapabilities();
  544. }
  545. if (is_array($desired_capabilities)) {
  546. return new DesiredCapabilities($desired_capabilities);
  547. }
  548. return $desired_capabilities;
  549. }
  550. }