_assertions.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  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 collections.abc
  15. from contextlib import contextmanager
  16. from typing import Any, Iterator, List, Literal, Optional, Pattern, Sequence, Union
  17. from urllib.parse import urljoin
  18. from playwright._impl._api_structures import (
  19. AriaRole,
  20. ExpectedTextValue,
  21. FrameExpectOptions,
  22. FrameExpectResult,
  23. )
  24. from playwright._impl._connection import format_call_log
  25. from playwright._impl._errors import Error
  26. from playwright._impl._fetch import APIResponse
  27. from playwright._impl._helper import is_textual_mime_type
  28. from playwright._impl._js_handle import parse_value
  29. from playwright._impl._locator import Locator
  30. from playwright._impl._page import Page
  31. from playwright._impl._str_utils import escape_regex_flags
  32. _soft_errors: Optional[List[AssertionError]] = None
  33. @contextmanager
  34. def _soft_scope() -> Iterator[List[AssertionError]]:
  35. global _soft_errors
  36. assert _soft_errors is None, "nested soft assertion scopes are not supported"
  37. _soft_errors = []
  38. try:
  39. yield _soft_errors
  40. finally:
  41. _soft_errors = None
  42. def _record_soft_or_raise(error: AssertionError, is_soft: bool) -> None:
  43. __tracebackhide__ = True
  44. if is_soft:
  45. if _soft_errors is None:
  46. raise RuntimeError(
  47. "expect.soft(...) requires pytest-playwright>=0.7.3 "
  48. "(or pytest-playwright-asyncio>=0.7.3). Upgrade the plugin, "
  49. "or use a regular expect(...) assertion."
  50. )
  51. _soft_errors.append(error)
  52. return
  53. raise error
  54. class AssertionsBase:
  55. def __init__(
  56. self,
  57. locator: Locator,
  58. timeout: float = None,
  59. is_not: bool = False,
  60. message: Optional[str] = None,
  61. is_soft: bool = False,
  62. ) -> None:
  63. self._actual_locator = locator
  64. self._loop = locator._loop
  65. self._dispatcher_fiber = locator._dispatcher_fiber
  66. self._timeout = timeout
  67. self._is_not = is_not
  68. self._custom_message = message
  69. self._is_soft = is_soft
  70. async def _call_expect(
  71. self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
  72. ) -> FrameExpectResult:
  73. raise NotImplementedError(
  74. "_call_expect must be implemented in a derived class."
  75. )
  76. async def _expect_impl(
  77. self,
  78. expression: str,
  79. expect_options: FrameExpectOptions,
  80. expected: Any,
  81. message: str,
  82. title: str = None,
  83. ) -> None:
  84. __tracebackhide__ = True
  85. expect_options["isNot"] = self._is_not
  86. if expect_options.get("timeout") is None:
  87. expect_options["timeout"] = self._timeout or 5_000
  88. if expect_options["isNot"]:
  89. message = message.replace("expected to", "expected not to")
  90. if "useInnerText" in expect_options and expect_options["useInnerText"] is None:
  91. del expect_options["useInnerText"]
  92. result = await self._call_expect(expression, expect_options, title)
  93. if result["matches"] == self._is_not:
  94. received = result.get("received") or {}
  95. if isinstance(received, dict):
  96. if "value" in received and received["value"] is not None:
  97. actual = parse_value(received["value"])
  98. else:
  99. actual = received.get("ariaSnapshot")
  100. else:
  101. actual = received
  102. if self._custom_message:
  103. out_message = self._custom_message
  104. if expected is not None:
  105. out_message += f"\nExpected value: '{expected or '<None>'}'"
  106. else:
  107. out_message = (
  108. f"{message} '{expected}'" if expected is not None else f"{message}"
  109. )
  110. error_message = result.get("errorMessage")
  111. error_message = f"\n{error_message}" if error_message else ""
  112. _record_soft_or_raise(
  113. AssertionError(
  114. f"{out_message}\nActual value: {actual}{error_message} {format_call_log(result.get('log'))}"
  115. ),
  116. self._is_soft,
  117. )
  118. class PageAssertions(AssertionsBase):
  119. def __init__(
  120. self,
  121. page: Page,
  122. timeout: float = None,
  123. is_not: bool = False,
  124. message: Optional[str] = None,
  125. is_soft: bool = False,
  126. ) -> None:
  127. super().__init__(page.locator(":root"), timeout, is_not, message, is_soft)
  128. self._actual_page = page
  129. async def _call_expect(
  130. self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
  131. ) -> FrameExpectResult:
  132. __tracebackhide__ = True
  133. return await self._actual_page.main_frame._expect(
  134. None, expression, expect_options, title
  135. )
  136. @property
  137. def _not(self) -> "PageAssertions":
  138. return PageAssertions(
  139. self._actual_page,
  140. self._timeout,
  141. not self._is_not,
  142. self._custom_message,
  143. self._is_soft,
  144. )
  145. async def to_have_title(
  146. self, titleOrRegExp: Union[Pattern[str], str], timeout: float = None
  147. ) -> None:
  148. __tracebackhide__ = True
  149. expected_values = to_expected_text_values(
  150. [titleOrRegExp], normalize_white_space=True
  151. )
  152. await self._expect_impl(
  153. "to.have.title",
  154. FrameExpectOptions(expectedText=expected_values, timeout=timeout),
  155. titleOrRegExp,
  156. "Page title expected to be",
  157. 'Expect "to_have_title"',
  158. )
  159. async def not_to_have_title(
  160. self, titleOrRegExp: Union[Pattern[str], str], timeout: float = None
  161. ) -> None:
  162. __tracebackhide__ = True
  163. await self._not.to_have_title(titleOrRegExp, timeout)
  164. async def to_have_url(
  165. self,
  166. urlOrRegExp: Union[str, Pattern[str]],
  167. timeout: float = None,
  168. ignoreCase: bool = None,
  169. ) -> None:
  170. __tracebackhide__ = True
  171. base_url = self._actual_page.context._base_url
  172. if isinstance(urlOrRegExp, str) and base_url:
  173. urlOrRegExp = urljoin(base_url, urlOrRegExp)
  174. expected_text = to_expected_text_values([urlOrRegExp], ignoreCase=ignoreCase)
  175. await self._expect_impl(
  176. "to.have.url",
  177. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  178. urlOrRegExp,
  179. "Page URL expected to be",
  180. 'Expect "to_have_url"',
  181. )
  182. async def not_to_have_url(
  183. self,
  184. urlOrRegExp: Union[Pattern[str], str],
  185. timeout: float = None,
  186. ignoreCase: bool = None,
  187. ) -> None:
  188. __tracebackhide__ = True
  189. await self._not.to_have_url(urlOrRegExp, timeout, ignoreCase)
  190. async def to_match_aria_snapshot(
  191. self, expected: str, timeout: float = None
  192. ) -> None:
  193. __tracebackhide__ = True
  194. await self._expect_impl(
  195. "to.match.aria",
  196. FrameExpectOptions(expectedValue=expected, timeout=timeout),
  197. expected,
  198. "Page expected to match Aria snapshot",
  199. 'Expect "to_match_aria_snapshot"',
  200. )
  201. async def not_to_match_aria_snapshot(
  202. self, expected: str, timeout: float = None
  203. ) -> None:
  204. __tracebackhide__ = True
  205. await self._not.to_match_aria_snapshot(expected, timeout)
  206. class LocatorAssertions(AssertionsBase):
  207. def __init__(
  208. self,
  209. locator: Locator,
  210. timeout: float = None,
  211. is_not: bool = False,
  212. message: Optional[str] = None,
  213. is_soft: bool = False,
  214. ) -> None:
  215. super().__init__(locator, timeout, is_not, message, is_soft)
  216. self._actual_locator = locator
  217. async def _call_expect(
  218. self, expression: str, expect_options: FrameExpectOptions, title: Optional[str]
  219. ) -> FrameExpectResult:
  220. __tracebackhide__ = True
  221. return await self._actual_locator._expect(expression, expect_options, title)
  222. @property
  223. def _not(self) -> "LocatorAssertions":
  224. return LocatorAssertions(
  225. self._actual_locator,
  226. self._timeout,
  227. not self._is_not,
  228. self._custom_message,
  229. self._is_soft,
  230. )
  231. async def to_contain_text(
  232. self,
  233. expected: Union[
  234. Sequence[str],
  235. Sequence[Pattern[str]],
  236. Sequence[Union[Pattern[str], str]],
  237. Pattern[str],
  238. str,
  239. ],
  240. useInnerText: bool = None,
  241. timeout: float = None,
  242. ignoreCase: bool = None,
  243. ) -> None:
  244. __tracebackhide__ = True
  245. if isinstance(expected, collections.abc.Sequence) and not isinstance(
  246. expected, str
  247. ):
  248. expected_text = to_expected_text_values(
  249. expected,
  250. match_substring=True,
  251. normalize_white_space=True,
  252. ignoreCase=ignoreCase,
  253. )
  254. await self._expect_impl(
  255. "to.contain.text.array",
  256. FrameExpectOptions(
  257. expectedText=expected_text,
  258. useInnerText=useInnerText,
  259. timeout=timeout,
  260. ),
  261. expected,
  262. "Locator expected to contain text",
  263. 'Expect "to_contain_text"',
  264. )
  265. else:
  266. expected_text = to_expected_text_values(
  267. [expected],
  268. match_substring=True,
  269. normalize_white_space=True,
  270. ignoreCase=ignoreCase,
  271. )
  272. await self._expect_impl(
  273. "to.have.text",
  274. FrameExpectOptions(
  275. expectedText=expected_text,
  276. useInnerText=useInnerText,
  277. timeout=timeout,
  278. ),
  279. expected,
  280. "Locator expected to contain text",
  281. 'Expect "to_contain_text"',
  282. )
  283. async def not_to_contain_text(
  284. self,
  285. expected: Union[
  286. Sequence[str],
  287. Sequence[Pattern[str]],
  288. Sequence[Union[Pattern[str], str]],
  289. Pattern[str],
  290. str,
  291. ],
  292. useInnerText: bool = None,
  293. timeout: float = None,
  294. ignoreCase: bool = None,
  295. ) -> None:
  296. __tracebackhide__ = True
  297. await self._not.to_contain_text(expected, useInnerText, timeout, ignoreCase)
  298. async def to_have_attribute(
  299. self,
  300. name: str,
  301. value: Union[str, Pattern[str]],
  302. ignoreCase: bool = None,
  303. timeout: float = None,
  304. ) -> None:
  305. __tracebackhide__ = True
  306. expected_text = to_expected_text_values([value], ignoreCase=ignoreCase)
  307. await self._expect_impl(
  308. "to.have.attribute.value",
  309. FrameExpectOptions(
  310. expressionArg=name, expectedText=expected_text, timeout=timeout
  311. ),
  312. value,
  313. "Locator expected to have attribute",
  314. 'Expect "to_have_attribute"',
  315. )
  316. async def not_to_have_attribute(
  317. self,
  318. name: str,
  319. value: Union[str, Pattern[str]],
  320. ignoreCase: bool = None,
  321. timeout: float = None,
  322. ) -> None:
  323. __tracebackhide__ = True
  324. await self._not.to_have_attribute(
  325. name, value, ignoreCase=ignoreCase, timeout=timeout
  326. )
  327. async def to_have_class(
  328. self,
  329. expected: Union[
  330. Sequence[str],
  331. Sequence[Pattern[str]],
  332. Sequence[Union[Pattern[str], str]],
  333. Pattern[str],
  334. str,
  335. ],
  336. timeout: float = None,
  337. ) -> None:
  338. __tracebackhide__ = True
  339. if isinstance(expected, collections.abc.Sequence) and not isinstance(
  340. expected, str
  341. ):
  342. expected_text = to_expected_text_values(expected)
  343. await self._expect_impl(
  344. "to.have.class.array",
  345. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  346. expected,
  347. "Locator expected to have class",
  348. 'Expect "to_have_class"',
  349. )
  350. else:
  351. expected_text = to_expected_text_values([expected])
  352. await self._expect_impl(
  353. "to.have.class",
  354. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  355. expected,
  356. "Locator expected to have class",
  357. 'Expect "to_have_class"',
  358. )
  359. async def not_to_have_class(
  360. self,
  361. expected: Union[
  362. Sequence[str],
  363. Sequence[Pattern[str]],
  364. Sequence[Union[Pattern[str], str]],
  365. Pattern[str],
  366. str,
  367. ],
  368. timeout: float = None,
  369. ) -> None:
  370. __tracebackhide__ = True
  371. await self._not.to_have_class(expected, timeout)
  372. async def to_contain_class(
  373. self,
  374. expected: Union[
  375. Sequence[str],
  376. str,
  377. ],
  378. timeout: float = None,
  379. ) -> None:
  380. __tracebackhide__ = True
  381. if isinstance(expected, collections.abc.Sequence) and not isinstance(
  382. expected, str
  383. ):
  384. expected_text = to_expected_text_values(expected)
  385. await self._expect_impl(
  386. "to.contain.class.array",
  387. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  388. expected,
  389. "Locator expected to contain class names",
  390. 'Expect "to_contain_class"',
  391. )
  392. else:
  393. expected_text = to_expected_text_values([expected])
  394. await self._expect_impl(
  395. "to.contain.class",
  396. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  397. expected,
  398. "Locator expected to contain class",
  399. 'Expect "to_contain_class"',
  400. )
  401. async def not_to_contain_class(
  402. self,
  403. expected: Union[
  404. Sequence[str],
  405. str,
  406. ],
  407. timeout: float = None,
  408. ) -> None:
  409. __tracebackhide__ = True
  410. await self._not.to_contain_class(expected, timeout)
  411. async def to_have_count(
  412. self,
  413. count: int,
  414. timeout: float = None,
  415. ) -> None:
  416. __tracebackhide__ = True
  417. await self._expect_impl(
  418. "to.have.count",
  419. FrameExpectOptions(expectedNumber=count, timeout=timeout),
  420. count,
  421. "Locator expected to have count",
  422. 'Expect "to_have_count"',
  423. )
  424. async def not_to_have_count(
  425. self,
  426. count: int,
  427. timeout: float = None,
  428. ) -> None:
  429. __tracebackhide__ = True
  430. await self._not.to_have_count(count, timeout)
  431. async def to_have_css(
  432. self,
  433. name: str,
  434. value: Union[str, Pattern[str]],
  435. timeout: float = None,
  436. pseudo: Literal["after", "before"] = None,
  437. ) -> None:
  438. __tracebackhide__ = True
  439. expected_text = to_expected_text_values([value])
  440. await self._expect_impl(
  441. "to.have.css",
  442. FrameExpectOptions(
  443. expressionArg=name,
  444. expectedText=expected_text,
  445. timeout=timeout,
  446. pseudo=pseudo,
  447. ),
  448. value,
  449. "Locator expected to have CSS",
  450. 'Expect "to_have_css"',
  451. )
  452. async def not_to_have_css(
  453. self,
  454. name: str,
  455. value: Union[str, Pattern[str]],
  456. timeout: float = None,
  457. ) -> None:
  458. __tracebackhide__ = True
  459. await self._not.to_have_css(name, value, timeout)
  460. async def to_have_id(
  461. self,
  462. id: Union[str, Pattern[str]],
  463. timeout: float = None,
  464. ) -> None:
  465. __tracebackhide__ = True
  466. expected_text = to_expected_text_values([id])
  467. await self._expect_impl(
  468. "to.have.id",
  469. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  470. id,
  471. "Locator expected to have ID",
  472. 'Expect "to_have_id"',
  473. )
  474. async def not_to_have_id(
  475. self,
  476. id: Union[str, Pattern[str]],
  477. timeout: float = None,
  478. ) -> None:
  479. __tracebackhide__ = True
  480. await self._not.to_have_id(id, timeout)
  481. async def to_have_js_property(
  482. self,
  483. name: str,
  484. value: Any,
  485. timeout: float = None,
  486. ) -> None:
  487. __tracebackhide__ = True
  488. await self._expect_impl(
  489. "to.have.property",
  490. FrameExpectOptions(
  491. expressionArg=name, expectedValue=value, timeout=timeout
  492. ),
  493. value,
  494. "Locator expected to have JS Property",
  495. 'Expect "to_have_property"',
  496. )
  497. async def not_to_have_js_property(
  498. self,
  499. name: str,
  500. value: Any,
  501. timeout: float = None,
  502. ) -> None:
  503. __tracebackhide__ = True
  504. await self._not.to_have_js_property(name, value, timeout)
  505. async def to_have_value(
  506. self,
  507. value: Union[str, Pattern[str]],
  508. timeout: float = None,
  509. ) -> None:
  510. __tracebackhide__ = True
  511. expected_text = to_expected_text_values([value])
  512. await self._expect_impl(
  513. "to.have.value",
  514. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  515. value,
  516. "Locator expected to have Value",
  517. 'Expect "to_have_value"',
  518. )
  519. async def not_to_have_value(
  520. self,
  521. value: Union[str, Pattern[str]],
  522. timeout: float = None,
  523. ) -> None:
  524. __tracebackhide__ = True
  525. await self._not.to_have_value(value, timeout)
  526. async def to_have_values(
  527. self,
  528. values: Union[
  529. Sequence[str], Sequence[Pattern[str]], Sequence[Union[Pattern[str], str]]
  530. ],
  531. timeout: float = None,
  532. ) -> None:
  533. __tracebackhide__ = True
  534. expected_text = to_expected_text_values(values)
  535. await self._expect_impl(
  536. "to.have.values",
  537. FrameExpectOptions(expectedText=expected_text, timeout=timeout),
  538. values,
  539. "Locator expected to have Values",
  540. 'Expect "to_have_values"',
  541. )
  542. async def not_to_have_values(
  543. self,
  544. values: Union[
  545. Sequence[str], Sequence[Pattern[str]], Sequence[Union[Pattern[str], str]]
  546. ],
  547. timeout: float = None,
  548. ) -> None:
  549. __tracebackhide__ = True
  550. await self._not.to_have_values(values, timeout)
  551. async def to_have_text(
  552. self,
  553. expected: Union[
  554. Sequence[str],
  555. Sequence[Pattern[str]],
  556. Sequence[Union[Pattern[str], str]],
  557. Pattern[str],
  558. str,
  559. ],
  560. useInnerText: bool = None,
  561. timeout: float = None,
  562. ignoreCase: bool = None,
  563. ) -> None:
  564. __tracebackhide__ = True
  565. if isinstance(expected, collections.abc.Sequence) and not isinstance(
  566. expected, str
  567. ):
  568. expected_text = to_expected_text_values(
  569. expected,
  570. normalize_white_space=True,
  571. ignoreCase=ignoreCase,
  572. )
  573. await self._expect_impl(
  574. "to.have.text.array",
  575. FrameExpectOptions(
  576. expectedText=expected_text,
  577. useInnerText=useInnerText,
  578. timeout=timeout,
  579. ),
  580. expected,
  581. "Locator expected to have text",
  582. 'Expect "to_have_text"',
  583. )
  584. else:
  585. expected_text = to_expected_text_values(
  586. [expected], normalize_white_space=True, ignoreCase=ignoreCase
  587. )
  588. await self._expect_impl(
  589. "to.have.text",
  590. FrameExpectOptions(
  591. expectedText=expected_text,
  592. useInnerText=useInnerText,
  593. timeout=timeout,
  594. ),
  595. expected,
  596. "Locator expected to have text",
  597. 'Expect "to_have_text"',
  598. )
  599. async def not_to_have_text(
  600. self,
  601. expected: Union[
  602. Sequence[str],
  603. Sequence[Pattern[str]],
  604. Sequence[Union[Pattern[str], str]],
  605. Pattern[str],
  606. str,
  607. ],
  608. useInnerText: bool = None,
  609. timeout: float = None,
  610. ignoreCase: bool = None,
  611. ) -> None:
  612. __tracebackhide__ = True
  613. await self._not.to_have_text(expected, useInnerText, timeout, ignoreCase)
  614. async def to_be_attached(
  615. self,
  616. attached: bool = None,
  617. timeout: float = None,
  618. ) -> None:
  619. __tracebackhide__ = True
  620. if attached is None:
  621. attached = True
  622. attached_string = "attached" if attached else "detached"
  623. await self._expect_impl(
  624. ("to.be.attached" if attached else "to.be.detached"),
  625. FrameExpectOptions(timeout=timeout),
  626. None,
  627. f"Locator expected to be {attached_string}",
  628. 'Expect "to_be_attached"',
  629. )
  630. async def to_be_checked(
  631. self,
  632. timeout: float = None,
  633. checked: bool = None,
  634. indeterminate: bool = None,
  635. ) -> None:
  636. __tracebackhide__ = True
  637. expected_value = {}
  638. if indeterminate is not None:
  639. expected_value["indeterminate"] = indeterminate
  640. if checked is not None:
  641. expected_value["checked"] = checked
  642. checked_string: str
  643. if indeterminate:
  644. checked_string = "indeterminate"
  645. else:
  646. checked_string = "unchecked" if checked is False else "checked"
  647. await self._expect_impl(
  648. "to.be.checked",
  649. FrameExpectOptions(timeout=timeout, expectedValue=expected_value),
  650. None,
  651. f"Locator expected to be {checked_string}",
  652. 'Expect "to_be_checked"',
  653. )
  654. async def not_to_be_attached(
  655. self,
  656. attached: bool = None,
  657. timeout: float = None,
  658. ) -> None:
  659. __tracebackhide__ = True
  660. await self._not.to_be_attached(attached=attached, timeout=timeout)
  661. async def not_to_be_checked(
  662. self,
  663. timeout: float = None,
  664. ) -> None:
  665. __tracebackhide__ = True
  666. await self._not.to_be_checked(timeout)
  667. async def to_be_disabled(
  668. self,
  669. timeout: float = None,
  670. ) -> None:
  671. __tracebackhide__ = True
  672. await self._expect_impl(
  673. "to.be.disabled",
  674. FrameExpectOptions(timeout=timeout),
  675. None,
  676. "Locator expected to be disabled",
  677. 'Expect "to_be_disabled"',
  678. )
  679. async def not_to_be_disabled(
  680. self,
  681. timeout: float = None,
  682. ) -> None:
  683. __tracebackhide__ = True
  684. await self._not.to_be_disabled(timeout)
  685. async def to_be_editable(
  686. self,
  687. editable: bool = None,
  688. timeout: float = None,
  689. ) -> None:
  690. __tracebackhide__ = True
  691. if editable is None:
  692. editable = True
  693. editable_string = "editable" if editable else "readonly"
  694. await self._expect_impl(
  695. "to.be.editable" if editable else "to.be.readonly",
  696. FrameExpectOptions(timeout=timeout),
  697. None,
  698. f"Locator expected to be {editable_string}",
  699. 'Expect "to_be_editable"',
  700. )
  701. async def not_to_be_editable(
  702. self,
  703. editable: bool = None,
  704. timeout: float = None,
  705. ) -> None:
  706. __tracebackhide__ = True
  707. await self._not.to_be_editable(editable, timeout)
  708. async def to_be_empty(
  709. self,
  710. timeout: float = None,
  711. ) -> None:
  712. __tracebackhide__ = True
  713. await self._expect_impl(
  714. "to.be.empty",
  715. FrameExpectOptions(timeout=timeout),
  716. None,
  717. "Locator expected to be empty",
  718. 'Expect "to_be_empty"',
  719. )
  720. async def not_to_be_empty(
  721. self,
  722. timeout: float = None,
  723. ) -> None:
  724. __tracebackhide__ = True
  725. await self._not.to_be_empty(timeout)
  726. async def to_be_enabled(
  727. self,
  728. enabled: bool = None,
  729. timeout: float = None,
  730. ) -> None:
  731. __tracebackhide__ = True
  732. if enabled is None:
  733. enabled = True
  734. enabled_string = "enabled" if enabled else "disabled"
  735. await self._expect_impl(
  736. "to.be.enabled" if enabled else "to.be.disabled",
  737. FrameExpectOptions(timeout=timeout),
  738. None,
  739. f"Locator expected to be {enabled_string}",
  740. 'Expect "to_be_enabled"',
  741. )
  742. async def not_to_be_enabled(
  743. self,
  744. enabled: bool = None,
  745. timeout: float = None,
  746. ) -> None:
  747. __tracebackhide__ = True
  748. await self._not.to_be_enabled(enabled, timeout)
  749. async def to_be_hidden(
  750. self,
  751. timeout: float = None,
  752. ) -> None:
  753. __tracebackhide__ = True
  754. await self._expect_impl(
  755. "to.be.hidden",
  756. FrameExpectOptions(timeout=timeout),
  757. None,
  758. "Locator expected to be hidden",
  759. 'Expect "to_be_hidden"',
  760. )
  761. async def not_to_be_hidden(
  762. self,
  763. timeout: float = None,
  764. ) -> None:
  765. __tracebackhide__ = True
  766. await self._not.to_be_hidden(timeout)
  767. async def to_be_visible(
  768. self,
  769. visible: bool = None,
  770. timeout: float = None,
  771. ) -> None:
  772. __tracebackhide__ = True
  773. if visible is None:
  774. visible = True
  775. visible_string = "visible" if visible else "hidden"
  776. await self._expect_impl(
  777. "to.be.visible" if visible else "to.be.hidden",
  778. FrameExpectOptions(timeout=timeout),
  779. None,
  780. f"Locator expected to be {visible_string}",
  781. 'Expect "to_be_visible"',
  782. )
  783. async def not_to_be_visible(
  784. self,
  785. visible: bool = None,
  786. timeout: float = None,
  787. ) -> None:
  788. __tracebackhide__ = True
  789. await self._not.to_be_visible(visible, timeout)
  790. async def to_be_focused(
  791. self,
  792. timeout: float = None,
  793. ) -> None:
  794. __tracebackhide__ = True
  795. await self._expect_impl(
  796. "to.be.focused",
  797. FrameExpectOptions(timeout=timeout),
  798. None,
  799. "Locator expected to be focused",
  800. 'Expect "to_be_focused"',
  801. )
  802. async def not_to_be_focused(
  803. self,
  804. timeout: float = None,
  805. ) -> None:
  806. __tracebackhide__ = True
  807. await self._not.to_be_focused(timeout)
  808. async def to_be_in_viewport(
  809. self,
  810. ratio: float = None,
  811. timeout: float = None,
  812. ) -> None:
  813. __tracebackhide__ = True
  814. await self._expect_impl(
  815. "to.be.in.viewport",
  816. FrameExpectOptions(timeout=timeout, expectedNumber=ratio),
  817. None,
  818. "Locator expected to be in viewport",
  819. 'Expect "to_be_in_viewport"',
  820. )
  821. async def not_to_be_in_viewport(
  822. self, ratio: float = None, timeout: float = None
  823. ) -> None:
  824. __tracebackhide__ = True
  825. await self._not.to_be_in_viewport(ratio=ratio, timeout=timeout)
  826. async def to_have_accessible_description(
  827. self,
  828. description: Union[str, Pattern[str]],
  829. ignoreCase: bool = None,
  830. timeout: float = None,
  831. ) -> None:
  832. __tracebackhide__ = True
  833. expected_values = to_expected_text_values(
  834. [description], ignoreCase=ignoreCase, normalize_white_space=True
  835. )
  836. await self._expect_impl(
  837. "to.have.accessible.description",
  838. FrameExpectOptions(expectedText=expected_values, timeout=timeout),
  839. None,
  840. "Locator expected to have accessible description",
  841. 'Expect "to_have_accessible_description"',
  842. )
  843. async def not_to_have_accessible_description(
  844. self,
  845. name: Union[str, Pattern[str]],
  846. ignoreCase: bool = None,
  847. timeout: float = None,
  848. ) -> None:
  849. __tracebackhide__ = True
  850. await self._not.to_have_accessible_description(name, ignoreCase, timeout)
  851. async def to_have_accessible_name(
  852. self,
  853. name: Union[str, Pattern[str]],
  854. ignoreCase: bool = None,
  855. timeout: float = None,
  856. ) -> None:
  857. __tracebackhide__ = True
  858. expected_values = to_expected_text_values(
  859. [name], ignoreCase=ignoreCase, normalize_white_space=True
  860. )
  861. await self._expect_impl(
  862. "to.have.accessible.name",
  863. FrameExpectOptions(expectedText=expected_values, timeout=timeout),
  864. None,
  865. "Locator expected to have accessible name",
  866. 'Expect "to_have_accessible_name"',
  867. )
  868. async def not_to_have_accessible_name(
  869. self,
  870. name: Union[str, Pattern[str]],
  871. ignoreCase: bool = None,
  872. timeout: float = None,
  873. ) -> None:
  874. __tracebackhide__ = True
  875. await self._not.to_have_accessible_name(name, ignoreCase, timeout)
  876. async def to_have_role(self, role: AriaRole, timeout: float = None) -> None:
  877. __tracebackhide__ = True
  878. if isinstance(role, Pattern):
  879. raise Error('"role" argument in to_have_role must be a string')
  880. expected_values = to_expected_text_values([role])
  881. await self._expect_impl(
  882. "to.have.role",
  883. FrameExpectOptions(expectedText=expected_values, timeout=timeout),
  884. None,
  885. "Locator expected to have accessible role",
  886. 'Expect "to_have_role"',
  887. )
  888. async def to_have_accessible_error_message(
  889. self,
  890. errorMessage: Union[str, Pattern[str]],
  891. ignoreCase: bool = None,
  892. timeout: float = None,
  893. ) -> None:
  894. __tracebackhide__ = True
  895. expected_values = to_expected_text_values(
  896. [errorMessage], ignoreCase=ignoreCase, normalize_white_space=True
  897. )
  898. await self._expect_impl(
  899. "to.have.accessible.error.message",
  900. FrameExpectOptions(expectedText=expected_values, timeout=timeout),
  901. None,
  902. "Locator expected to have accessible error message",
  903. 'Expect "to_have_accessible_error_message"',
  904. )
  905. async def not_to_have_accessible_error_message(
  906. self,
  907. errorMessage: Union[str, Pattern[str]],
  908. ignoreCase: bool = None,
  909. timeout: float = None,
  910. ) -> None:
  911. __tracebackhide__ = True
  912. await self._not.to_have_accessible_error_message(
  913. errorMessage=errorMessage, ignoreCase=ignoreCase, timeout=timeout
  914. )
  915. async def not_to_have_role(self, role: AriaRole, timeout: float = None) -> None:
  916. __tracebackhide__ = True
  917. await self._not.to_have_role(role, timeout)
  918. async def to_match_aria_snapshot(
  919. self, expected: str, timeout: float = None
  920. ) -> None:
  921. __tracebackhide__ = True
  922. await self._expect_impl(
  923. "to.match.aria",
  924. FrameExpectOptions(expectedValue=expected, timeout=timeout),
  925. expected,
  926. "Locator expected to match Aria snapshot",
  927. 'Expect "to_match_aria_snapshot"',
  928. )
  929. async def not_to_match_aria_snapshot(
  930. self, expected: str, timeout: float = None
  931. ) -> None:
  932. __tracebackhide__ = True
  933. await self._not.to_match_aria_snapshot(expected, timeout)
  934. class APIResponseAssertions:
  935. def __init__(
  936. self,
  937. response: APIResponse,
  938. timeout: float = None,
  939. is_not: bool = False,
  940. message: Optional[str] = None,
  941. is_soft: bool = False,
  942. ) -> None:
  943. self._loop = response._loop
  944. self._dispatcher_fiber = response._dispatcher_fiber
  945. self._timeout = timeout
  946. self._is_not = is_not
  947. self._actual = response
  948. self._custom_message = message
  949. self._is_soft = is_soft
  950. @property
  951. def _not(self) -> "APIResponseAssertions":
  952. return APIResponseAssertions(
  953. self._actual,
  954. self._timeout,
  955. not self._is_not,
  956. self._custom_message,
  957. self._is_soft,
  958. )
  959. async def to_be_ok(
  960. self,
  961. ) -> None:
  962. __tracebackhide__ = True
  963. if self._is_not is not self._actual.ok:
  964. return
  965. message = f"Response status expected to be within [200..299] range, was '{self._actual.status}'"
  966. if self._is_not:
  967. message = message.replace("expected to", "expected not to")
  968. out_message = self._custom_message or message
  969. out_message += format_call_log(await self._actual._fetch_log())
  970. content_type = self._actual.headers.get("content-type")
  971. is_text_encoding = content_type and is_textual_mime_type(content_type)
  972. text = await self._actual.text() if is_text_encoding else None
  973. if text is not None:
  974. out_message += f"\n Response Text:\n{text[:1000]}"
  975. _record_soft_or_raise(AssertionError(out_message), self._is_soft)
  976. async def not_to_be_ok(self) -> None:
  977. __tracebackhide__ = True
  978. await self._not.to_be_ok()
  979. def expected_regex(
  980. pattern: Pattern[str],
  981. match_substring: bool,
  982. normalize_white_space: bool,
  983. ignoreCase: Optional[bool] = None,
  984. ) -> ExpectedTextValue:
  985. expected = ExpectedTextValue(
  986. regexSource=pattern.pattern,
  987. regexFlags=escape_regex_flags(pattern),
  988. matchSubstring=match_substring,
  989. normalizeWhiteSpace=normalize_white_space,
  990. ignoreCase=ignoreCase,
  991. )
  992. if expected["ignoreCase"] is None:
  993. del expected["ignoreCase"]
  994. return expected
  995. def to_expected_text_values(
  996. items: Union[
  997. Sequence[Pattern[str]], Sequence[str], Sequence[Union[str, Pattern[str]]]
  998. ],
  999. match_substring: bool = False,
  1000. normalize_white_space: bool = False,
  1001. ignoreCase: Optional[bool] = None,
  1002. ) -> Sequence[ExpectedTextValue]:
  1003. out: List[ExpectedTextValue] = []
  1004. assert isinstance(items, (list, tuple))
  1005. for item in items:
  1006. if isinstance(item, str):
  1007. o = ExpectedTextValue(
  1008. string=item,
  1009. matchSubstring=match_substring,
  1010. normalizeWhiteSpace=normalize_white_space,
  1011. ignoreCase=ignoreCase,
  1012. )
  1013. if o["ignoreCase"] is None:
  1014. del o["ignoreCase"]
  1015. out.append(o)
  1016. elif isinstance(item, Pattern):
  1017. out.append(
  1018. expected_regex(item, match_substring, normalize_white_space, ignoreCase)
  1019. )
  1020. else:
  1021. raise Error("value must be a string or regular expression")
  1022. return out