_locator.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  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 json
  15. import pathlib
  16. import re
  17. from typing import (
  18. TYPE_CHECKING,
  19. Any,
  20. Awaitable,
  21. Callable,
  22. Dict,
  23. List,
  24. Literal,
  25. Optional,
  26. Pattern,
  27. Sequence,
  28. Tuple,
  29. TypeVar,
  30. Union,
  31. )
  32. from playwright._impl._api_structures import (
  33. AriaRole,
  34. FilePayload,
  35. FloatRect,
  36. FrameExpectOptions,
  37. FrameExpectResult,
  38. Position,
  39. )
  40. from playwright._impl._element_handle import ElementHandle
  41. from playwright._impl._helper import (
  42. Error,
  43. KeyboardModifier,
  44. MouseButton,
  45. locals_to_params,
  46. monotonic_time,
  47. to_impl,
  48. )
  49. from playwright._impl._js_handle import Serializable
  50. from playwright._impl._str_utils import (
  51. escape_for_attribute_selector,
  52. escape_for_text_selector,
  53. )
  54. if TYPE_CHECKING: # pragma: no cover
  55. from playwright._impl._frame import Frame
  56. from playwright._impl._js_handle import JSHandle
  57. from playwright._impl._page import Page
  58. T = TypeVar("T")
  59. class Locator:
  60. def __init__(
  61. self,
  62. frame: "Frame",
  63. selector: str,
  64. has_text: Union[str, Pattern[str]] = None,
  65. has_not_text: Union[str, Pattern[str]] = None,
  66. has: "Locator" = None,
  67. has_not: "Locator" = None,
  68. visible: bool = None,
  69. ) -> None:
  70. self._frame = frame
  71. self._selector = selector
  72. self._loop = frame._loop
  73. self._dispatcher_fiber = frame._connection._dispatcher_fiber
  74. if has_text:
  75. self._selector += f" >> internal:has-text={escape_for_text_selector(has_text, exact=False)}"
  76. if has:
  77. if has._frame != frame:
  78. raise Error('Inner "has" locator must belong to the same frame.')
  79. self._selector += " >> internal:has=" + json.dumps(
  80. has._selector, ensure_ascii=False
  81. )
  82. if has_not_text:
  83. self._selector += f" >> internal:has-not-text={escape_for_text_selector(has_not_text, exact=False)}"
  84. if has_not:
  85. locator = has_not
  86. if locator._frame != frame:
  87. raise Error('Inner "has_not" locator must belong to the same frame.')
  88. self._selector += " >> internal:has-not=" + json.dumps(locator._selector)
  89. if visible is not None:
  90. self._selector += f" >> visible={bool_to_js_bool(visible)}"
  91. def __repr__(self) -> str:
  92. return f"<Locator frame={self._frame!r} selector={self._selector!r}>"
  93. async def _with_element(
  94. self,
  95. task: Callable[[ElementHandle, float], Awaitable[T]],
  96. timeout: float = None,
  97. ) -> T:
  98. timeout = self._frame._timeout(timeout)
  99. deadline = (monotonic_time() + timeout) if timeout else 0
  100. handle = await self.element_handle(timeout=timeout)
  101. if not handle:
  102. raise Error(f"Could not resolve {self._selector} to DOM Element")
  103. try:
  104. return await task(
  105. handle,
  106. (deadline - monotonic_time()) if deadline else 0,
  107. )
  108. finally:
  109. await handle.dispose()
  110. def _equals(self, locator: "Locator") -> bool:
  111. return self._frame == locator._frame and self._selector == locator._selector
  112. @property
  113. def page(self) -> "Page":
  114. return self._frame.page
  115. async def bounding_box(self, timeout: float = None) -> Optional[FloatRect]:
  116. return await self._with_element(
  117. lambda h, _: h.bounding_box(),
  118. timeout,
  119. )
  120. async def check(
  121. self,
  122. position: Position = None,
  123. timeout: float = None,
  124. force: bool = None,
  125. noWaitAfter: bool = None,
  126. trial: bool = None,
  127. ) -> None:
  128. params = locals_to_params(locals())
  129. return await self._frame.check(self._selector, strict=True, **params)
  130. async def click(
  131. self,
  132. modifiers: Sequence[KeyboardModifier] = None,
  133. position: Position = None,
  134. delay: float = None,
  135. button: MouseButton = None,
  136. clickCount: int = None,
  137. timeout: float = None,
  138. force: bool = None,
  139. noWaitAfter: bool = None,
  140. trial: bool = None,
  141. steps: int = None,
  142. ) -> None:
  143. params = locals_to_params(locals())
  144. return await self._frame._click(self._selector, strict=True, **params)
  145. async def dblclick(
  146. self,
  147. modifiers: Sequence[KeyboardModifier] = None,
  148. position: Position = None,
  149. delay: float = None,
  150. button: MouseButton = None,
  151. timeout: float = None,
  152. force: bool = None,
  153. noWaitAfter: bool = None,
  154. trial: bool = None,
  155. steps: int = None,
  156. ) -> None:
  157. params = locals_to_params(locals())
  158. return await self._frame.dblclick(self._selector, strict=True, **params)
  159. async def dispatch_event(
  160. self,
  161. type: str,
  162. eventInit: Dict = None,
  163. timeout: float = None,
  164. ) -> None:
  165. params = locals_to_params(locals())
  166. return await self._frame.dispatch_event(self._selector, strict=True, **params)
  167. async def evaluate(
  168. self, expression: str, arg: Serializable = None, timeout: float = None
  169. ) -> Any:
  170. return await self._with_element(
  171. lambda h, _: h.evaluate(expression, arg),
  172. timeout,
  173. )
  174. async def evaluate_all(self, expression: str, arg: Serializable = None) -> Any:
  175. params = locals_to_params(locals())
  176. return await self._frame.eval_on_selector_all(self._selector, **params)
  177. async def evaluate_handle(
  178. self, expression: str, arg: Serializable = None, timeout: float = None
  179. ) -> "JSHandle":
  180. return await self._with_element(
  181. lambda h, _: h.evaluate_handle(expression, arg), timeout
  182. )
  183. async def fill(
  184. self,
  185. value: str,
  186. timeout: float = None,
  187. noWaitAfter: bool = None,
  188. force: bool = None,
  189. ) -> None:
  190. params = locals_to_params(locals())
  191. return await self._frame.fill(self._selector, strict=True, **params)
  192. async def clear(
  193. self,
  194. timeout: float = None,
  195. noWaitAfter: bool = None,
  196. force: bool = None,
  197. ) -> None:
  198. params = locals_to_params(locals())
  199. await self._frame._fill(self._selector, value="", title="Clear", **params)
  200. def locator(
  201. self,
  202. selectorOrLocator: Union[str, "Locator"],
  203. hasText: Union[str, Pattern[str]] = None,
  204. hasNotText: Union[str, Pattern[str]] = None,
  205. has: "Locator" = None,
  206. hasNot: "Locator" = None,
  207. ) -> "Locator":
  208. if isinstance(selectorOrLocator, str):
  209. return Locator(
  210. self._frame,
  211. f"{self._selector} >> {selectorOrLocator}",
  212. has_text=hasText,
  213. has_not_text=hasNotText,
  214. has_not=hasNot,
  215. has=has,
  216. )
  217. selectorOrLocator = to_impl(selectorOrLocator)
  218. if selectorOrLocator._frame != self._frame:
  219. raise Error("Locators must belong to the same frame.")
  220. return Locator(
  221. self._frame,
  222. f"{self._selector} >> internal:chain={json.dumps(selectorOrLocator._selector)}",
  223. has_text=hasText,
  224. has_not_text=hasNotText,
  225. has_not=hasNot,
  226. has=has,
  227. )
  228. def get_by_alt_text(
  229. self, text: Union[str, Pattern[str]], exact: bool = None
  230. ) -> "Locator":
  231. return self.locator(get_by_alt_text_selector(text, exact=exact))
  232. def get_by_label(
  233. self, text: Union[str, Pattern[str]], exact: bool = None
  234. ) -> "Locator":
  235. return self.locator(get_by_label_selector(text, exact=exact))
  236. def get_by_placeholder(
  237. self, text: Union[str, Pattern[str]], exact: bool = None
  238. ) -> "Locator":
  239. return self.locator(get_by_placeholder_selector(text, exact=exact))
  240. def get_by_role(
  241. self,
  242. role: AriaRole,
  243. checked: bool = None,
  244. disabled: bool = None,
  245. expanded: bool = None,
  246. includeHidden: bool = None,
  247. level: int = None,
  248. name: Union[str, Pattern[str]] = None,
  249. pressed: bool = None,
  250. selected: bool = None,
  251. exact: bool = None,
  252. ) -> "Locator":
  253. return self.locator(
  254. get_by_role_selector(
  255. role,
  256. checked=checked,
  257. disabled=disabled,
  258. expanded=expanded,
  259. includeHidden=includeHidden,
  260. level=level,
  261. name=name,
  262. pressed=pressed,
  263. selected=selected,
  264. exact=exact,
  265. )
  266. )
  267. def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
  268. return self.locator(get_by_test_id_selector(test_id_attribute_name(), testId))
  269. def get_by_text(
  270. self, text: Union[str, Pattern[str]], exact: bool = None
  271. ) -> "Locator":
  272. return self.locator(get_by_text_selector(text, exact=exact))
  273. def get_by_title(
  274. self, text: Union[str, Pattern[str]], exact: bool = None
  275. ) -> "Locator":
  276. return self.locator(get_by_title_selector(text, exact=exact))
  277. def frame_locator(self, selector: str) -> "FrameLocator":
  278. return FrameLocator(self._frame, self._selector + " >> " + selector)
  279. async def element_handle(
  280. self,
  281. timeout: float = None,
  282. ) -> ElementHandle:
  283. params = locals_to_params(locals())
  284. handle = await self._frame.wait_for_selector(
  285. self._selector, strict=True, state="attached", **params
  286. )
  287. assert handle
  288. return handle
  289. async def element_handles(self) -> List[ElementHandle]:
  290. return await self._frame.query_selector_all(self._selector)
  291. @property
  292. def first(self) -> "Locator":
  293. return Locator(self._frame, f"{self._selector} >> nth=0")
  294. @property
  295. def last(self) -> "Locator":
  296. return Locator(self._frame, f"{self._selector} >> nth=-1")
  297. def nth(self, index: int) -> "Locator":
  298. return Locator(self._frame, f"{self._selector} >> nth={index}")
  299. @property
  300. def content_frame(self) -> "FrameLocator":
  301. return FrameLocator(self._frame, self._selector)
  302. def describe(self, description: str) -> "Locator":
  303. return Locator(
  304. self._frame,
  305. f"{self._selector} >> internal:describe={json.dumps(description)}",
  306. )
  307. @property
  308. def description(self) -> Optional[str]:
  309. try:
  310. match = re.search(
  311. r' >> internal:describe=("(?:[^"\\]|\\.)*")$', self._selector
  312. )
  313. if match:
  314. description = json.loads(match.group(1))
  315. if isinstance(description, str):
  316. return description
  317. except (json.JSONDecodeError, ValueError):
  318. pass
  319. return None
  320. def filter(
  321. self,
  322. hasText: Union[str, Pattern[str]] = None,
  323. hasNotText: Union[str, Pattern[str]] = None,
  324. has: "Locator" = None,
  325. hasNot: "Locator" = None,
  326. visible: bool = None,
  327. ) -> "Locator":
  328. return Locator(
  329. self._frame,
  330. self._selector,
  331. has_text=hasText,
  332. has_not_text=hasNotText,
  333. has=has,
  334. has_not=hasNot,
  335. visible=visible,
  336. )
  337. def or_(self, locator: "Locator") -> "Locator":
  338. if locator._frame != self._frame:
  339. raise Error("Locators must belong to the same frame.")
  340. return Locator(
  341. self._frame,
  342. self._selector + " >> internal:or=" + json.dumps(locator._selector),
  343. )
  344. def and_(self, locator: "Locator") -> "Locator":
  345. if locator._frame != self._frame:
  346. raise Error("Locators must belong to the same frame.")
  347. return Locator(
  348. self._frame,
  349. self._selector + " >> internal:and=" + json.dumps(locator._selector),
  350. )
  351. async def focus(self, timeout: float = None) -> None:
  352. params = locals_to_params(locals())
  353. return await self._frame.focus(self._selector, strict=True, **params)
  354. async def blur(self, timeout: float = None) -> None:
  355. await self._frame._channel.send(
  356. "blur",
  357. self._frame._timeout,
  358. {
  359. "selector": self._selector,
  360. "strict": True,
  361. **locals_to_params(locals()),
  362. },
  363. )
  364. async def all(
  365. self,
  366. ) -> List["Locator"]:
  367. result = []
  368. for index in range(await self.count()):
  369. result.append(self.nth(index))
  370. return result
  371. async def count(
  372. self,
  373. ) -> int:
  374. return await self._frame._query_count(self._selector)
  375. async def drag_to(
  376. self,
  377. target: "Locator",
  378. force: bool = None,
  379. noWaitAfter: bool = None,
  380. timeout: float = None,
  381. trial: bool = None,
  382. sourcePosition: Position = None,
  383. targetPosition: Position = None,
  384. steps: int = None,
  385. ) -> None:
  386. params = locals_to_params(locals())
  387. del params["target"]
  388. return await self._frame.drag_and_drop(
  389. self._selector, target._selector, strict=True, **params
  390. )
  391. async def get_attribute(self, name: str, timeout: float = None) -> Optional[str]:
  392. params = locals_to_params(locals())
  393. return await self._frame.get_attribute(
  394. self._selector,
  395. strict=True,
  396. **params,
  397. )
  398. async def hover(
  399. self,
  400. modifiers: Sequence[KeyboardModifier] = None,
  401. position: Position = None,
  402. timeout: float = None,
  403. noWaitAfter: bool = None,
  404. force: bool = None,
  405. trial: bool = None,
  406. ) -> None:
  407. params = locals_to_params(locals())
  408. return await self._frame.hover(
  409. self._selector,
  410. strict=True,
  411. **params,
  412. )
  413. async def inner_html(self, timeout: float = None) -> str:
  414. params = locals_to_params(locals())
  415. return await self._frame.inner_html(
  416. self._selector,
  417. strict=True,
  418. **params,
  419. )
  420. async def inner_text(self, timeout: float = None) -> str:
  421. params = locals_to_params(locals())
  422. return await self._frame.inner_text(
  423. self._selector,
  424. strict=True,
  425. **params,
  426. )
  427. async def input_value(self, timeout: float = None) -> str:
  428. params = locals_to_params(locals())
  429. return await self._frame.input_value(
  430. self._selector,
  431. strict=True,
  432. **params,
  433. )
  434. async def is_checked(self, timeout: float = None) -> bool:
  435. params = locals_to_params(locals())
  436. return await self._frame.is_checked(
  437. self._selector,
  438. strict=True,
  439. **params,
  440. )
  441. async def is_disabled(self, timeout: float = None) -> bool:
  442. params = locals_to_params(locals())
  443. return await self._frame.is_disabled(
  444. self._selector,
  445. strict=True,
  446. **params,
  447. )
  448. async def is_editable(self, timeout: float = None) -> bool:
  449. params = locals_to_params(locals())
  450. return await self._frame.is_editable(
  451. self._selector,
  452. strict=True,
  453. **params,
  454. )
  455. async def is_enabled(self, timeout: float = None) -> bool:
  456. params = locals_to_params(locals())
  457. return await self._frame.is_enabled(
  458. self._selector,
  459. strict=True,
  460. **params,
  461. )
  462. async def is_hidden(self, timeout: float = None) -> bool:
  463. # timeout is deprecated and does nothing
  464. return await self._frame.is_hidden(
  465. self._selector,
  466. strict=True,
  467. )
  468. async def is_visible(self, timeout: float = None) -> bool:
  469. # timeout is deprecated and does nothing
  470. return await self._frame.is_visible(
  471. self._selector,
  472. strict=True,
  473. )
  474. async def press(
  475. self,
  476. key: str,
  477. delay: float = None,
  478. timeout: float = None,
  479. noWaitAfter: bool = None,
  480. ) -> None:
  481. params = locals_to_params(locals())
  482. return await self._frame.press(self._selector, strict=True, **params)
  483. async def screenshot(
  484. self,
  485. timeout: float = None,
  486. type: Literal["jpeg", "png"] = None,
  487. path: Union[str, pathlib.Path] = None,
  488. quality: int = None,
  489. omitBackground: bool = None,
  490. animations: Literal["allow", "disabled"] = None,
  491. caret: Literal["hide", "initial"] = None,
  492. scale: Literal["css", "device"] = None,
  493. mask: Sequence["Locator"] = None,
  494. maskColor: str = None,
  495. style: str = None,
  496. ) -> bytes:
  497. params = locals_to_params(locals())
  498. return await self._with_element(
  499. lambda h, timeout: h.screenshot(
  500. **{**params, "timeout": timeout},
  501. ),
  502. )
  503. async def aria_snapshot(self, timeout: float = None) -> str:
  504. return await self._frame._channel.send(
  505. "ariaSnapshot",
  506. self._frame._timeout,
  507. {
  508. "selector": self._selector,
  509. **locals_to_params(locals()),
  510. },
  511. )
  512. async def scroll_into_view_if_needed(
  513. self,
  514. timeout: float = None,
  515. ) -> None:
  516. return await self._with_element(
  517. lambda h, timeout: h.scroll_into_view_if_needed(timeout=timeout),
  518. timeout,
  519. )
  520. async def select_option(
  521. self,
  522. value: Union[str, Sequence[str]] = None,
  523. index: Union[int, Sequence[int]] = None,
  524. label: Union[str, Sequence[str]] = None,
  525. element: Union["ElementHandle", Sequence["ElementHandle"]] = None,
  526. timeout: float = None,
  527. noWaitAfter: bool = None,
  528. force: bool = None,
  529. ) -> List[str]:
  530. params = locals_to_params(locals())
  531. return await self._frame.select_option(
  532. self._selector,
  533. strict=True,
  534. **params,
  535. )
  536. async def select_text(self, force: bool = None, timeout: float = None) -> None:
  537. params = locals_to_params(locals())
  538. return await self._with_element(
  539. lambda h, timeout: h.select_text(**{**params, "timeout": timeout}),
  540. timeout,
  541. )
  542. async def set_input_files(
  543. self,
  544. files: Union[
  545. str,
  546. pathlib.Path,
  547. FilePayload,
  548. Sequence[Union[str, pathlib.Path]],
  549. Sequence[FilePayload],
  550. ],
  551. timeout: float = None,
  552. noWaitAfter: bool = None,
  553. ) -> None:
  554. params = locals_to_params(locals())
  555. return await self._frame.set_input_files(
  556. self._selector,
  557. strict=True,
  558. **params,
  559. )
  560. async def tap(
  561. self,
  562. modifiers: Sequence[KeyboardModifier] = None,
  563. position: Position = None,
  564. timeout: float = None,
  565. force: bool = None,
  566. noWaitAfter: bool = None,
  567. trial: bool = None,
  568. ) -> None:
  569. params = locals_to_params(locals())
  570. return await self._frame.tap(
  571. self._selector,
  572. strict=True,
  573. **params,
  574. )
  575. async def text_content(self, timeout: float = None) -> Optional[str]:
  576. params = locals_to_params(locals())
  577. return await self._frame.text_content(
  578. self._selector,
  579. strict=True,
  580. **params,
  581. )
  582. async def type(
  583. self,
  584. text: str,
  585. delay: float = None,
  586. timeout: float = None,
  587. noWaitAfter: bool = None,
  588. ) -> None:
  589. params = locals_to_params(locals())
  590. return await self._frame.type(
  591. self._selector,
  592. strict=True,
  593. **params,
  594. )
  595. async def press_sequentially(
  596. self,
  597. text: str,
  598. delay: float = None,
  599. timeout: float = None,
  600. noWaitAfter: bool = None,
  601. ) -> None:
  602. await self.type(text, delay=delay, timeout=timeout)
  603. async def uncheck(
  604. self,
  605. position: Position = None,
  606. timeout: float = None,
  607. force: bool = None,
  608. noWaitAfter: bool = None,
  609. trial: bool = None,
  610. ) -> None:
  611. params = locals_to_params(locals())
  612. return await self._frame.uncheck(
  613. self._selector,
  614. strict=True,
  615. **params,
  616. )
  617. async def all_inner_texts(
  618. self,
  619. ) -> List[str]:
  620. return await self._frame.eval_on_selector_all(
  621. self._selector, "ee => ee.map(e => e.innerText)"
  622. )
  623. async def all_text_contents(
  624. self,
  625. ) -> List[str]:
  626. return await self._frame.eval_on_selector_all(
  627. self._selector, "ee => ee.map(e => e.textContent || '')"
  628. )
  629. async def wait_for(
  630. self,
  631. timeout: float = None,
  632. state: Literal["attached", "detached", "hidden", "visible"] = None,
  633. ) -> None:
  634. await self._frame.wait_for_selector(
  635. self._selector, strict=True, timeout=timeout, state=state
  636. )
  637. async def set_checked(
  638. self,
  639. checked: bool,
  640. position: Position = None,
  641. timeout: float = None,
  642. force: bool = None,
  643. noWaitAfter: bool = None,
  644. trial: bool = None,
  645. ) -> None:
  646. if checked:
  647. await self.check(
  648. position=position,
  649. timeout=timeout,
  650. force=force,
  651. trial=trial,
  652. )
  653. else:
  654. await self.uncheck(
  655. position=position,
  656. timeout=timeout,
  657. force=force,
  658. trial=trial,
  659. )
  660. async def _expect(
  661. self,
  662. expression: str,
  663. options: FrameExpectOptions,
  664. title: str = None,
  665. ) -> FrameExpectResult:
  666. return await self._frame._expect(self._selector, expression, options, title)
  667. async def highlight(self) -> None:
  668. await self._frame._highlight(self._selector)
  669. class FrameLocator:
  670. def __init__(self, frame: "Frame", frame_selector: str) -> None:
  671. self._frame = frame
  672. self._loop = frame._loop
  673. self._dispatcher_fiber = frame._connection._dispatcher_fiber
  674. self._frame_selector = frame_selector
  675. def locator(
  676. self,
  677. selectorOrLocator: Union["Locator", str],
  678. hasText: Union[str, Pattern[str]] = None,
  679. hasNotText: Union[str, Pattern[str]] = None,
  680. has: Locator = None,
  681. hasNot: Locator = None,
  682. ) -> Locator:
  683. if isinstance(selectorOrLocator, str):
  684. return Locator(
  685. self._frame,
  686. f"{self._frame_selector} >> internal:control=enter-frame >> {selectorOrLocator}",
  687. has_text=hasText,
  688. has_not_text=hasNotText,
  689. has=has,
  690. has_not=hasNot,
  691. )
  692. selectorOrLocator = to_impl(selectorOrLocator)
  693. if selectorOrLocator._frame != self._frame:
  694. raise ValueError("Locators must belong to the same frame.")
  695. return Locator(
  696. self._frame,
  697. f"{self._frame_selector} >> internal:control=enter-frame >> {selectorOrLocator._selector}",
  698. has_text=hasText,
  699. has_not_text=hasNotText,
  700. has=has,
  701. has_not=hasNot,
  702. )
  703. def get_by_alt_text(
  704. self, text: Union[str, Pattern[str]], exact: bool = None
  705. ) -> "Locator":
  706. return self.locator(get_by_alt_text_selector(text, exact=exact))
  707. def get_by_label(
  708. self, text: Union[str, Pattern[str]], exact: bool = None
  709. ) -> "Locator":
  710. return self.locator(get_by_label_selector(text, exact=exact))
  711. def get_by_placeholder(
  712. self, text: Union[str, Pattern[str]], exact: bool = None
  713. ) -> "Locator":
  714. return self.locator(get_by_placeholder_selector(text, exact=exact))
  715. def get_by_role(
  716. self,
  717. role: AriaRole,
  718. checked: bool = None,
  719. disabled: bool = None,
  720. expanded: bool = None,
  721. includeHidden: bool = None,
  722. level: int = None,
  723. name: Union[str, Pattern[str]] = None,
  724. pressed: bool = None,
  725. selected: bool = None,
  726. exact: bool = None,
  727. ) -> "Locator":
  728. return self.locator(
  729. get_by_role_selector(
  730. role,
  731. checked=checked,
  732. disabled=disabled,
  733. expanded=expanded,
  734. includeHidden=includeHidden,
  735. level=level,
  736. name=name,
  737. pressed=pressed,
  738. selected=selected,
  739. exact=exact,
  740. )
  741. )
  742. def get_by_test_id(self, testId: Union[str, Pattern[str]]) -> "Locator":
  743. return self.locator(get_by_test_id_selector(test_id_attribute_name(), testId))
  744. def get_by_text(
  745. self, text: Union[str, Pattern[str]], exact: bool = None
  746. ) -> "Locator":
  747. return self.locator(get_by_text_selector(text, exact=exact))
  748. def get_by_title(
  749. self, text: Union[str, Pattern[str]], exact: bool = None
  750. ) -> "Locator":
  751. return self.locator(get_by_title_selector(text, exact=exact))
  752. def frame_locator(self, selector: str) -> "FrameLocator":
  753. return FrameLocator(
  754. self._frame,
  755. f"{self._frame_selector} >> internal:control=enter-frame >> {selector}",
  756. )
  757. @property
  758. def first(self) -> "FrameLocator":
  759. return FrameLocator(self._frame, f"{self._frame_selector} >> nth=0")
  760. @property
  761. def last(self) -> "FrameLocator":
  762. return FrameLocator(self._frame, f"{self._frame_selector} >> nth=-1")
  763. @property
  764. def owner(self) -> "Locator":
  765. return Locator(self._frame, self._frame_selector)
  766. def nth(self, index: int) -> "FrameLocator":
  767. return FrameLocator(self._frame, f"{self._frame_selector} >> nth={index}")
  768. def __repr__(self) -> str:
  769. return f"<FrameLocator frame={self._frame!r} selector={self._frame_selector!r}>"
  770. _test_id_attribute_name: str = "data-testid"
  771. def test_id_attribute_name() -> str:
  772. return _test_id_attribute_name
  773. def set_test_id_attribute_name(attribute_name: str) -> None:
  774. global _test_id_attribute_name
  775. _test_id_attribute_name = attribute_name
  776. def get_by_test_id_selector(
  777. test_id_attribute_name: str, test_id: Union[str, Pattern[str]]
  778. ) -> str:
  779. return f"internal:testid=[{test_id_attribute_name}={escape_for_attribute_selector(test_id, True)}]"
  780. def get_by_attribute_text_selector(
  781. attr_name: str, text: Union[str, Pattern[str]], exact: bool = None
  782. ) -> str:
  783. return f"internal:attr=[{attr_name}={escape_for_attribute_selector(text, exact=exact)}]"
  784. def get_by_label_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str:
  785. return "internal:label=" + escape_for_text_selector(text, exact=exact)
  786. def get_by_alt_text_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str:
  787. return get_by_attribute_text_selector("alt", text, exact=exact)
  788. def get_by_title_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str:
  789. return get_by_attribute_text_selector("title", text, exact=exact)
  790. def get_by_placeholder_selector(
  791. text: Union[str, Pattern[str]], exact: bool = None
  792. ) -> str:
  793. return get_by_attribute_text_selector("placeholder", text, exact=exact)
  794. def get_by_text_selector(text: Union[str, Pattern[str]], exact: bool = None) -> str:
  795. return "internal:text=" + escape_for_text_selector(text, exact=exact)
  796. def bool_to_js_bool(value: bool) -> str:
  797. return "true" if value else "false"
  798. def get_by_role_selector(
  799. role: AriaRole,
  800. checked: bool = None,
  801. disabled: bool = None,
  802. expanded: bool = None,
  803. includeHidden: bool = None,
  804. level: int = None,
  805. name: Union[str, Pattern[str]] = None,
  806. pressed: bool = None,
  807. selected: bool = None,
  808. exact: bool = None,
  809. ) -> str:
  810. props: List[Tuple[str, str]] = []
  811. if checked is not None:
  812. props.append(("checked", bool_to_js_bool(checked)))
  813. if disabled is not None:
  814. props.append(("disabled", bool_to_js_bool(disabled)))
  815. if selected is not None:
  816. props.append(("selected", bool_to_js_bool(selected)))
  817. if expanded is not None:
  818. props.append(("expanded", bool_to_js_bool(expanded)))
  819. if includeHidden is not None:
  820. props.append(("include-hidden", bool_to_js_bool(includeHidden)))
  821. if level is not None:
  822. props.append(("level", str(level)))
  823. if name is not None:
  824. props.append(
  825. (
  826. "name",
  827. escape_for_attribute_selector(name, exact=exact),
  828. )
  829. )
  830. if pressed is not None:
  831. props.append(("pressed", bool_to_js_bool(pressed)))
  832. props_str = "".join([f"[{t[0]}={t[1]}]" for t in props])
  833. return f"internal:role={role}{props_str}"