Skip to main content

ferridriver_expect/
page.rs

1//! Page web-first matchers — url + title only. Snapshot/screenshot/
2//! aria matchers live in `ferridriver-test` because they need the
3//! test-runner's snapshot directory + image pipeline.
4
5use std::sync::Arc;
6
7use ferridriver::Page;
8
9use crate::AssertionFailure;
10use crate::builder::Expect;
11use crate::poll::{ExpectContext, MatchError, poll_until};
12use crate::value::StringOrRegex;
13
14fn page_ctx(method: &'static str, is_not: bool) -> ExpectContext {
15  ExpectContext {
16    method,
17    subject: "page".into(),
18    is_not,
19  }
20}
21
22impl Expect<'_, Arc<Page>> {
23  pub async fn to_have_title(&self, expected: impl Into<StringOrRegex>) -> Result<(), AssertionFailure> {
24    let expected = expected.into();
25    let page = self.subject;
26    let is_not = self.is_not;
27    poll_until(self.timeout, page_ctx("toHaveTitle", is_not), || {
28      let expected = expected.clone();
29      async move {
30        let actual = page
31          .title()
32          .await
33          .map_err(|e| MatchError::new("(title)", e.to_string()))?;
34        let matches = expected.matches(&actual);
35        if matches == is_not {
36          Err(MatchError::new(
37            format!("{}{}", if is_not { "not " } else { "" }, expected.description()),
38            format!("\"{actual}\""),
39          ))
40        } else {
41          Ok(())
42        }
43      }
44    })
45    .await
46  }
47
48  pub async fn to_contain_title(&self, expected: &str) -> Result<(), AssertionFailure> {
49    let expected = expected.to_string();
50    let page = self.subject;
51    let is_not = self.is_not;
52    poll_until(self.timeout, page_ctx("toContainTitle", is_not), || {
53      let expected = expected.clone();
54      async move {
55        let actual = page
56          .title()
57          .await
58          .map_err(|e| MatchError::new("(title)", e.to_string()))?;
59        let contains = actual.contains(&expected);
60        if contains == is_not {
61          Err(MatchError::new(
62            format!("{}containing \"{expected}\"", if is_not { "not " } else { "" }),
63            format!("\"{actual}\""),
64          ))
65        } else {
66          Ok(())
67        }
68      }
69    })
70    .await
71  }
72
73  pub async fn to_have_url(&self, expected: impl Into<StringOrRegex>) -> Result<(), AssertionFailure> {
74    let expected = expected.into();
75    let page = self.subject;
76    let is_not = self.is_not;
77    poll_until(self.timeout, page_ctx("toHaveURL", is_not), || {
78      let expected = expected.clone();
79      async move {
80        let actual = page.url();
81        let matches = expected.matches(&actual);
82        if matches == is_not {
83          Err(MatchError::new(
84            format!("{}{}", if is_not { "not " } else { "" }, expected.description()),
85            format!("\"{actual}\""),
86          ))
87        } else {
88          Ok(())
89        }
90      }
91    })
92    .await
93  }
94
95  pub async fn to_contain_url(&self, expected: &str) -> Result<(), AssertionFailure> {
96    let expected = expected.to_string();
97    let page = self.subject;
98    let is_not = self.is_not;
99    poll_until(self.timeout, page_ctx("toContainURL", is_not), || {
100      let expected = expected.clone();
101      async move {
102        let actual = page.url();
103        let contains = actual.contains(&expected);
104        if contains == is_not {
105          Err(MatchError::new(
106            format!("{}containing \"{expected}\"", if is_not { "not " } else { "" }),
107            format!("\"{actual}\""),
108          ))
109        } else {
110          Ok(())
111        }
112      }
113    })
114    .await
115  }
116}