Automation Testing
Intermediate 14 min read Automation Testing

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.

explicit waitimplicit waitauto-waitWebDriverWaitflakinesspollingtimeout
Introduction

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.

Motivation

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.
Think about it

Mental model

Imagine

Waiting for a bus

Think of

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.

Mechanics

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.

Locator created
Action requested
Poll DOM
Condition met?
Timeout exceeded?
Action completes
Reference

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_000

Raise the per-test timeout only for genuinely slow flows.

Hands-on

Examples

Explicit waits with WebDriverWait and Playwright's expect

Explicit waits with WebDriverWait and Playwright's expect
// Selenium / Java - explicit wait on a condition
import 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 polling
const 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.

Real world

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.
Guidelines

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.
Watch out

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.
Root cause analysis

Common issues

Error
TimeoutError: locator.click: Timeout 5000ms exceeded
Root cause

Element never reached the actionable state (not visible/enabled/stable).

Fix

Check the element's readiness and loading condition; raise a targeted timeout on this locator if the flow is genuinely slow.

Error
Selenium TimeoutException: Expected condition failed
Root cause

Condition never became true within the WebDriverWait window.

Fix

Confirm the selector and the actual state of the element; wait for the right condition (visible vs present).

Error
Timeout waiting for 'networkidle'
Root cause

Persistent connections (websockets, beacons) never go idle.

Fix

Stop waiting on networkidle; sync on a specific response or user-visible element instead.

When things break

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.
Interviews

Interview checkpoint

Practice

Hands-on lab

Synchronize a Flaky Checkout Flow
35 min ยท intermediate
In progress
Objectives
  • 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

0/5 steps
0%
sandbox ยท Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which of these is the most reliable synchronization technique for a payment confirmation screen?

2

Playwright actions like click() wait automatically until the element is ready before acting.

3

The same test passes in CI at 2am but fails at 9am when the servers are slow. What is the most likely cause?

Quick reference

Cheat sheet

Waits at a glance
page.waitForTimeout(ms)Fixed sleep - last resort
expect(locator).toBeVisible()Poll until visible (Playwright)
WebDriverWait(...).until(...)Explicit wait (Selenium)
implicitlyWait(...)Global lookup timeout (Selenium)
waitForResponse('**/api/...')Sync on a network call
page.setDefaultTimeout(...)Default action timeout
Go deeper

Official documentation