Skip to content

Documentation

Agentic end-to-end tests for .NET. Describe a goal in plain language, let an agent drive the app in Chromium, then check the result with locators. A verified agent step is recorded and replays on the next run with no model call.

Terminal window
dotnet add package E2E
dotnet add package E2E.NUnit # or E2E.XUnit.V3 for xUnit v3

Test filters (ITestFilter, TestFilterContext, and TestFilterResult) leave out the tests a change cannot affect. The seam, the change detection (INVALID_CHANGED_SINCE), and the run.changes and run.filters report fields are ported; no dotnet test host calls a filter yet.

Locator.Filter keeps or drops the matches of a locator by their subtree. It takes any of four named arguments, and at least one.

var row = Screen.GetByRole("listitem").Filter(hasText: "Invoice 42", hasNotText: "Paid");
await row.GetByRole("button", "Void").TapAsync();
  • hasText with a string is a case-insensitive substring match over the match and its descendants, unlike a query text, which matches exactly. Pass a Regex for anything stricter.
  • hasNotText excludes matches with that subtree text, using the same substring or Regex rules.
  • has keeps a match whose subtree resolves the inner locator.
  • hasNot drops a match whose subtree resolves the inner locator. It follows the same scoping rules as has.

Filter() with none of the four throws INVALID_LOCATOR, and so does a has or hasNot locator made by another screen. Filter("text") and Filter(locator) remain as the one-argument forms of hasText and has. On iOS, a plain React Native View has no children in the tree, so has on it matches nothing and hasNot keeps every such row.

Screen.ScrollUntilVisibleAsync(target, options) scrolls the viewport until target resolves visibly. Locator.ScrollUntilVisibleAsync(target, options) swipes that node instead, so a feed or table that is its own scroll container pages without the pointer hovering it first.

await Screen.ScrollUntilVisibleAsync(Screen.GetByRole("button", "Accept"));
await Screen.GetByTestId("feed").ScrollUntilVisibleAsync(row, new ScrollUntilVisibleOptions { Momentum = Momentum.Fast });
Option Type Default
Direction ScrollDirection Down
Momentum Momentum (None, Slow, Fast) Slow
Timeout TimeSpan 30 seconds

Timeout is its own default, not actionTimeout, and bounds the whole scroll, each swipe included. LOCATOR_NOT_FOUND means the deadline expired first, a swipe it cut short included. INVALID_LOCATOR means target was made by another target’s screen.

A read or navigation that times out fails with OPERATION_TIMEOUT. It is not retried, even when the call log mentions a detached element or a navigation race. On a device, an input (a tap, key, swipe, or Back) that the device does not answer within actionTimeout fails with ACTION_MAY_HAVE_COMMITTED and is never repeated, because it may have landed; an input the timeout ran out before is not sent (OPERATION_TIMEOUT), and a long press that holds at least as long as the time left is refused (NOT_ACTIONABLE). See EngineErrorCodes in the API reference.