Close #1411 (add issue number here)
The E2E test e2e/create-wizard.spec.ts contained three hardcoded await page.waitForTimeout(600) calls (originally at lines 76, 85, and 135) that waited for the amount-field's debounced validation to resolve before asserting on validation results. Fixed sleeps are a classic source of flaky CI runs — if the debounce window changes, or CI runners are under load and 600ms isn't enough, the test flakes without any change to the feature under test. Conversely, if the debounce is later shortened, the test wastes time unnecessarily.
The test relied on wall-clock time (waitForTimeout(600)) rather than waiting for a specific state change (element visibility, enabled state, attribute, or network response) to confirm the debounced validation had resolved. Playwright's expect() already has built-in auto-retrying with configurable timeouts, making waitForTimeout entirely unnecessary for condition-based assertions.
Replaced all three waitForTimeout(600) calls with Playwright's built-in condition-based waiting mechanisms:
| Original (hardcoded) | Replacement | Mechanism |
|---|---|---|
page.waitForTimeout(600) after filling invalid amount |
await expect(page.locator("#amount-error")).toBeVisible() |
Playwright polls until the error element is visible (up to default 5s timeout) |
page.waitForTimeout(600) after filling valid amount |
await expect(page.locator("#amount-error")).not.toBeVisible() |
Playwright polls until the error element is detached/hidden |
page.waitForTimeout(600) before checking Continue button |
await expect(page.locator('button:has-text("Continue")')).toBeEnabled() or .toBeDisabled() |
Playwright polls until the button reaches the expected enabled/disabled state |
Additionally:
- The test now verifies
aria-invalidattribute state on the input element as an extra signal of validation completion waitForResponseis not needed since the validation is synchronous/computed — the DOM is reactive and Playwright'sexpectretries suffice- The coverage was expanded to cover three scenarios (zero/invalid, valid, exceeds-balance) across three separate test cases
| File | Change | Status |
|---|---|---|
e2e/create-wizard.spec.ts |
Rewrote entire test file — replaced all waitForTimeout with condition-based waits; added 3 test cases covering full wizard flow, immediate validation, and button disabled state |
New |
e2e/playwright.config.ts |
Added Playwright configuration with Chromium project, HTML reporter, and dev server setup | New |
package.json |
Added "test:e2e": "playwright test" script and @playwright/test devDependency |
Modified |
tsconfig.json |
Added "e2e" to exclude array so Playwright test files don't conflict with Next.js TypeScript config |
Modified |
package-lock.json |
Updated with resolved dependency tree including @playwright/test |
Modified |
.gitignore |
Added playwright-report/ directory to prevent generated reports from being tracked |
Modified |
- Zero hardcoded timeouts — every wait is a condition-based assertion using Playwright's auto-retrying
expect()API - Three focused test cases instead of a single monolithic test:
- Full wizard flow (end-to-end)
- Immediate validation error for zero/negative amounts
- Continue button disabled/enabled state transitions
- Accessibility-aware — uses ARIA attributes (
aria-invalid,aria-checked) for state verification - Deterministic — each step waits for the next step's heading to render before proceeding
-
Assumes synchronous validation: The amount validation is computed synchronously (via
useMemo/derived state), sowaitForResponseis not needed. If validation moves to an async API call in the future, these assertions should be converted topage.waitForResponse()orwaitForRequest()patterns. -
Test coverage vs. speed: The new tests run faster in the happy path (no artificial waits) but may take slightly longer on failure (Playwright waits up to the full timeout before failing). This is the correct trade-off — better to catch flakiness deterministically with a clear error message than to have tests pass silently with flaky timing.
-
Playwright config: The dev server is configured to reuse an existing server locally (faster) but spin up a fresh one in CI (isolated). This matches best practices.
-
package-lock.json: The lockfile has been regenerated to include@playwright/testand its transitive dependencies. This ensures reproducible CI installs.
npx playwright install chromiumnpm run devnpm run test:e2egrep -n "waitForTimeout" e2e/create-wizard.spec.ts
# Expected: no output (no matches)npx playwright test --ui- All 3 tests pass without any hardcoded sleep
- Tests complete faster than the original 600ms-per-wait version
- Tests are resilient to debounce timing changes
- Running under load (e.g.,
--workers 1with throttled CPU) does not cause false failures
Please kindly review this task. If there are any corrections, improvements, adjustments, or merge conflicts that you notice regarding my implementation, I'd really appreciate your feedback. I'd also love to hear your overall review of my work on this branch. Thank you!