These are live on this page. Each one teaches a specific lesson about brittle test patterns. Point Playwright at any of them.
Tier 1hostile
DOM Selector Rotation
Don't hardcode brittle locators; use semantic, resilient patterns.
Breaks when: CSS class selectors like .add-to-cart-btn fail because the class is generated procedurally and rotates every page load.
Try: Use getByRole, getByLabel, or text-based selectors — they survive class rotation.
1-1-dom-selector-rotation
Tier 1hostile
Data Mutation on Load
Don't assert on dynamic values; verify conditions, not exact states.
Breaks when: Exact-match assertions on stock status or price pass ~70% of the time and fail ~30% — flaky by design.
Try: Accept both valid states (In stock / Out of stock) or check ranges instead of exact values.
1-2-data-mutation
Tier 2hostile
Decoy / Duplicate Elements
Be specific; verify before interacting; don't trust first match.
Breaks when: click() on nth(0) hits a decoy element that looks identical but is non-functional.
Try: Use filter({ hasText: ... }) or getByRole with accessible name to target the real element.
1-3-decoy-duplicate-elements
Tier 2subtle
Structural Drift Between Pages
Don't assume patterns transfer; each page needs fresh recon.
Breaks when: Selectors that work on /catalog fail on /checkout because class names, nesting, or element order changed.
Try: Use semantic locators that don't depend on DOM structure or versioned class names.
1-4-structural-drift
Tier 3subtle
Email Scenarios
Async delivery and expiry timers break tests that assume instant results.
Breaks when: Emails arrive after a delay, links expire after a few seconds, and some messages route to spam. Tests that check immediately or use stale links fail.
Try: Wait for delivery indicators, poll until expected state appears, and regenerate links before each test pass.
1-5-email-scenarios
Tier 2hostile
Subscription Trap
A button's label and behavior change after the first click. Naive tests click and check once.
Breaks when: The button transitions through confirm, processing, and done phases. A test that clicks and asserts the original label fails on the second click.
Try: Use a state machine in your test: assert the label changes through each expected phase instead of assuming it stays the same.
1-6-subscription-trap
Tier 3hostile
File Upload
Upload progress is async and chunked. Tests that expect instant completion miss intermediate states.
Breaks when: The upload emits start, progress (0-100%), processing, and complete events. A test that waits only for 'done' misses timing-sensitive progress assertions.
Try: Use Playwright's waitForEvent or poll for the complete event instead of relying on static timeouts.
1-7-file-upload
Tier 3subtle
Webhook Callback
Async callbacks with retry logic break tests that fire-and-forget.
Breaks when: Webhooks transition pending → delivered/failed/retrying. A test that doesn't wait for the final delivery state passes when the webhook is still pending and flaky when it retries.
Try: Poll the delivery log until status reaches delivered or the retry budget is exhausted.
1-8-webhook-callback
Tier 2environmental
Rate Limit
Rate-limited endpoints reject rapid requests. Tests that hammer the same route without spacing fail intermittently.
Breaks when: After N requests in a sliding window, subsequent requests return 429. A test that fires concurrent requests without coordination hits the limit.
Try: Stagger requests in your test, or use the reset header to wait for the window to clear before continuing.
1-9-rate-limit
Tier 2environmental
Timeout / Slow Load
Async content with variable delay breaks tests that assume fixed timings.
Breaks when: Content appears after a configurable delay (or random delay). A test with a hardcoded wait may timeout before content loads, or pass on the first run and fail on CI.
Try: Use Playwright's waitForSelector with a generous timeout instead of fixed sleep(). Enable auto-waiting assertions.
1-10-timeout
Tier 3hostile
State Desync
Client state may diverge from server truth. Checking only the rendered DOM misses the real data.
Breaks when: Items show a stale price, wrong stock badge, or outdated count on the client while the server holds correct data. A test that checks only the displayed value catches the desync intermittently.
Try: Cross-reference visible values against hidden data-testid attributes that carry the server ground truth.
1-11-state-desync
Tier 2environmental
Permission Error
Role-based access gates break tests that assume all users are admins.
Breaks when: A test using admin-panel actions gets a permission denied response when the current role is viewer or operator.
Try: Set the role explicitly before testing restricted actions, or test across all roles to confirm the access control works.
1-12-permission-error
Tier 2hostile
Modal Overlay
Modals that appear after a delay or can't be dismissed break naive interaction tests.
Breaks when: A modal appears after N ms. A test that clicks the trigger and immediately looks for modal content finds nothing because the overlay hasn't rendered yet.
Try: Wait for the modal to be visible (waitForSelector or locator.waitFor) before interacting with its contents.
1-13-modal-overlay
Tier 2hostile
Focus Trap
Trapped focus loops inside a dialog. Tests that tab-out or expect focus to leave the dialog fail.
Breaks when: Tabbing past the last focusable element inside a dialog wraps back to the first. A test that presses Tab and expects focus on a page-level element never escapes.
Try: Test that focus cycles correctly inside the trap. Use locator.press('Tab') and assert the active element stays within the dialog.
1-14-focus-trap
Tier 3subtle
Scroll Stealth
Lazy-loaded content below the fold doesn't exist until the user scrolls.
Breaks when: Sections below the viewport are empty divs. A test that scrolls down and immediately checks for content sees nothing because the reveal is tied to scroll position.
Try: Scroll the element into view then wait for its content to render. Use scrollIntoViewIfNeeded or Playwright's locator.scrollIntoViewIfNeeded.
1-15-scroll-stealth
Tier 2subtle
Tab Visibility
Background tab behavior differs from foreground. Tests that assume the tab stays visible miss hidden-tab branches.
Breaks when: When the tab is hidden, polling pauses or throttles. A test that switches tabs (or the system does) and expects continuous updates sees stale data.
Try: Override document.hidden via page.addInitScript for tests that need to simulate background behavior, or test explicitly in both visibility states.
1-16-tab-visibility
Tier 3environmental
Network Jitter
Variable network latency causes test race conditions that are hard to reproduce.
Breaks when: A request-response cycle takes between minDelayMs and maxDelayMs. A test with a fixed timeout succeeds on fast networks and fails on slower CI runners.
Try: Use Playwright's page.route to simulate or record latency, or rely on auto-waiting assertions that give the full timeout budget.
1-17-network-jitter
Tier 3environmental
Clock Skew
Client and server time differences break tests that compare timestamps locally.
Breaks when: Client time is offset from server time by a configurable amount. A test that compares new Date() against a server-returned timestamp sees a mismatch.
Try: Use the server's reported time as the source of truth. Avoid Date.now() in assertions that cross the client-server boundary.
1-18-clock-skew
Tier 4subtle
Memory Leak
Accumulated DOM nodes degrade performance and eventually crash the page under test.
Breaks when: A growing list of LeakItem elements is appended every tick. A test that navigates away and back sees accumulated nodes from the previous visit.
Try: Clear or reset the list between test cases. Use page.evaluate to measure DOM node count before and after each scenario.
1-19-memory-leak