1. Diagnose Timeout Triggers: Network vs. DOM vs. Configuration #
Before adjusting thresholds, isolate the failure vector. Auto-wait timeouts typically stem from delayed DOM hydration, unhandled network requests, or misaligned global values. Use Playwright’s trace viewer and page.on('console') listeners to verify element detachment or overlay interference. Cross-reference network waterfall data with DOM readiness states. This diagnostic step is critical when investigating broader Root Causes of JavaScript Test Flakiness.
2. Optimize Global & Per-Action Timeout Thresholds #
Global timeouts apply uniformly, masking localized bottlenecks. Scope thresholds to specific actions using locator.click({ timeout: 5000 }) or page.waitForSelector(). Align values with your application’s SLA. Set navigation timeouts higher for heavy SPAs, but keep interaction timeouts tight to catch regressions early. Avoid blanket increases that degrade CI feedback loops.
3. Implement Custom Wait Strategies for Async State #
When auto-waiting fails due to framework-specific state hydration, replace implicit waits with explicit, condition-based polling. Use expect(locator).toBeVisible() or await page.waitForFunction() to assert state completion. This approach eliminates race conditions without compromising Playwright’s built-in actionability guarantees.
Configuration & Code Examples #
// playwright.config.ts - Global config override
export default defineConfig({
use: {
actionTimeout: 5000,
navigationTimeout: 10000,
},
});
// Per-action scoped timeout
test('submit form', async ({ page }) => {
await page.locator('#submit-btn').click({ timeout: 3000 });
});
// Explicit state wait (replaces auto-wait)
await page.waitForFunction(() => {
const el = document.querySelector('#dynamic-content');
return el && el.innerText.includes('Loaded');
});
Common Pitfalls #
- Increasing global timeouts to mask underlying async race conditions
- Using deprecated
page.waitForTimeout()for hard delays instead of state-based waits - Overriding actionability checks with
{ force: true }, causing false positives - Failing to account for CSS transitions or layout shifts that temporarily block interaction
FAQ #
Q: Why does Playwright auto-wait timeout even when the element is visible? A: Visibility alone is insufficient. Playwright requires elements to be stable (no pending CSS animations), actionable (not covered by overlays), and attached to the DOM. Use the trace viewer to inspect actionability blockers.
Q: Should I disable auto-waiting for faster test execution? A: No. Disabling auto-waiting removes Playwright’s core reliability engine. Instead, optimize timeout scopes and use explicit waits for framework-specific hydration states.
Q: How do I differentiate between a flaky test and a genuine timeout?
A: Run the test locally with --repeat-each=5 and capture traces. Consistent failures at the same DOM state indicate configuration or app issues; intermittent failures point to race conditions or environment drift.
Reliability Metrics #
| Metric | Target / Tracking Method |
|---|---|
timeout_failure_rate |
Target < 2% of total test runs |
auto_wait_bypass_frequency |
Track explicit wait usage vs. auto-wait reliance |
ci_feedback_latency |
Monitor average test duration post-timeout optimization |
flake_resolution_time |
Measure MTTR for timeout-related failures |