Diagnosing Playwright TimeoutError: Actionability Check Failures and Fixes
Playwright throws TimeoutError when an element fails an actionability check (visible, stable, enabled, etc.). Learn to read the log, match the failure to its cause, and apply the right fix.
10 Jan 2026, 06:34 UTC

Recognizable condition
You run a test and see TimeoutError: locator.click: Timeout 30000ms exceeded even though the element appears in the DOM. The useful takeaway is that Playwright’s auto‑waiting stopped because one of its actionability checks did not pass within the default 30‑second window.
Cause and diagnostic table
| Log indicates it waited for… | Typical reason | First‑line fix |
|---|---|---|
| element to be visible | Hidden by a loading spinner, skeleton UI, or a duplicate hidden match | Wait for the loading indicator to disappear or tighten the locator |
| element to be stable | CSS animation, carousel slide, or element still moving into place | Assert on the animation’s end state (e.g., a class change) instead of using a sleep |
| element to be enabled / editable | Form control disabled until async validation finishes | Add expect(locator).toBeEnabled() before the interaction |
| element to receive events (intercepts pointer events) | Overlay, modal backdrop, sticky header, or toast covering the target | Wait for the intercepting element to be hidden or remove it |
| element to be attached | Wrong locator, element inside an iframe or shadow DOM, or detached during a re‑render | Use frameLocator(), pierce the shadow root, or re‑resolve the locator after the re‑render |
Ordered diagnostic checks
- Read the actionability log – The timeout message lists what Playwright was waiting for (visible, stable, etc.) and, for pointer‑intercept issues, names the element that blocks the click.
- Confirm the locator resolves to the intended element – Run the test with
npx playwright test my-test.spec.ts --debugand watch the red highlight. If nothing highlights or the wrong element does, adjust the locator (iframe, shadow DOM, duplicate). - Inspect the trace – Enable tracing (
trace: 'retain-on-failure') and open the trace withnpx playwright show-trace. The action’s detail panel shows each check over time and screenshots that reveal overlays or animations. - Check for detachment during re‑render – If the log shows the element resolving then disappearing, wait for a post‑render signal (e.g., a success message) before interacting.
Fixes tied to findings
- Overlay intercepting clicks – Wait for the overlay to hide:
await expect(page.locator('.loading-overlay')).toBeHidden(); await page.getByRole('button', { name: 'Submit' }).click(); - Disabled‑until‑ready control – Use a web‑first assertion:
const btn = page.getByRole('button', { name: 'Save' }); await expect(btn).toBeEnabled(); await btn.click(); - Unstable element (animation) – Assert on the end condition:
await expect(page.locator('.panel')).toHaveClass('panel--open'); await page.getByRole('tab', { name: 'Settings' }).click(); - Element in an iframe or shadow DOM – Scope through frameLocator:
const frame = page.frameLocator(\"iframe[title='Payment']\"); await frame.getByLabel('Card number').fill('4242424242424242');
What not to reach for first
Avoid { force: true } as a timeout fix; it skips actionability checks and can interact with hidden or disabled elements, hiding real UI bugs. Also avoid simply increasing the global timeout – it masks performance regressions. Prefer per‑action or per‑assertion timeouts only when a legitimate long wait is measured.
Escalation criteria
Treat the timeout as an application bug when Trace Viewer shows the element never becomes actionable for a real user (e.g., a spinner that never dismisses, a control that stays disabled after data loads, or a layout where a sticky header permanently covers the target). Also escalate when the same test passes headed but fails headless repeatedly, indicating a genuine rendering or timing difference. Verify any fix by repeating the test many times (npx playwright test my-test.spec.ts --repeat-each=20) and confirming zero failures.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.