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. The trace shows it as one operation with the steps nested underneath. A failed checkout then reads as "Checkout › step 3 failed", not as a flat list of clicks.

WEB flow · Pay by card (5 steps) › step 3 failed
├─ step 1 Fill CardNumber .......... ok
├─ step 2 Fill Expiry .............. ok
├─ step 3 Select Country ........... FAILED (WebActionabilityException)
├─ step 4 Check SaveCard ........... not run
└─ step 5 Click Pay ................ not run
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);
WebFlow<TComponent> Uncheck(Func<TComponent, WebElement> element);
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);
}

Use Check to check a box and Uncheck to clear it. 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 sign-in example in WebJourney.cs uses a flow.

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​