Two loops behind every click
2.1 · Actions ·
locator.click()What Playwright does between your click() call and the mouse event.
First, Playwright finds the element 1. If no element matches, it waits and looks again. If two or more elements match, the click fails at once 5.
// ✗ Two "Buy" buttons matchawait page .getByRole('button', { name: 'Buy' }) .click();
// ✓ Find the one in the cartawait page .getByTestId('cart') .getByRole('button', { name: 'Buy' }) .click();Then Playwright tries to click that element 2. Each try checks the element in this order: stable 3, visible, enabled. Then Playwright scrolls the element into view. It also makes sure that no other element covers the click point.
if (states.includes('stable')) { const stableResult = await this._checkElementIsStable(node); if (stableResult === false) return { missingState: 'stable' }; …}for (const state of states) { // visible, enabled if (state !== 'stable') { … }}If a check fails, Playwright waits and tries the same element again 4. It does not find the element again. It does that only if the app removed the element from the page.
// We progressively wait longer between// retries, up to 500ms.const waitTime = [0, 20, 100, 100, 500];Both loops stop at the action timeout. Then the last line of the call log tells you which check failed.
// ✗ Not needed: the loops already waitawait page.waitForTimeout(1000);
// ✓ Set the deadline for both loopsawait page .getByRole('button', { name: 'Buy' }) .click({ timeout: 5_000 });Runs in:Playwright serverpage (injected script)browser