Waits & Synchronization
Web apps are asynchronous: elements render, networks respond and animations settle at unpredictable times. Waits synchronize your test with the app, and the difference between implicit, explicit and auto-waits is the difference between flaky and stable suites.
What is this?
Waits control when an automation command proceeds relative to the page state. Implicit waits set a global timeout that the driver applies to every element lookup; explicit waits poll a specific condition until it is true or times out. Modern frameworks like Playwright auto-wait: built-in actions poll until the element is actionable, so most flakiness disappears without a single sleep() in your code.
Why do you need it?
- The DOM and network are asynchronous, so an element can exist, then change, and only then be ready to click.
- Blind sleep() guesses the timing once and is wrong as often as it is right - slow when the app is fast, flaky when it is not.
- Explicit waits encode the actual condition (element visible, request finished, text changed) instead of a duration.
- Auto-waiting frameworks eliminate whole classes of timing bugs by default, leaving waits only for genuinely slow flows.
- Correct waits make suites deterministic and fast: they proceed the instant the condition is met, not after a fixed delay.
Mental model
Waiting for a bus
Waiting for the app
If you wait 10 seconds exactly (sleep), you catch the bus only if it happens to arrive then - flaky. If you check the timetable (polling) and board when the bus actually arrives (condition met), you never wait longer than necessary. Explicit waits check 'is the bus here?' instead of counting seconds.
How it works
- A command targets an element and, if auto-wait is on, first waits for the element to be attached, visible and stable.
- If the condition is not met, the framework polls the DOM on an interval (for example every 50-100ms) up to a timeout.
- When the condition becomes true, the action executes immediately - no wasted fixed delay.
- If the timeout elapses first, the runner throws a descriptive timeout error with the last known page state.
- Implicit waits, by contrast, apply a blanket timeout to every lookup but cannot wait for 'clickable'.
How a Playwright action waits for an element to become actionable.
Key commands
page.setDefaultTimeout(10_000)Set the default timeout for all Playwright actions in milliseconds.
expect(locator).toBeVisible({ timeout: 15_000 })Poll visibility for up to 15s before failing the assertion.
page.waitForResponse('**/api/order')Wait for a specific network request to finish - a precise sync point.
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5))Set a global implicit wait (Selenium); affects every element lookup.
page.waitForLoadState('networkidle')Wait until the page has no pending network activity (use sparingly).
npx playwright test --grep 'slow-flow' --timeout 30_000Raise the per-test timeout only for genuinely slow flows.
Examples
Explicit waits with WebDriverWait and Playwright's expect
// Selenium / Java - explicit wait on a conditionimport org.openqa.selenium.support.ui.WebDriverWait;import org.openqa.selenium.support.ui.ExpectedConditions;import java.time.Duration; WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));wait.until(ExpectedConditions.visibilityOfElementLocated( By.cssSelector(".order-confirmation"))); // Playwright - auto-wait plus expect pollingconst confirmation = page.locator(".order-confirmation");await expect(confirmation).toBeVisible({ timeout: 10_000 });The Selenium example polls until the confirmation element is visible, up to 10 seconds, instead of sleeping a fixed time. The Playwright equivalent relies on auto-wait inside actions and polls inside expect() until toBeVisible passes. Both advance the instant the condition is met and fail with a clear timeout otherwise.
In production
- Auto-waiting actions replace hundreds of sleep() calls, cutting suite duration and flakiness at once.
- Explicit waits on network responses keep payments and async form submissions in lockstep with the server.
- Custom expect polls give reliable assertions on data that renders asynchronously after an API response.
- Waits keep headed debugging sessions sane: the same action works headed and headless with one code path.
Best practices
- Prefer auto-wait and expect-polling over explicit waits; prefer explicit waits over sleep().
- Wait for the condition that matters: text visible, element enabled, response received - not 'the whole page loaded'.
- Keep a global default timeout and override per-locator only where genuinely slow (charts, reports).
- Never mix implicit and explicit waits in Selenium - they compound and cause unpredictable stalls.
- Base waits on user-visible state (a spinner gone, a confirmation shown) instead of implementation timing.
Common mistakes
- Using Thread.sleep() or page.waitForTimeout() as a default; it bakes a guess into the suite and slows every run.
- Mixing implicit and explicit waits in the same Selenium session, which unpredictably doubles timeouts.
- Waiting on 'networkidle' everywhere - it stalls on perpetual connections like websockets and analytics beacons.
- Waiting for an element to exist when you actually need it to be visible and clickable.
Common issues
TimeoutError: locator.click: Timeout 5000ms exceededElement never reached the actionable state (not visible/enabled/stable).
Check the element's readiness and loading condition; raise a targeted timeout on this locator if the flow is genuinely slow.
Selenium TimeoutException: Expected condition failedCondition never became true within the WebDriverWait window.
Confirm the selector and the actual state of the element; wait for the right condition (visible vs present).
Timeout waiting for 'networkidle'Persistent connections (websockets, beacons) never go idle.
Stop waiting on networkidle; sync on a specific response or user-visible element instead.
Debugging tips
- Read the timeout message: Playwright prints the selector and a DOM snapshot of what existed at failure time.
- Run headed and pause (page.pause()) to see the exact app state when a wait expires.
- Check whether the app itself is slow by replaying the flow manually; the wait may be right and the timeout too short.
- Use the network panel: if the awaited response never fires, the wait is on the wrong trigger, not the wrong duration.
Interview checkpoint
Hands-on lab
- Reproduce a timing-dependent failure
- Replace fixed sleeps with condition-based waits
- Wait on a network response as a sync point
- Verify the suite passes repeatedly and quickly
Synchronize a Flaky Checkout Flow
35 min ยท intermediate
Quick quiz
Which of these is the most reliable synchronization technique for a payment confirmation screen?
Playwright actions like click() wait automatically until the element is ready before acting.
The same test passes in CI at 2am but fails at 9am when the servers are slow. What is the most likely cause?
Cheat sheet
page.waitForTimeout(ms)Fixed sleep - last resortexpect(locator).toBeVisible()Poll until visible (Playwright)WebDriverWait(...).until(...)Explicit wait (Selenium)implicitlyWait(...)Global lookup timeout (Selenium)waitForResponse('**/api/...')Sync on a network callpage.setDefaultTimeout(...)Default action timeout