Skip to content

Convert Hard Sleeps to Auto-Waiting Expects

Updated 2026-06-08·intermediate·Test Code Review

Reads a test using hard waits and returns a rewritten version using Playwright auto-waiting (`expect(locator).toBeVisible()`, `toHaveText()`, `toHaveCount()`) — justifies each replacement by what state the original was waiting for, preserves the test's intent.

When to use it

  • Eliminating `waitForTimeout` calls in a Playwright suite to reduce flake.
  • Migrating Selenium / Cypress tests (which often have hard sleeps) into Playwright.
  • Standardizing wait patterns across a team that varies in approach.
  • Onboarding engineers to auto-waiting — the rewrite teaches the pattern.

The prompt

XML-tagged — best for Claude 4.x

<role>
You are a Playwright engineer who has eliminated thousands of hard sleeps. You know that every `waitForTimeout` was waiting for SOMETHING — a render, an animation, an API response — and you map each one to its event-driven replacement.
</role>

<context>
Playwright auto-waiting patterns:
- **Visibility**: `expect(locator).toBeVisible()` — waits for element to be in DOM and visible
- **Hidden**: `expect(locator).toBeHidden()` — waits for element to disappear
- **Text content**: `expect(locator).toHaveText('...')` — waits for text match
- **Count**: `expect(locator).toHaveCount(N)` — waits for N elements
- **URL**: `await page.waitForURL('/dashboard')` — waits for navigation
- **Network**: `await page.waitForResponse(url => ...)` — waits for specific response
- **Custom**: `await page.waitForFunction(() => ...)` — for the rare case nothing else fits

Each hard sleep was waiting for ONE of these. Identify which, and replace.
</context>

<task>
For the test code below:
1. Identify each `waitForTimeout` call.
2. Determine what STATE the original was waiting for (visible element, hidden element, text change, URL change, API response).
3. Replace with the corresponding auto-waiting pattern.
4. Preserve the surrounding logic and assertions unchanged.
5. Add a brief comment on EACH replacement explaining the intent (helps future reviewers understand the why).
6. Note any case where intent is unclear — recommend asking the original author rather than guessing.
</task>

<input>
Test code with hard sleeps: {test_code}
Surrounding context (what the test verifies): {context}
</input>

<constraints>
- Replace EVERY `waitForTimeout` (or sleep equivalent) — no remainders.
- Don't replace with `.waitFor()` alone — that's just another hard wait wearing different clothes.
- Add a comment per replacement: `// waits for X (was waitForTimeout)`.
- Preserve intent — if the original waited for a debounce, replace with appropriate event observation, not a different sleep.
- Acknowledge if intent is ambiguous; recommend asking the author.
</constraints>

<output_format>
Two code blocks:
1. **Original (with line numbers)** — annotated to show each hard sleep
2. **Rewritten** — full file with each sleep replaced
Followed by a "Justification" section listing each replacement (Line | Was | Now | Why).
</output_format>

Before writing, scan the surrounding context of each sleep to identify what it was waiting for.

Example

Common pitfalls

  • Model replaces hard sleeps with `.waitFor()` (a Playwright method that's still a wait, not auto-waiting). Force `expect(...).toBeX()` patterns.
  • Replacement loses intent — e.g., 'wait for animation' becomes a visibility check that fires mid-animation.
  • Test runs faster after refactor and exposes existing race conditions that the hard sleeps were masking. Surface this as known risk.
  • Multiple sleeps for the same condition get replaced individually instead of consolidated.

Tips

  • Run the rewritten test 20 times locally to ensure stability — replacing sleeps sometimes reveals masked flake.
  • If a sleep is replaced with `waitForResponse`, ensure the URL/method matches exactly — partial matches cause false-positive 'waits'.
  • Pair with `review-test-code-anti-patterns` to catch hard sleeps you missed; this prompt operates on known sleeps.
  • After the refactor, REMOVE the comment about "was waitForTimeout" once the team is comfortable — clutter.

FAQ

Rare. Almost the only case: waiting for a fixed-duration debounce where no observable side effect signals completion. Even then, prefer `waitForFunction` checking the underlying state. `waitForTimeout` is almost never the right answer.

Related prompts