JS Sketchbooksee JavaScript think ✏️
← back to phase 11

11.5 — Actions

Locators find; actions DO. And the deep fact about Playwright’s actions is what happens before each one: a four-point checklist — attached, visible, stable, enabled — retried until the element is genuinely ready for a human-grade interaction. 7.8 taught you why that gap exists; today you watch the tool close it.

watch it happen
await page.getByLabel("Email").fill("ada@shop.com");
await page.getByRole("button", { name: "Log in" })
  .click();
await page.getByLabel("Qty").press("ArrowUp");
await page.getByLabel("Size").selectOption("L");
await page.getByLabel("Accept terms").check();
await page.getByText("Menu").hover();

// before EVERY action, the checklist:
//   attached?  (in the DOM — 7.1)
//   visible?   (rendered — 7.8)
//   stable?    (not mid-animation)
//   enabled?   (not disabled)
// …retried until all tick, or timeout

Each action line is one command across 11.1’s bridge — and on the browser side it becomes REAL input events, not JavaScript fakery. A Playwright click is a trusted click: the same event sequence a physical mouse produces.

one .click() — what actually happens1. hit-test the point2. hover + focus3. mousedown4. mouseup → click eventan action = a command across 11.1’s bridge that becomes REAL input events inthe browser — not JavaScript fakery
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

The checklist is per-action-appropriate: click additionally checks the element isn’t covered by something else (it must actually receive events at the click point — a cookie banner on top blocks it, exactly as it blocks a human); fill checks the target is editable. The four you learned are the core; the variations all serve the same principle.

Escape hatches exist and are honestly labeled: click({ force: true }) skips the checks, and dispatchEvent fires synthetic events. Treat both as confessions — “my test clicks something a user couldn’t” — occasionally right (a deliberately hidden but functional control), usually a locator or app bug wearing a workaround.

Timing precision: the checklist runs against the element the locator resolves at action time, and re-resolves during retries — so even if the page re-renders and the node is replaced (frameworks do this constantly), the action follows the DESCRIPTION, not a dead reference. 11.4’s locator-not-element design pays exactly here.

Job note: “element is not stable” failures usually mean CSS animations — real suites often disable animations globally for tests (a config/use option) both for speed and stability. And “not visible” with a correct locator often means you found a SECOND, hidden copy of the element (mobile + desktop nav rendering twice) — strict mode’s cousin; narrow the locator’s scope (11.4’s chaining).

your turn

⌨️ build the actionability gate

Model the checklist that runs before every real click: given an element’s state, either clear it for action or name EXACTLY which check blocked it — the same name the real TimeoutError would carry.

requirements:

  • Write canAct(el): check attached, visible, stable, enabled IN THAT ORDER; return the FIRST failing check’s name, or "actionable" when all four hold (early returns keep it clean).
  • Write click(el): consult the gate — print click performed when actionable, otherwise blocked: NAME.
  • Run it on a ready button ({ attached: true, visible: true, stable: true, enabled: true }), then on a hidden one (same but visible: false), then on a disabled one (visible but enabled: false).

when you press RUN, the console must show exactly:

click performed
blocked: visible
blocked: enabled

✏️ Quick check 1

Name the four actionability checks, in order.

✏️ Quick check 2

A button exists in the DOM but has display:none. Does .click() eventually click it, or time out?

✏️ Quick check 3

You want the field to contain exactly "mug" regardless of what was there before. Which verb — fill or press?

teach it back

🗣️ Now teach it back

Explain to a friend what happens between “await button.click()” and the click landing: the bridge, the trusted events, the four-point checklist (with each check’s Phase-7 root), and what the error says when a check never passes.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Actions = bridge commands that become REAL trusted input events (full mouse/keyboard rituals — 7.4 from the sender’s side). Verbs map to user intentions: fill (clear+set), press, selectOption, check (idempotent), hover, dragTo.
Before EVERY action: the actionability checklist — attached (7.1) → visible (7.8) → stable → enabled — retried on the freshly-resolved element until all tick or timeout.
Failures NAME the check (“element is not visible” = 7.8’s vocabulary). force: true skips checks — treat as a confession. The waiting lives inside the verbs; 11.6 gives assertions the same patience.