RemoteWebDriver.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  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. protected function __construct(
  58. HttpCommandExecutor $commandExecutor,
  59. $sessionId,
  60. WebDriverCapabilities $capabilities = null
  61. ) {
  62. $this->executor = $commandExecutor;
  63. $this->sessionID = $sessionId;
  64. if ($capabilities !== null) {
  65. $this->capabilities = $capabilities;
  66. }
  67. }
  68. /**
  69. * Construct the RemoteWebDriver by a desired capabilities.
  70. *
  71. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  72. * @param DesiredCapabilities|array $desired_capabilities The desired capabilities
  73. * @param int|null $connection_timeout_in_ms Set timeout for the connect phase to remote Selenium WebDriver server
  74. * @param int|null $request_timeout_in_ms Set the maximum time of a request to remote Selenium WebDriver server
  75. * @param string|null $http_proxy The proxy to tunnel requests to the remote Selenium WebDriver through
  76. * @param int|null $http_proxy_port The proxy port to tunnel requests to the remote Selenium WebDriver through
  77. * @param DesiredCapabilities $required_capabilities The required capabilities
  78. * @return RemoteWebDriver
  79. */
  80. public static function create(
  81. $selenium_server_url = 'http://localhost:4444/wd/hub',
  82. $desired_capabilities = null,
  83. $connection_timeout_in_ms = null,
  84. $request_timeout_in_ms = null,
  85. $http_proxy = null,
  86. $http_proxy_port = null,
  87. DesiredCapabilities $required_capabilities = null
  88. ) {
  89. $selenium_server_url = preg_replace('#/+$#', '', $selenium_server_url);
  90. $desired_capabilities = self::castToDesiredCapabilitiesObject($desired_capabilities);
  91. $executor = new HttpCommandExecutor($selenium_server_url, $http_proxy, $http_proxy_port);
  92. if ($connection_timeout_in_ms !== null) {
  93. $executor->setConnectionTimeout($connection_timeout_in_ms);
  94. }
  95. if ($request_timeout_in_ms !== null) {
  96. $executor->setRequestTimeout($request_timeout_in_ms);
  97. }
  98. if ($required_capabilities !== null) {
  99. // TODO: Selenium (as of v3.0.1) does accept requiredCapabilities only as a property of desiredCapabilities.
  100. // This will probably change in future with the W3C WebDriver spec, but is the only way how to pass these
  101. // values now.
  102. $desired_capabilities->setCapability('requiredCapabilities', $required_capabilities->toArray());
  103. }
  104. $command = new WebDriverCommand(
  105. null,
  106. DriverCommand::NEW_SESSION,
  107. ['desiredCapabilities' => $desired_capabilities->toArray()]
  108. );
  109. $response = $executor->execute($command);
  110. $returnedCapabilities = new DesiredCapabilities($response->getValue());
  111. $driver = new static($executor, $response->getSessionID(), $returnedCapabilities);
  112. return $driver;
  113. }
  114. /**
  115. * Cast legacy types (array or null) to DesiredCapabilities object. To be removed in future when instance of
  116. * DesiredCapabilities will be required.
  117. *
  118. * @param array|DesiredCapabilities|null $desired_capabilities
  119. * @return DesiredCapabilities
  120. */
  121. protected static function castToDesiredCapabilitiesObject($desired_capabilities = null)
  122. {
  123. if ($desired_capabilities === null) {
  124. return new DesiredCapabilities();
  125. }
  126. if (is_array($desired_capabilities)) {
  127. return new DesiredCapabilities($desired_capabilities);
  128. }
  129. return $desired_capabilities;
  130. }
  131. /**
  132. * [Experimental] Construct the RemoteWebDriver by an existing session.
  133. *
  134. * This constructor can boost the performance a lot by reusing the same browser for the whole test suite.
  135. * You cannot the desired capabilities because the session was created before.
  136. *
  137. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  138. * @param string $session_id The existing session id
  139. * @return RemoteWebDriver
  140. */
  141. public static function createBySessionID($session_id, $selenium_server_url = 'http://localhost:4444/wd/hub')
  142. {
  143. $executor = new HttpCommandExecutor($selenium_server_url);
  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. * Prepare arguments for JavaScript injection
  263. *
  264. * @param array $arguments
  265. * @return array
  266. */
  267. private function prepareScriptArguments(array $arguments)
  268. {
  269. $args = [];
  270. foreach ($arguments as $key => $value) {
  271. if ($value instanceof WebDriverElement) {
  272. $args[$key] = ['ELEMENT' => $value->getID()];
  273. } else {
  274. if (is_array($value)) {
  275. $value = $this->prepareScriptArguments($value);
  276. }
  277. $args[$key] = $value;
  278. }
  279. }
  280. return $args;
  281. }
  282. /**
  283. * Inject a snippet of JavaScript into the page for execution in the context
  284. * of the currently selected frame. The executed script is assumed to be
  285. * synchronous and the result of evaluating the script will be returned.
  286. *
  287. * @param string $script The script to inject.
  288. * @param array $arguments The arguments of the script.
  289. * @return mixed The return value of the script.
  290. */
  291. public function executeScript($script, array $arguments = [])
  292. {
  293. $params = [
  294. 'script' => $script,
  295. 'args' => $this->prepareScriptArguments($arguments),
  296. ];
  297. return $this->execute(DriverCommand::EXECUTE_SCRIPT, $params);
  298. }
  299. /**
  300. * Inject a snippet of JavaScript into the page for asynchronous execution in
  301. * the context of the currently selected frame.
  302. *
  303. * The driver will pass a callback as the last argument to the snippet, and
  304. * block until the callback is invoked.
  305. *
  306. * @see WebDriverExecuteAsyncScriptTestCase
  307. *
  308. * @param string $script The script to inject.
  309. * @param array $arguments The arguments of the script.
  310. * @return mixed The value passed by the script to the callback.
  311. */
  312. public function executeAsyncScript($script, array $arguments = [])
  313. {
  314. $params = [
  315. 'script' => $script,
  316. 'args' => $this->prepareScriptArguments($arguments),
  317. ];
  318. return $this->execute(
  319. DriverCommand::EXECUTE_ASYNC_SCRIPT,
  320. $params
  321. );
  322. }
  323. /**
  324. * Take a screenshot of the current page.
  325. *
  326. * @param string $save_as The path of the screenshot to be saved.
  327. * @return string The screenshot in PNG format.
  328. */
  329. public function takeScreenshot($save_as = null)
  330. {
  331. $screenshot = base64_decode(
  332. $this->execute(DriverCommand::SCREENSHOT)
  333. );
  334. if ($save_as) {
  335. file_put_contents($save_as, $screenshot);
  336. }
  337. return $screenshot;
  338. }
  339. /**
  340. * Construct a new WebDriverWait by the current WebDriver instance.
  341. * Sample usage:
  342. *
  343. * $driver->wait(20, 1000)->until(
  344. * WebDriverExpectedCondition::titleIs('WebDriver Page')
  345. * );
  346. *
  347. * @param int $timeout_in_second
  348. * @param int $interval_in_millisecond
  349. *
  350. * @return WebDriverWait
  351. */
  352. public function wait($timeout_in_second = 30, $interval_in_millisecond = 250)
  353. {
  354. return new WebDriverWait(
  355. $this,
  356. $timeout_in_second,
  357. $interval_in_millisecond
  358. );
  359. }
  360. /**
  361. * An abstraction for managing stuff you would do in a browser menu. For example, adding and deleting cookies.
  362. *
  363. * @return WebDriverOptions
  364. */
  365. public function manage()
  366. {
  367. return new WebDriverOptions($this->getExecuteMethod());
  368. }
  369. /**
  370. * An abstraction allowing the driver to access the browser's history and to navigate to a given URL.
  371. *
  372. * @return WebDriverNavigation
  373. * @see WebDriverNavigation
  374. */
  375. public function navigate()
  376. {
  377. return new WebDriverNavigation($this->getExecuteMethod());
  378. }
  379. /**
  380. * Switch to a different window or frame.
  381. *
  382. * @return RemoteTargetLocator
  383. * @see RemoteTargetLocator
  384. */
  385. public function switchTo()
  386. {
  387. return new RemoteTargetLocator($this->getExecuteMethod(), $this);
  388. }
  389. /**
  390. * @return RemoteMouse
  391. */
  392. public function getMouse()
  393. {
  394. if (!$this->mouse) {
  395. $this->mouse = new RemoteMouse($this->getExecuteMethod());
  396. }
  397. return $this->mouse;
  398. }
  399. /**
  400. * @return RemoteKeyboard
  401. */
  402. public function getKeyboard()
  403. {
  404. if (!$this->keyboard) {
  405. $this->keyboard = new RemoteKeyboard($this->getExecuteMethod());
  406. }
  407. return $this->keyboard;
  408. }
  409. /**
  410. * @return RemoteTouchScreen
  411. */
  412. public function getTouch()
  413. {
  414. if (!$this->touch) {
  415. $this->touch = new RemoteTouchScreen($this->getExecuteMethod());
  416. }
  417. return $this->touch;
  418. }
  419. /**
  420. * @return RemoteExecuteMethod
  421. */
  422. protected function getExecuteMethod()
  423. {
  424. if (!$this->executeMethod) {
  425. $this->executeMethod = new RemoteExecuteMethod($this);
  426. }
  427. return $this->executeMethod;
  428. }
  429. /**
  430. * Construct a new action builder.
  431. *
  432. * @return WebDriverActions
  433. */
  434. public function action()
  435. {
  436. return new WebDriverActions($this);
  437. }
  438. /**
  439. * Return the WebDriverElement with the given id.
  440. *
  441. * @param string $id The id of the element to be created.
  442. * @return RemoteWebElement
  443. */
  444. protected function newElement($id)
  445. {
  446. return new RemoteWebElement($this->getExecuteMethod(), $id);
  447. }
  448. /**
  449. * Set the command executor of this RemoteWebdriver
  450. *
  451. * @deprecated To be removed in the future. Executor should be passed in the constructor.
  452. * @internal
  453. * @codeCoverageIgnore
  454. * @param WebDriverCommandExecutor $executor Despite the typehint, it have be an instance of HttpCommandExecutor.
  455. * @return RemoteWebDriver
  456. */
  457. public function setCommandExecutor(WebDriverCommandExecutor $executor)
  458. {
  459. $this->executor = $executor;
  460. return $this;
  461. }
  462. /**
  463. * Get the command executor of this RemoteWebdriver
  464. *
  465. * @return HttpCommandExecutor
  466. */
  467. public function getCommandExecutor()
  468. {
  469. return $this->executor;
  470. }
  471. /**
  472. * Set the session id of the RemoteWebDriver.
  473. *
  474. * @deprecated To be removed in the future. Session ID should be passed in the constructor.
  475. * @internal
  476. * @codeCoverageIgnore
  477. * @param string $session_id
  478. * @return RemoteWebDriver
  479. */
  480. public function setSessionID($session_id)
  481. {
  482. $this->sessionID = $session_id;
  483. return $this;
  484. }
  485. /**
  486. * Get current selenium sessionID
  487. *
  488. * @return string
  489. */
  490. public function getSessionID()
  491. {
  492. return $this->sessionID;
  493. }
  494. /**
  495. * Get capabilities of the RemoteWebDriver.
  496. *
  497. * @return WebDriverCapabilities
  498. */
  499. public function getCapabilities()
  500. {
  501. return $this->capabilities;
  502. }
  503. /**
  504. * Returns a list of the currently active sessions.
  505. *
  506. * @param string $selenium_server_url The url of the remote Selenium WebDriver server
  507. * @param int $timeout_in_ms
  508. * @return array
  509. */
  510. public static function getAllSessions($selenium_server_url = 'http://localhost:4444/wd/hub', $timeout_in_ms = 30000)
  511. {
  512. $executor = new HttpCommandExecutor($selenium_server_url);
  513. $executor->setConnectionTimeout($timeout_in_ms);
  514. $command = new WebDriverCommand(
  515. null,
  516. DriverCommand::GET_ALL_SESSIONS,
  517. []
  518. );
  519. return $executor->execute($command)->getValue();
  520. }
  521. public function execute($command_name, $params = [])
  522. {
  523. $command = new WebDriverCommand(
  524. $this->sessionID,
  525. $command_name,
  526. $params
  527. );
  528. if ($this->executor) {
  529. $response = $this->executor->execute($command);
  530. return $response->getValue();
  531. }
  532. return null;
  533. }
  534. }