_element_handle.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. # Copyright (c) Microsoft Corporation.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import base64
  15. import mimetypes
  16. from pathlib import Path
  17. from typing import (
  18. TYPE_CHECKING,
  19. Any,
  20. Callable,
  21. Dict,
  22. List,
  23. Literal,
  24. Optional,
  25. Sequence,
  26. Union,
  27. cast,
  28. )
  29. from playwright._impl._api_structures import FilePayload, FloatRect, Position
  30. from playwright._impl._connection import ChannelOwner, from_nullable_channel
  31. from playwright._impl._helper import (
  32. Error,
  33. KeyboardModifier,
  34. MouseButton,
  35. async_writefile,
  36. locals_to_params,
  37. make_dirs_for_file,
  38. )
  39. from playwright._impl._js_handle import (
  40. JSHandle,
  41. Serializable,
  42. parse_result,
  43. serialize_argument,
  44. )
  45. from playwright._impl._set_input_files_helpers import convert_input_files
  46. if TYPE_CHECKING: # pragma: no cover
  47. from playwright._impl._frame import Frame
  48. from playwright._impl._locator import Locator
  49. class ElementHandle(JSHandle):
  50. def __init__(
  51. self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
  52. ) -> None:
  53. super().__init__(parent, type, guid, initializer)
  54. self._frame = cast("Frame", parent)
  55. async def _createSelectorForTest(self, name: str) -> Optional[str]:
  56. return await self._channel.send(
  57. "createSelectorForTest", self._frame._timeout, dict(name=name)
  58. )
  59. def as_element(self) -> Optional["ElementHandle"]:
  60. return self
  61. async def owner_frame(self) -> Optional["Frame"]:
  62. return from_nullable_channel(await self._channel.send("ownerFrame", None))
  63. async def content_frame(self) -> Optional["Frame"]:
  64. return from_nullable_channel(await self._channel.send("contentFrame", None))
  65. async def get_attribute(self, name: str) -> Optional[str]:
  66. return await self._channel.send("getAttribute", None, dict(name=name))
  67. async def text_content(self) -> Optional[str]:
  68. return await self._channel.send("textContent", None)
  69. async def inner_text(self) -> str:
  70. return await self._channel.send("innerText", None)
  71. async def inner_html(self) -> str:
  72. return await self._channel.send("innerHTML", None)
  73. async def is_checked(self) -> bool:
  74. return await self._channel.send("isChecked", None)
  75. async def is_disabled(self) -> bool:
  76. return await self._channel.send("isDisabled", None)
  77. async def is_editable(self) -> bool:
  78. return await self._channel.send("isEditable", None)
  79. async def is_enabled(self) -> bool:
  80. return await self._channel.send("isEnabled", None)
  81. async def is_hidden(self) -> bool:
  82. return await self._channel.send("isHidden", None)
  83. async def is_visible(self) -> bool:
  84. return await self._channel.send("isVisible", None)
  85. async def dispatch_event(self, type: str, eventInit: Dict = None) -> None:
  86. await self._channel.send(
  87. "dispatchEvent",
  88. None,
  89. dict(type=type, eventInit=serialize_argument(eventInit)),
  90. )
  91. async def scroll_into_view_if_needed(self, timeout: float = None) -> None:
  92. await self._channel.send(
  93. "scrollIntoViewIfNeeded", self._frame._timeout, locals_to_params(locals())
  94. )
  95. async def hover(
  96. self,
  97. modifiers: Sequence[KeyboardModifier] = None,
  98. position: Position = None,
  99. timeout: float = None,
  100. noWaitAfter: bool = None,
  101. force: bool = None,
  102. trial: bool = None,
  103. ) -> None:
  104. await self._channel.send(
  105. "hover", self._frame._timeout, locals_to_params(locals())
  106. )
  107. async def click(
  108. self,
  109. modifiers: Sequence[KeyboardModifier] = None,
  110. position: Position = None,
  111. delay: float = None,
  112. button: MouseButton = None,
  113. clickCount: int = None,
  114. timeout: float = None,
  115. force: bool = None,
  116. noWaitAfter: bool = None,
  117. trial: bool = None,
  118. steps: int = None,
  119. ) -> None:
  120. await self._channel.send(
  121. "click", self._frame._timeout, locals_to_params(locals())
  122. )
  123. async def dblclick(
  124. self,
  125. modifiers: Sequence[KeyboardModifier] = None,
  126. position: Position = None,
  127. delay: float = None,
  128. button: MouseButton = None,
  129. timeout: float = None,
  130. force: bool = None,
  131. noWaitAfter: bool = None,
  132. trial: bool = None,
  133. steps: int = None,
  134. ) -> None:
  135. await self._channel.send(
  136. "dblclick", self._frame._timeout, locals_to_params(locals())
  137. )
  138. async def select_option(
  139. self,
  140. value: Union[str, Sequence[str]] = None,
  141. index: Union[int, Sequence[int]] = None,
  142. label: Union[str, Sequence[str]] = None,
  143. element: Union["ElementHandle", Sequence["ElementHandle"]] = None,
  144. timeout: float = None,
  145. force: bool = None,
  146. noWaitAfter: bool = None,
  147. ) -> List[str]:
  148. params = locals_to_params(
  149. dict(
  150. timeout=timeout,
  151. force=force,
  152. **convert_select_option_values(value, index, label, element),
  153. )
  154. )
  155. return await self._channel.send("selectOption", self._frame._timeout, params)
  156. async def tap(
  157. self,
  158. modifiers: Sequence[KeyboardModifier] = None,
  159. position: Position = None,
  160. timeout: float = None,
  161. force: bool = None,
  162. noWaitAfter: bool = None,
  163. trial: bool = None,
  164. ) -> None:
  165. await self._channel.send(
  166. "tap", self._frame._timeout, locals_to_params(locals())
  167. )
  168. async def fill(
  169. self,
  170. value: str,
  171. timeout: float = None,
  172. noWaitAfter: bool = None,
  173. force: bool = None,
  174. ) -> None:
  175. await self._channel.send(
  176. "fill", self._frame._timeout, locals_to_params(locals())
  177. )
  178. async def select_text(self, force: bool = None, timeout: float = None) -> None:
  179. await self._channel.send(
  180. "selectText", self._frame._timeout, locals_to_params(locals())
  181. )
  182. async def input_value(self, timeout: float = None) -> str:
  183. return await self._channel.send(
  184. "inputValue", self._frame._timeout, locals_to_params(locals())
  185. )
  186. async def set_input_files(
  187. self,
  188. files: Union[
  189. str, Path, FilePayload, Sequence[Union[str, Path]], Sequence[FilePayload]
  190. ],
  191. timeout: float = None,
  192. noWaitAfter: bool = None,
  193. ) -> None:
  194. frame = await self.owner_frame()
  195. if not frame:
  196. raise Error("Cannot set input files to detached element")
  197. converted = await convert_input_files(files, frame.page.context)
  198. await self._channel.send(
  199. "setInputFiles",
  200. self._frame._timeout,
  201. {
  202. "timeout": timeout,
  203. **converted,
  204. },
  205. )
  206. async def focus(self) -> None:
  207. await self._channel.send("focus", None)
  208. async def type(
  209. self,
  210. text: str,
  211. delay: float = None,
  212. timeout: float = None,
  213. noWaitAfter: bool = None,
  214. ) -> None:
  215. await self._channel.send(
  216. "type", self._frame._timeout, locals_to_params(locals())
  217. )
  218. async def press(
  219. self,
  220. key: str,
  221. delay: float = None,
  222. timeout: float = None,
  223. noWaitAfter: bool = None,
  224. ) -> None:
  225. await self._channel.send(
  226. "press", self._frame._timeout, locals_to_params(locals())
  227. )
  228. async def set_checked(
  229. self,
  230. checked: bool,
  231. position: Position = None,
  232. timeout: float = None,
  233. force: bool = None,
  234. noWaitAfter: bool = None,
  235. trial: bool = None,
  236. ) -> None:
  237. if checked:
  238. await self.check(
  239. position=position,
  240. timeout=timeout,
  241. force=force,
  242. trial=trial,
  243. )
  244. else:
  245. await self.uncheck(
  246. position=position,
  247. timeout=timeout,
  248. force=force,
  249. trial=trial,
  250. )
  251. async def check(
  252. self,
  253. position: Position = None,
  254. timeout: float = None,
  255. force: bool = None,
  256. noWaitAfter: bool = None,
  257. trial: bool = None,
  258. ) -> None:
  259. await self._channel.send(
  260. "check", self._frame._timeout, locals_to_params(locals())
  261. )
  262. async def uncheck(
  263. self,
  264. position: Position = None,
  265. timeout: float = None,
  266. force: bool = None,
  267. noWaitAfter: bool = None,
  268. trial: bool = None,
  269. ) -> None:
  270. await self._channel.send(
  271. "uncheck", self._frame._timeout, locals_to_params(locals())
  272. )
  273. async def bounding_box(self) -> Optional[FloatRect]:
  274. return await self._channel.send("boundingBox", None)
  275. async def screenshot(
  276. self,
  277. timeout: float = None,
  278. type: Literal["jpeg", "png"] = None,
  279. path: Union[str, Path] = None,
  280. quality: int = None,
  281. omitBackground: bool = None,
  282. animations: Literal["allow", "disabled"] = None,
  283. caret: Literal["hide", "initial"] = None,
  284. scale: Literal["css", "device"] = None,
  285. mask: Sequence["Locator"] = None,
  286. maskColor: str = None,
  287. style: str = None,
  288. ) -> bytes:
  289. params = locals_to_params(locals())
  290. if "path" in params:
  291. if "type" not in params:
  292. params["type"] = determine_screenshot_type(params["path"])
  293. del params["path"]
  294. if "mask" in params:
  295. params["mask"] = list(
  296. map(
  297. lambda locator: (
  298. {
  299. "frame": locator._frame._channel,
  300. "selector": locator._selector,
  301. }
  302. ),
  303. params["mask"],
  304. )
  305. )
  306. encoded_binary = await self._channel.send(
  307. "screenshot", self._frame._timeout, params
  308. )
  309. decoded_binary = base64.b64decode(encoded_binary)
  310. if path:
  311. make_dirs_for_file(path)
  312. await async_writefile(path, decoded_binary)
  313. return decoded_binary
  314. async def query_selector(self, selector: str) -> Optional["ElementHandle"]:
  315. return from_nullable_channel(
  316. await self._channel.send("querySelector", None, dict(selector=selector))
  317. )
  318. async def query_selector_all(self, selector: str) -> List["ElementHandle"]:
  319. return list(
  320. map(
  321. cast(Callable[[Any], Any], from_nullable_channel),
  322. await self._channel.send(
  323. "querySelectorAll", None, dict(selector=selector)
  324. ),
  325. )
  326. )
  327. async def eval_on_selector(
  328. self,
  329. selector: str,
  330. expression: str,
  331. arg: Serializable = None,
  332. ) -> Any:
  333. return parse_result(
  334. await self._channel.send(
  335. "evalOnSelector",
  336. None,
  337. dict(
  338. selector=selector,
  339. expression=expression,
  340. arg=serialize_argument(arg),
  341. ),
  342. )
  343. )
  344. async def eval_on_selector_all(
  345. self,
  346. selector: str,
  347. expression: str,
  348. arg: Serializable = None,
  349. ) -> Any:
  350. return parse_result(
  351. await self._channel.send(
  352. "evalOnSelectorAll",
  353. None,
  354. dict(
  355. selector=selector,
  356. expression=expression,
  357. arg=serialize_argument(arg),
  358. ),
  359. )
  360. )
  361. async def wait_for_element_state(
  362. self,
  363. state: Literal[
  364. "disabled", "editable", "enabled", "hidden", "stable", "visible"
  365. ],
  366. timeout: float = None,
  367. ) -> None:
  368. await self._channel.send(
  369. "waitForElementState", self._frame._timeout, locals_to_params(locals())
  370. )
  371. async def wait_for_selector(
  372. self,
  373. selector: str,
  374. state: Literal["attached", "detached", "hidden", "visible"] = None,
  375. timeout: float = None,
  376. strict: bool = None,
  377. ) -> Optional["ElementHandle"]:
  378. return from_nullable_channel(
  379. await self._channel.send(
  380. "waitForSelector", self._frame._timeout, locals_to_params(locals())
  381. )
  382. )
  383. def convert_select_option_values(
  384. value: Union[str, Sequence[str]] = None,
  385. index: Union[int, Sequence[int]] = None,
  386. label: Union[str, Sequence[str]] = None,
  387. element: Union["ElementHandle", Sequence["ElementHandle"]] = None,
  388. ) -> Any:
  389. if value is None and index is None and label is None and element is None:
  390. return {}
  391. options: Any = None
  392. elements: Any = None
  393. if value is not None:
  394. if isinstance(value, str):
  395. value = [value]
  396. options = (options or []) + list(map(lambda e: dict(valueOrLabel=e), value))
  397. if index is not None:
  398. if isinstance(index, int):
  399. index = [index]
  400. options = (options or []) + list(map(lambda e: dict(index=e), index))
  401. if label is not None:
  402. if isinstance(label, str):
  403. label = [label]
  404. options = (options or []) + list(map(lambda e: dict(label=e), label))
  405. if element:
  406. if isinstance(element, ElementHandle):
  407. element = [element]
  408. elements = list(map(lambda e: e._channel, element))
  409. return dict(options=options, elements=elements)
  410. def determine_screenshot_type(path: Union[str, Path]) -> Literal["jpeg", "png"]:
  411. mime_type, _ = mimetypes.guess_type(path)
  412. if mime_type == "image/png":
  413. return "png"
  414. if mime_type == "image/jpeg":
  415. return "jpeg"
  416. raise Error(f'Unsupported screenshot mime type for path "{path}": {mime_type}')