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.