RemoteWebElement.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  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\Exception\WebDriverException;
  17. use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
  18. use Facebook\WebDriver\Internal\WebDriverLocatable;
  19. use Facebook\WebDriver\WebDriverBy;
  20. use Facebook\WebDriver\WebDriverDimension;
  21. use Facebook\WebDriver\WebDriverElement;
  22. use Facebook\WebDriver\WebDriverKeys;
  23. use Facebook\WebDriver\WebDriverPoint;
  24. use ZipArchive;
  25. /**
  26. * Represents an HTML element.
  27. */
  28. class RemoteWebElement implements WebDriverElement, WebDriverLocatable
  29. {
  30. /**
  31. * @var RemoteExecuteMethod
  32. */
  33. protected $executor;
  34. /**
  35. * @var string
  36. */
  37. protected $id;
  38. /**
  39. * @var UselessFileDetector
  40. */
  41. protected $fileDetector;
  42. /**
  43. * @param RemoteExecuteMethod $executor
  44. * @param string $id
  45. */
  46. public function __construct(RemoteExecuteMethod $executor, $id)
  47. {
  48. $this->executor = $executor;
  49. $this->id = $id;
  50. $this->fileDetector = new UselessFileDetector();
  51. }
  52. /**
  53. * If this element is a TEXTAREA or text INPUT element, this will clear the value.
  54. *
  55. * @return RemoteWebElement The current instance.
  56. */
  57. public function clear()
  58. {
  59. $this->executor->execute(
  60. DriverCommand::CLEAR_ELEMENT,
  61. [':id' => $this->id]
  62. );
  63. return $this;
  64. }
  65. /**
  66. * Click this element.
  67. *
  68. * @return RemoteWebElement The current instance.
  69. */
  70. public function click()
  71. {
  72. $this->executor->execute(
  73. DriverCommand::CLICK_ELEMENT,
  74. [':id' => $this->id]
  75. );
  76. return $this;
  77. }
  78. /**
  79. * Find the first WebDriverElement within this element using the given mechanism.
  80. *
  81. * @param WebDriverBy $by
  82. * @return RemoteWebElement NoSuchElementException is thrown in
  83. * HttpCommandExecutor if no element is found.
  84. * @see WebDriverBy
  85. */
  86. public function findElement(WebDriverBy $by)
  87. {
  88. $params = [
  89. 'using' => $by->getMechanism(),
  90. 'value' => $by->getValue(),
  91. ':id' => $this->id,
  92. ];
  93. $raw_element = $this->executor->execute(
  94. DriverCommand::FIND_CHILD_ELEMENT,
  95. $params
  96. );
  97. return $this->newElement($raw_element['ELEMENT']);
  98. }
  99. /**
  100. * Find all WebDriverElements within this element using the given mechanism.
  101. *
  102. * @param WebDriverBy $by
  103. * @return RemoteWebElement[] A list of all WebDriverElements, or an empty
  104. * array if nothing matches
  105. * @see WebDriverBy
  106. */
  107. public function findElements(WebDriverBy $by)
  108. {
  109. $params = [
  110. 'using' => $by->getMechanism(),
  111. 'value' => $by->getValue(),
  112. ':id' => $this->id,
  113. ];
  114. $raw_elements = $this->executor->execute(
  115. DriverCommand::FIND_CHILD_ELEMENTS,
  116. $params
  117. );
  118. $elements = [];
  119. foreach ($raw_elements as $raw_element) {
  120. $elements[] = $this->newElement($raw_element['ELEMENT']);
  121. }
  122. return $elements;
  123. }
  124. /**
  125. * Get the value of a the given attribute of the element.
  126. *
  127. * @param string $attribute_name The name of the attribute.
  128. * @return string|null The value of the attribute.
  129. */
  130. public function getAttribute($attribute_name)
  131. {
  132. $params = [
  133. ':name' => $attribute_name,
  134. ':id' => $this->id,
  135. ];
  136. return $this->executor->execute(
  137. DriverCommand::GET_ELEMENT_ATTRIBUTE,
  138. $params
  139. );
  140. }
  141. /**
  142. * Get the value of a given CSS property.
  143. *
  144. * @param string $css_property_name The name of the CSS property.
  145. * @return string The value of the CSS property.
  146. */
  147. public function getCSSValue($css_property_name)
  148. {
  149. $params = [
  150. ':propertyName' => $css_property_name,
  151. ':id' => $this->id,
  152. ];
  153. return $this->executor->execute(
  154. DriverCommand::GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
  155. $params
  156. );
  157. }
  158. /**
  159. * Get the location of element relative to the top-left corner of the page.
  160. *
  161. * @return WebDriverPoint The location of the element.
  162. */
  163. public function getLocation()
  164. {
  165. $location = $this->executor->execute(
  166. DriverCommand::GET_ELEMENT_LOCATION,
  167. [':id' => $this->id]
  168. );
  169. return new WebDriverPoint($location['x'], $location['y']);
  170. }
  171. /**
  172. * Try scrolling the element into the view port and return the location of
  173. * element relative to the top-left corner of the page afterwards.
  174. *
  175. * @return WebDriverPoint The location of the element.
  176. */
  177. public function getLocationOnScreenOnceScrolledIntoView()
  178. {
  179. $location = $this->executor->execute(
  180. DriverCommand::GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW,
  181. [':id' => $this->id]
  182. );
  183. return new WebDriverPoint($location['x'], $location['y']);
  184. }
  185. /**
  186. * @return WebDriverCoordinates
  187. */
  188. public function getCoordinates()
  189. {
  190. $element = $this;
  191. $on_screen = null; // planned but not yet implemented
  192. $in_view_port = function () use ($element) {
  193. return $element->getLocationOnScreenOnceScrolledIntoView();
  194. };
  195. $on_page = function () use ($element) {
  196. return $element->getLocation();
  197. };
  198. $auxiliary = $this->getID();
  199. return new WebDriverCoordinates(
  200. $on_screen,
  201. $in_view_port,
  202. $on_page,
  203. $auxiliary
  204. );
  205. }
  206. /**
  207. * Get the size of element.
  208. *
  209. * @return WebDriverDimension The dimension of the element.
  210. */
  211. public function getSize()
  212. {
  213. $size = $this->executor->execute(
  214. DriverCommand::GET_ELEMENT_SIZE,
  215. [':id' => $this->id]
  216. );
  217. return new WebDriverDimension($size['width'], $size['height']);
  218. }
  219. /**
  220. * Get the tag name of this element.
  221. *
  222. * @return string The tag name.
  223. */
  224. public function getTagName()
  225. {
  226. // Force tag name to be lowercase as expected by protocol for Opera driver
  227. // until this issue is not resolved :
  228. // https://github.com/operasoftware/operadriver/issues/102
  229. // Remove it when fixed to be consistent with the protocol.
  230. return strtolower($this->executor->execute(
  231. DriverCommand::GET_ELEMENT_TAG_NAME,
  232. [':id' => $this->id]
  233. ));
  234. }
  235. /**
  236. * Get the visible (i.e. not hidden by CSS) innerText of this element,
  237. * including sub-elements, without any leading or trailing whitespace.
  238. *
  239. * @return string The visible innerText of this element.
  240. */
  241. public function getText()
  242. {
  243. return $this->executor->execute(
  244. DriverCommand::GET_ELEMENT_TEXT,
  245. [':id' => $this->id]
  246. );
  247. }
  248. /**
  249. * Is this element displayed or not? This method avoids the problem of having
  250. * to parse an element's "style" attribute.
  251. *
  252. * @return bool
  253. */
  254. public function isDisplayed()
  255. {
  256. return $this->executor->execute(
  257. DriverCommand::IS_ELEMENT_DISPLAYED,
  258. [':id' => $this->id]
  259. );
  260. }
  261. /**
  262. * Is the element currently enabled or not? This will generally return true
  263. * for everything but disabled input elements.
  264. *
  265. * @return bool
  266. */
  267. public function isEnabled()
  268. {
  269. return $this->executor->execute(
  270. DriverCommand::IS_ELEMENT_ENABLED,
  271. [':id' => $this->id]
  272. );
  273. }
  274. /**
  275. * Determine whether or not this element is selected or not.
  276. *
  277. * @return bool
  278. */
  279. public function isSelected()
  280. {
  281. return $this->executor->execute(
  282. DriverCommand::IS_ELEMENT_SELECTED,
  283. [':id' => $this->id]
  284. );
  285. }
  286. /**
  287. * Simulate typing into an element, which may set its value.
  288. *
  289. * @param mixed $value The data to be typed.
  290. * @return RemoteWebElement The current instance.
  291. */
  292. public function sendKeys($value)
  293. {
  294. $local_file = $this->fileDetector->getLocalFile($value);
  295. if ($local_file === null) {
  296. $params = [
  297. 'value' => WebDriverKeys::encode($value),
  298. ':id' => $this->id,
  299. ];
  300. $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params);
  301. } else {
  302. $remote_path = $this->upload($local_file);
  303. $params = [
  304. 'value' => WebDriverKeys::encode($remote_path),
  305. ':id' => $this->id,
  306. ];
  307. $this->executor->execute(DriverCommand::SEND_KEYS_TO_ELEMENT, $params);
  308. }
  309. return $this;
  310. }
  311. /**
  312. * Upload a local file to the server
  313. *
  314. * @param string $local_file
  315. *
  316. * @throws WebDriverException
  317. * @return string The remote path of the file.
  318. */
  319. private function upload($local_file)
  320. {
  321. if (!is_file($local_file)) {
  322. throw new WebDriverException('You may only upload files: ' . $local_file);
  323. }
  324. // Create a temporary file in the system temp directory.
  325. $temp_zip = tempnam(sys_get_temp_dir(), 'WebDriverZip');
  326. $zip = new ZipArchive();
  327. if ($zip->open($temp_zip, ZipArchive::CREATE) !== true) {
  328. return false;
  329. }
  330. $info = pathinfo($local_file);
  331. $file_name = $info['basename'];
  332. $zip->addFile($local_file, $file_name);
  333. $zip->close();
  334. $params = [
  335. 'file' => base64_encode(file_get_contents($temp_zip)),
  336. ];
  337. $remote_path = $this->executor->execute(
  338. DriverCommand::UPLOAD_FILE,
  339. $params
  340. );
  341. unlink($temp_zip);
  342. return $remote_path;
  343. }
  344. /**
  345. * Set the fileDetector in order to let the RemoteWebElement to know that
  346. * you are going to upload a file.
  347. *
  348. * Basically, if you want WebDriver trying to send a file, set the fileDetector
  349. * to be LocalFileDetector. Otherwise, keep it UselessFileDetector.
  350. *
  351. * eg. $element->setFileDetector(new LocalFileDetector);
  352. *
  353. * @param FileDetector $detector
  354. * @return RemoteWebElement
  355. * @see FileDetector
  356. * @see LocalFileDetector
  357. * @see UselessFileDetector
  358. */
  359. public function setFileDetector(FileDetector $detector)
  360. {
  361. $this->fileDetector = $detector;
  362. return $this;
  363. }
  364. /**
  365. * If this current element is a form, or an element within a form, then this will be submitted to the remote server.
  366. *
  367. * @return RemoteWebElement The current instance.
  368. */
  369. public function submit()
  370. {
  371. $this->executor->execute(
  372. DriverCommand::SUBMIT_ELEMENT,
  373. [':id' => $this->id]
  374. );
  375. return $this;
  376. }
  377. /**
  378. * Get the opaque ID of the element.
  379. *
  380. * @return string The opaque ID.
  381. */
  382. public function getID()
  383. {
  384. return $this->id;
  385. }
  386. /**
  387. * Test if two element IDs refer to the same DOM element.
  388. *
  389. * @param WebDriverElement $other
  390. * @return bool
  391. */
  392. public function equals(WebDriverElement $other)
  393. {
  394. return $this->executor->execute(DriverCommand::ELEMENT_EQUALS, [
  395. ':id' => $this->id,
  396. ':other' => $other->getID(),
  397. ]);
  398. }
  399. /**
  400. * Return the WebDriverElement with $id
  401. *
  402. * @param string $id
  403. *
  404. * @return RemoteWebElement
  405. */
  406. protected function newElement($id)
  407. {
  408. return new static($this->executor, $id);
  409. }
  410. }