Chapter 4 · Auto-Wait and Actionability
Chapter goals: Understand how Playwright's auto-wait works and the five actionability checks, know exactly what each operation waits for, and learn the correct use of timeout and force.
4.1 What Auto-Wait Solves
Tests in the Selenium era looked like this:
# ❌ Anti-pattern: guessing a "should be long enough" sleep
time.sleep(3)
driver.find_element(By.ID, "submit").click()Sleep 3 seconds: wastes 2.8 seconds on fast networks, still fails on slow ones. Playwright bakes waiting into every operation — it auto-waits for all relevant checks to pass before executing, and only throws TimeoutError on actual timeout. Your test code should never contain a single sleep.
4.2 The Five Actionability Checks
Taking locator.click() as an example, the following checks run in sequence before execution:
| Check | Meaning |
|---|---|
| Visible | Has a non-empty bounding box and no visibility:hidden |
| Stable | Not mid-animation (position consistent across two consecutive frames) |
| Receives Events | Hit-testing proves the point is not covered by a mask |
| Enabled | Not disabled |
| Editable | (For input types) editable |
Different operations have different check combinations; the official docs provide a full reference (excerpt):
Action Visible Stable Receives Events Enabled Editable
locator.click() Yes Yes Yes Yes -
locator.check() Yes Yes Yes Yes -
locator.hover() Yes Yes Yes - -
locator.fill() Yes - - Yes Yes
locator.select_option() Yes - - Yes -
locator.set_input_files() - - - - -Two details worth noting:
filldoes not require Stable or Receives Events — typing into an input doesn't need the element to be still or clickable;press,focus, anddispatch_eventbarely perform any actionability checks — they simulate keyboard/event-layer behavior.
4.3 timeout: Local Overrides and Global Defaults
Each operation has a 30-second default timeout, adjustable as needed:
# Extend a single operation to 10 seconds
page.get_by_role("button", name="Submit").click(timeout=10_000)
# Change the global default to 15 seconds
page.set_default_timeout(15_000)
# Navigation has its own default (45s), also separately configurable
page.set_default_navigation_timeout(60_000)Don't use inflated timeouts to mask problems
Timeouts are a buffer for "reasonably slow" scenarios, not a retry mechanism. A page that reliably loads within 5 seconds but is configured with a 120-second timeout will just make CI failures wait two extra minutes.
4.4 force=True: The Cost and Use of Bypassing Checks
force=True skips actionability checks and fires the action directly:
# Force-click even if the element is obscured by animation
page.get_by_role("button").click(force=True)Applicable scenarios are narrow: custom animations that permanently block Receives Events, or when you're certain an obscuring element won't intercept events — a scalpel, not a hammer. The cost is giving up Playwright's most important safety net — avoid whenever possible; if you do use it, document the reason in a code comment.
4.5 The Other Half of Waiting: Explicit State Waits
Auto-wait handles "wait for the element to be ready before acting", but some scenarios require you to explicitly declare the wait condition:
# Wait for URL change (e.g., login redirect)
page.wait_for_url("**/dashboard")
# Wait for a specific network response
with page.expect_response("**/api/user") as resp_info:
page.get_by_role("button", name="Refresh").click()
print(resp_info.value.status)
# Wait for a request to be sent
with page.expect_request("**/analytics") as req_info:
page.goto("https://example.com")
# Wait for load/networkidle state
page.wait_for_load_state("networkidle")These "event waiters" also use auto-retry semantics: they probe continuously within the timeout window, not just once and move on.
4.6 Chapter Summary
- Every action auto-runs actionability checks before executing; only proceeds when all pass, otherwise TimeoutError;
- Five checks: Visible, Stable, Receives Events, Enabled, Editable — different combos per operation;
- Never use
sleepin tests; use timeout for local fine-tuning only, don't globally inflate it to mask problems; force=Truebypassing checks is a last resort;- Page-level changes should use
wait_for_url/expect_responseetc. to express intent explicitly.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. When clicking a button with an entrance animation, Playwright will?
2. Regarding locator.fill() actionability checks, which is correct?
3. The greatest harm of having time.sleep(2) everywhere in a test is?
4. When is force=True appropriate?
🛠️ Hands-On Exercises
- Build a local page with a CSS-animated button; click it during and after the animation to observe Playwright's auto-wait behavior.
- Use
expect_responseto capture an XHR request on a real site and print its status code. - Set timeout to 1ms on a button that appears after a delay; observe the TimeoutError detail showing which checks failed.
Proceed to Chapter 5: Web-First Assertions with expect.