Assertions

Web-first assertions in the library, after Playwright's: an assertion checks the page again and again until it holds or the timeout passes. A journey's expect line is the same engine.

C#
await page.Css("#count").ExpectAsync(Matcher.Text("2 results"));
await page.Role("button", "Save").ExpectAsync(Matcher.Enabled, new ExpectOptions { Timeout = TimeSpan.FromSeconds(10) });
await page.Css(".spinner").ExpectAsync(Matcher.Visible, new ExpectOptions { Negate = true });
await page.ExpectAsync(Matcher.Url("**/checkout"));

ExpectAsync throws an ExpectationException (a TimeoutException) carrying the result. CheckAsync takes the same arguments and returns the ExpectationResult without throwing, which is what a test framework adapter reports:

C#
ExpectationResult result = await page.Css("#count").CheckAsync(Matcher.Text("3"), new ExpectOptions { Timeout = TimeSpan.FromSeconds(1) });
result.Passed;
result.Message;   // Timed out after 1s waiting for css #count to have text "3": received "2 results".
result.Received;  // "2 results"
  1. false

A Matcher is data: what to check, and for text how (TextMatch.Exact, Contains or Regex). The same value is built by a journey line, by code and by an adapter.

Matcher Holds when
Visible, Hidden the first match is visible; Hidden also holds when nothing matches
Enabled, Disabled, Checked, Unchecked the state
Focused, Editable, Attached, Empty focus, input, in the document, no value or text
Text(text), Value(text) the whole text (whitespace collapsed) or value; Contains, Regex
Attribute(name, [text]) the attribute is present, or has the value
Class(classes) the whole class attribute; Contains: every class listed
Count(n) exactly n matches
Title(text), Url(glob) on a Page: page.ExpectAsync(...)

Only Visible, Hidden, Attached and Count can be asserted about nothing; every other matcher needs an element, even negated, as in Playwright: Enabled with Negate on a missing element keeps failing (received no element matches).

ExpectOptions holds the Timeout (the page's ActionTimeout, 5 s, by default), Negate, IgnoreCase, UseInnerText for Text, and the PollInterval. A negated matcher keeps checking until it stops holding.

The core package depends on no test framework. Because CheckAsync returns a result, an adapter only has to report it: build a Matcher, call CheckAsync, and fail the framework's assertion with result.Message. The Nullean.Cdp tests include a small AwesomeAssertions adapter (await page.Css("#count").Should().HaveTextAsync("2 results")) as proof that because reasons and AssertionScope behave. Pass the message to the framework as an argument, never as its format string: a selector or page text can hold {braces}.