Skip to main content

Flows

A flow is a named sequence of steps on one component. It reads better than repeating the component on every line, and it shows up in the trace as one operation with the steps nested underneath — so a failed checkout reads as "Checkout › step 3 failed", not as a flat list of clicks.

await checkout.Payment.Flow("Pay by card")
.Fill(form => form.CardNumber, "4242 4242 4242 4242")
.Fill(form => form.Expiry, "12/30")
.Select(form => form.Country, "BE")
.Check(form => form.SaveCard)
.Click(form => form.Pay)
.RunAsync();

Each lambda receives the component, so steps are type-checked and refactor-safe.

Steps

public sealed class WebFlow<TComponent> where TComponent : WebComponent
{
WebFlow<TComponent> Fill(Func<TComponent, WebElement> element, string value);
WebFlow<TComponent> Click(Func<TComponent, WebElement> element);
WebFlow<TComponent> Check(Func<TComponent, WebElement> element, bool isChecked = true);
WebFlow<TComponent> Select(Func<TComponent, WebElement> element, string value);
WebFlow<TComponent> Press(Func<TComponent, WebElement> element, WebKey key);
WebFlow<TComponent> Do(Func<TComponent, CancellationToken, ValueTask> interaction);
ValueTask RunAsync(CancellationToken cancellationToken = default);
}

Check(..., isChecked: false) unchecks. Do runs anything else — including assertions — as a step:

await dialog.Flow("Confirm deletion")
.Fill(d => d.Confirmation, "DELETE")
.Do((d, ct) => d.Confirm.Should.BeEnabledAsync(cancellationToken: ct))
.Click(d => d.Confirm)
.RunAsync();

Create a flow on any component or page with Flow(name):

public static WebFlow<TComponent> Flow<TComponent>(this TComponent component, string name)
where TComponent : WebComponent;

The demo's real login strategy drives its login page through a flow — see StandaloneSampleApp.cs.

Rules

  • Steps run in order, one at a time.
  • Each step keeps its normal behaviour: actionability waits, wait conditions, middleware and its own trace entry.
  • A flow runs once. Calling RunAsync() again, or adding a step after it started, throws InvalidOperationException.
  • The flow's trace entry has kind web.flow, the name WEB flow · {name}, and records web.session, web.backend and web.flow.step_count.

A single named interaction

For one-off interactions that deserve a name in the trace, InteractAsync wraps an arbitrary delegate as a one-step flow:

public static ValueTask InteractAsync<TComponent>(
this TComponent component,
string name,
Func<TComponent, ValueTask> interaction,
CancellationToken cancellationToken = default)
where TComponent : WebComponent;
await page.Banner.InteractAsync("Dismiss cookie banner", banner => banner.Accept.ClickAsync());

Next