In Playwright, why does locator.fill() succeed on an input covered by an overlay when locator.click() times out?
answer
- Not every action runs every check
- Typing never hit-tests a point
- Clicking needs a point that lands
- Editable means enabled and not readonly
- A green fill can hide an overlay
basics
~10 sDifferent actions require different checks. Filling waits only for visible, enabled and editable, then sets the value directly. Clicking additionally requires stability and a hit test at the click point, which the overlay fails.
solid answer
~40 sPlaywright's actionability gate is per action, not per element. `locator.fill()` requires the field to be **visible**, **enabled** and **editable** (not `readonly`); it then focuses the control and sets its value, raising an `input` event. It never hit-tests, so nothing painted on top of the field matters. `locator.click()` requires **visible**, **stable**, **receives events** and **enabled**: it scrolls the element into view, computes a point and hit-tests it, and if a modal backdrop or a toast owns that point the check fails and is retried until the timeout. A passing `fill` beside a timing-out `click` is therefore not a contradiction -- it is the checklist difference. It is also a warning: the field a human cannot reach is one your test just typed into, so the passing `fill` may be concealing a real layering bug.
code
typescript · 8 linesimport { test, expect } from '@playwright/test';
test('comment box is reachable, not merely fillable', async ({ page }) => {
const composer = page.getByRole('textbox', { name: 'Add a comment' });
await composer.click({ trial: true, timeout: 3_000 });
await composer.fill('Reproduced on staging with two reviewers.');
await expect(composer).toHaveValue(/Reproduced on staging/);
});go deeper
Remember that filling and clicking do not wait for the same things, so a test that fills a field is not proof that a person could have clicked into it.
Explain the two requirement lists side by side and why the delivery mechanism differs: a value set on a focused control versus a pointer event at a hit-tested point.
Treat the disagreement as a defect report. A field that fills but cannot be clicked is a layering bug the suite is currently certifying as working.
Set the convention for when a test drives the app the way a user does versus the fastest way that works, so the suite's coverage claims stay honest about reachability.
## The gate is per action, not per element Playwright's actionability checks are a per-call requirement list, not a single "is this element usable" verdict. Each action declares which of the five conditions it needs, and the runner retries only those until they pass or the call's timeout expires. So two calls against the same locator can disagree, and that disagreement is information rather than a bug. ## What filling requires `locator.fill()` waits for three conditions: - **Visible** -- the field has a non-empty bounding box and is not `visibility: hidden`. - **Enabled** -- it is not `disabled`, directly or through an ancestor `<fieldset>`. - **Editable** -- it is enabled and not `readonly`; a `readonly` input is enabled but not editable, and filling it fails. It does **not** wait for stability and it does **not** hit-test. Playwright focuses the control and sets its value, raising an `input` event, rather than driving a pointer at a coordinate. Nothing painted on top of the field is in the way of that, because no point on the page is ever consulted. ## What clicking requires `locator.click()` waits for four: - **Visible**, as above. - **Stable** -- an identical bounding box across two consecutive animation frames, so a modal that is still easing open does not get clicked mid-flight. - **Receives events** -- Playwright scrolls the element into view, computes a point, and hit-tests it. The node at that point must be the target or one of its descendants. - **Enabled** -- not `disabled`. The hit test is the one that a covering layer fails. A modal backdrop, a sticky header, a toast, or an invisible full-screen `div` owns the point, so the check fails, the attempt is retried, and eventually the call times out with a log line naming the intruding node. ## The comparison in one table | | `locator.fill()` | `locator.click()` | |---|---|---| | Visible | required | required | | Stable | not required | required | | Receives events | not required | required | | Enabled | required | required | | Editable | required | not required | | Delivery | focus, then set the value | a real pointer event at a computed point | | Effect of an overlay | none | the check fails and retries | ## Why this is a warning, not a convenience A comment composer that a modal backdrop covers is unusable to a human. `locator.fill()` will write into it anyway and the test will go green, which means the suite has just certified a screen the user cannot operate. Three habits keep that from happening: 1. When a test must prove reachability, run the click gate explicitly -- `locator.click({ trial: true })` performs the full set of checks and skips the action, so the overlay becomes a named failure. 2. Prefer driving the app the way a user does. If the flow really is "click into the box, then type", the click is part of the flow and the gate comes for free. 3. Treat a `fill` that passes where a `click` fails as a bug report about layering, not as a way around the failure. ## Neighbouring cases worth knowing - `locator.clear()` has the same requirement list as `fill`: visible, enabled and editable. - `locator.selectOption()` requires visible, stable and enabled, but not the hit test -- so it too works under a covering layer. - `locator.press()`, `locator.focus()` and `locator.dispatchEvent()` require nothing at all, and can drive a control the user could never reach. - A `readonly` field is the mirror image of the overlay case: clicking it passes every check, filling it fails on editable. The general rule to carry into an interview: value-writing calls are gated on the control's own state, and pointer calls are gated on the geometry of the page. When they disagree, the geometry is usually telling you something true.
- What does the editable check add on top of enabled?Editable means enabled **and** not `readonly`. A `readonly` input is enabled -- it can be focused, and it is not `disabled` -- so it sails through the click gate but fails `fill`, which retries and then times out rather than quietly writing a value the user could not have typed.
- How would you prove the overlay is a real bug instead of trusting the fill?Run the same locator through something that hit-tests. `locator.click({ trial: true })` performs the full click gate and skips the action, so it fails exactly when a person could not reach the field. Keeping that line in the test turns an invisible covering layer into a named, reproducible failure.
saying these in an interview costs you the question
- Says Playwright runs identical checks for every action
- Thinks fill fails when an overlay covers the input
- Assumes fill dispatches keystrokes at the element coordinates
- Treats a passing fill as proof the field is usable
- Confuses the editable check with the visible check