Auto-Waiting
Why it exists
Desktop applications have the same race conditions as web applications: a button may not be clickable the moment the window opens, a dialog may take a moment to appear after a trigger, a list may populate asynchronously. Playwright proved that building the wait loop into the framework — rather than leaving it to every test author — produces tests that are more reliable and shorter.
Flawright follows the same principle. Actions and assertions that need an element to exist block until the element appears, up to a configurable timeout, before failing.
How it works
Internally, Flawright uses a polling loop (AutoWait.UntilAsync / AutoWait.UntilTrueAsync) that:
- Checks the condition (element present, element visible, text matches, etc.)
- If the condition is not yet met, sleeps for the retry interval and tries again
- Continues until the condition is met or the deadline is exceeded
- Throws
FlawrightTimeoutExceptionwhen the deadline is exceeded
The polling loop also catches transient exceptions from FlaUI (e.g., element becoming stale during a redraw) and retries silently.
Default values
| Setting | Default |
|---|---|
DefaultTimeout |
5 seconds |
DefaultRetryInterval |
100 ms |
Configure these via FlawrightOptions:
await using var fw = await Flawright.LaunchAsync(
new LaunchOptions { ApplicationPath = "notepad.exe" },
new FlawrightOptions
{
DefaultTimeout = TimeSpan.FromSeconds(10),
DefaultRetryInterval = TimeSpan.FromMilliseconds(50)
});
Overriding per-call
Every locator and page action accepts an optional timeout parameter and a CancellationToken:
using Flawright.Locator; // for LocatorWaitForOptions
// Wait up to 30 seconds for a specific element
var locator = page.Locator("name:Loading Complete");
await locator.WaitForAsync(new LocatorWaitForOptions { Timeout = TimeSpan.FromSeconds(30) });
// Use a CancellationToken to cancel early
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await page.Locator("name:Dialog").Expect()
.ToBeVisibleAsync(ct: cts.Token);
What gets auto-waited
| Operation | Auto-waited |
|---|---|
locator.First / locator.Nth(n) |
Sync narrowing — auto-wait happens when an action is called on the resulting locator |
locator.AllAsync() |
Yes — retries until at least one element exists |
locator.CountAsync() |
No — returns current count immediately |
page.ClickAsync(selector) |
Yes — waits for element, then clicks |
page.FillAsync(selector, text) |
Yes — waits for element, then fills |
page.TypeAsync(selector, text) |
Yes — waits for element, then types |
page.PressAsync(selector, key) |
Yes — waits for element, then sends key |
page.CheckAsync(selector) |
Yes — waits for element, then checks |
page.UncheckAsync(selector) |
Yes — waits for element, then unchecks |
page.SelectOptionAsync(selector, value) |
Yes — waits for element, then selects |
page.WaitForSelectorAsync(selector) |
Yes — waits for the element to appear |
browser.WaitForPageAsync(title) |
Yes — polls until window with matching title appears |
expect.ToBeVisibleAsync() |
Yes — retries until visible |
expect.ToBeHiddenAsync() |
Yes — retries until hidden |
expect.ToBeEnabledAsync() |
Yes — retries until enabled |
expect.ToBeDisabledAsync() |
Yes — retries until disabled |
expect.ToHaveTextAsync(text) |
Yes — retries until text matches |
expect.ToHaveValueAsync(value) |
Yes — retries until value matches |
expect.ToBeCheckedAsync() |
Yes — retries until checked |
expect.ToHaveCountAsync(n) |
No — checks current count, throws immediately on mismatch |
element.ClickAsync() |
No — element already resolved |
element.FillAsync(text) |
No — element already resolved |
page.ScreenshotAsync() |
No — captures current state |
Timeout exception
When the deadline is exceeded, FlawrightTimeoutException is thrown. It carries the selector string and the elapsed duration:
using Flawright.Locator; // for LocatorWaitForOptions
try
{
await page.Locator("#save").WaitForAsync(new LocatorWaitForOptions { Timeout = TimeSpan.FromSeconds(2) });
}
catch (FlawrightTimeoutException ex)
{
Console.WriteLine(ex.Message); // "Locator '#save' not found within 00:00:02."
Console.WriteLine(ex.Selector); // "#save"
Console.WriteLine(ex.Timeout); // 00:00:02
}
FlawrightTimeoutException inherits from TimeoutException.
Best practices
Prefer locator actions over element actions for top-level interaction. page.ClickAsync("name:OK") auto-waits for the element. If you need to interact with a specific index, use locator.Nth(n).ClickAsync() — the auto-wait fires when the action is called on the narrowed locator.
Use Expect() assertions instead of manual element state checks. expect.ToBeVisibleAsync() retries until visible; a raw element.IsVisibleAsync() check in Assert.True(...) does not.
Set tighter timeouts in CI. A test that waits 10 seconds by default can mask a genuine bug. In CI, override to 5 seconds (or leave the 5-second default) so flakiness surfaces quickly.
Don't hardcode Task.Delay. Hardcoded sleeps hide timing problems and slow down the suite. Rely on the auto-waiting built into every locator action.