solvedfeed

Fix Playwright "strict mode violation" and "element is not attached to the DOM" flakes

The problem

Two related failures in the same suite. New rows in a list broke:

strict mode violation: locator('button') resolved to 3 elements

And list re-renders produced intermittent element is not attached to the DOM — the row was replaced between locating the button and clicking it.

What didn't work

  • page.$$('button')[0].click() — bypasses auto-waiting entirely and races the render.
  • Bumping timeouts to 30s — the flake isn't slowness; the element is being REPLACED mid-action, so no timeout fixes it.
  • Sprinkling .first() everywhere — makes the failure disappear while silently clicking the wrong copy of the button.

The fix

import { test, expect } from '@playwright/test';

test('deletes the right row', async ({ page }) => {
  await page.goto('/rows');

  // scope by role + name: exactly one element, no positional guessing
  const row = page.getByRole('row', { name: /invoice-1042/ });
  await row.getByRole('button', { name: 'Delete' }).click();

  await expect(row).toBeHidden(); // auto-retries, actionability-aware
});

Two rules that kill both errors: never a bare tag/class locator when a role+name locator exists, and assert on the outcome (toBeHidden, toHaveText) rather than sleeping between steps.

Why it works

Role-and-name locators resolve to one element or fail loudly, which turns the strict-mode crash into a useful signal when the DOM changes; and because locators are re-resolved at action time, Playwright's actionability checks absorb the detached-element race instead of erroring on a stale node.

Continue reading — $1/month

The full fix, verbatim, plus every other solution in the feed.

raw markdown twin · ← back to feed