Convert Hard Sleeps to Auto-Waiting Expects
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
Refactor Flaky Test to Stable
Takes a flaky test and its failure history, identifies which of the canonical root causes (race, hard sleep, shared state, network dependency, ordering, animation) is responsible, and produces a rewritten test that fixes the specific cause — no blanket retries.
Open →Review Test Code for Anti-Patterns
Reads a test file and returns a categorized list of anti-patterns — hard sleeps, shared mutable state, weak assertions (`toBeTruthy` instead of `toEqual`), missing teardown, mixed setup/assertion concerns — each with line numbers, severity, and a suggested fix.
Open →Generate Playwright Page Object Model
Give the model a page description plus a list of UI elements and it returns a complete Page Object Model in TypeScript using Playwright's auto-waiting locators (getByRole / getByTestId), typed action and assertion methods, and a page-level fixture.
Open →Test Code Quality Checklist
Returns a per-test-file quality checklist with 20-30 items grouped by category (naming / structure / assertions / isolation / performance / maintainability) — each marked PASS/FAIL with one-line evidence from the code.
Open →