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, throwsInvalidOperationException. - The flow's trace entry has kind
web.flow, the nameWEB flow · {name}, and recordsweb.session,web.backendandweb.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
- Logging in - a login strategy built from flows.
- Diagnostics and artifacts - how a failed flow step appears in the trace.