A team wants every Playwright form interaction to use pressSequentially for realism. How do you evaluate that?
answer
- The instinct is right, the rule is wrong
- Count what realism actually costs
- Per field, from the page's behaviour
- Name the mechanism in a comment
- A long exception list means wrong layer
basics
~20 sReplace the blanket rule with a criterion. Typing costs one round trip per character and adds a re-render window between each, so reserve it for fields whose own behaviour depends on key events and use fill everywhere else.
solid answer
~50 sThe instinct is sound — masks, mention menus and search-as-you-type only exist under real key events, and a suite of nothing but `locator.fill()` is blind to them — but the blanket rule is the wrong shape. `locator.pressSequentially()` is one protocol round trip per character, so five twenty-five-character fields become roughly a hundred and twenty-five steps, each a window for a re-render or focus steal. That is runtime and flakiness bought on fields that only read `value`. Offer a per-field criterion instead: type when the page binds a key handler, rewrites the value per keystroke, or fires something per character; `fill` otherwise. Make the exception self-documenting with a comment naming the mechanism. Where the exception list keeps growing, that is the signal to move key-handling coverage down to the component layer and let the end-to-end suite prove the integration once.
code
typescript · 13 linesimport { test, expect } from '@playwright/test';
test('filter the board as the user types', async ({ page }) => {
await page.goto('https://tracker.example.com/board');
const title = page.getByLabel('Issue title');
await title.fill('Crash on board drag'); // no key handling: fill
const search = page.getByRole('searchbox', { name: 'Filter issues' });
await search.pressSequentially('cra', { delay: 100 }); // debounced per keystroke
await expect(page.getByRole('listitem')).toHaveCount(3);
});go deeper
Know that typing character by character is slower than filling and is reserved for fields that genuinely react to keys, rather than being the more careful option everywhere.
Be able to quantify the cost: one round trip per character, plus a window between every keystroke where a re-render can interfere with the field.
Turn the proposal into a per-field criterion tied to the page's actual handlers, and make each exception carry a comment naming the mechanism that justifies it.
Own the tradeoff explicitly. Set the cheap default, keep a justified exception list, and treat a growing list as the signal to push key-handling coverage down a layer.
## Take the proposal seriously first The argument for typing everything is not stupid. `locator.pressSequentially()` produces the key events a human produces, and there are real widgets — input masks, mention menus, search-as-you-type, keyboard shortcut handlers — whose behaviour only exists under those events. A suite that uses `locator.fill()` everywhere is blind to that class of bug, and someone who has been burnt by it will reach for the blanket rule. The job is not to say no; it is to replace a blanket rule with a criterion. ## What the blanket rule costs - **Runtime.** Typing is one protocol round trip per character. A form with five fields averaging twenty-five characters goes from five steps to roughly a hundred and twenty-five. Across a suite running on several workers, that is measured in minutes per run and hours per week. - **Flakiness.** Every keystroke is a window in which a re-render, a focus steal, a toast or a late-arriving autocomplete can land between characters. Interleaving more steps with the page strictly increases the number of interleavings that can go wrong. - **False confidence.** Typing does not make a test more truthful about a field that only reads `value`; it makes it slower. Realism you do not need is cost without coverage. - **Debuggability.** A failure mid-string leaves a half-typed field, which reads as a product bug and burns triage time before anyone notices the mechanism. ## The criterion to offer instead Choose per field, from the page's behaviour, not per suite from a preference: 1. **Does the page have a handler bound to a key event on this field?** If not, `fill` is complete and typing adds nothing. 2. **Does the value get rewritten as it is entered** — a mask, a formatter, a character limit that truncates per keystroke? Then type it. 3. **Does something fire per character** — a debounced lookup, a counter, a menu? Then type at least the characters that trigger it, and only those. 4. **Is the answer "yes" across dozens of edge cases?** Then the coverage belongs closer to the component than to the end-to-end suite; prove the integration once here. | Field | Verb | Reason | |---|---|---| | Issue title | `fill` | value read on submit | | Description textarea | `fill` | no key handling | | Mention trigger in a comment | `pressSequentially` (trigger only) | menu opens on `keydown` | | Due-date mask | `pressSequentially` | value rewritten per keystroke | | Board search box | `pressSequentially` with `delay` | debounced per character | ## How to land it as a convention State the default in the team's testing guide — `fill` unless the field's own behaviour requires keys — and make the exception self-documenting with a one-line comment naming the mechanism (`// mask reformats per keystroke`). That comment is what stops the next person from "optimising" it back to `fill` and reintroducing the bug, and equally what stops typing spreading by copy-paste into fields that never needed it. Then measure rather than argue. If the concern is a real class of missed bugs, look for it: have the team name the widgets on the product that are key-driven, and cover exactly those. That list is usually short — a handful of fields on an issue tracker — and converting a handful is a very different proposal from converting everything. ## The tradeoff worth naming out loud Every end-to-end suite trades fidelity against runtime and stability, and the only wrong answer is choosing one axis globally without looking at the other. Blanket typing maximises fidelity at a runtime and flakiness cost paid on every field, most of which cannot benefit. Blanket `fill` minimises cost but leaves key-driven widgets untested. The principled position is a cheap default plus a named, justified exception list — and, where the exception list starts growing, a push to move that coverage down a layer where a keystroke costs microseconds instead of a round trip.
- How would you measure whether the blanket rule is actually costing anything?Convert one representative spec both ways and compare wall-clock time and retry rate over a run of the full suite, not a single execution. The interesting number is not the average but the tail: how often a keystroke run needs a retry that a fill run does not.
- The team says typing catches bugs fill would miss. How do you test that claim?Ask them to name the widgets. Key-driven behaviour is a property of specific controls — masks, mention menus, debounced search — and the list on a given product is usually short. Cover exactly those, which is a very different proposal from converting every field.
- When does key-driven behaviour stop belonging in the end-to-end suite at all?When the exception list keeps growing, or when a widget needs dozens of keystroke permutations to exercise. At that point a component test covers the key handling in microseconds, and the end-to-end test is left proving the integration once: one handle typed, one selection, one saved record.
saying these in an interview costs you the question
- Typing is always more realistic, so always better
- Runtime does not matter because tests run in parallel
- fill leaves framework state stale, so typing is required
- One suite-wide rule is simpler than a per-field judgment
- Adding a delay to every field makes tests more stable
- Key-driven widgets can only be covered end to end