In Playwright, what do button, modifiers, position, clickCount and delay change about locator.click()?
answer
- Left button unless you say otherwise
- Modifiers are exclusive, then restored
- Offsets start at the padding box
- clickCount becomes the event detail
- delay sits inside the click
basics
~20 sThey reshape one synthetic mouse gesture: which button is pressed, which modifier keys are held, where inside the element's padding box the pointer lands, how many clicks are sent, and the pause between mousedown and mouseup.
solid answer
~40 s`locator.click()` moves Playwright's virtual pointer onto the element and issues a real press-and-release, and the options tune that gesture. `button` picks `left` (the default), `right` or `middle`, so there is no separate right-click verb. `modifiers` takes `Alt`, `Control`, `ControlOrMeta`, `Meta` or `Shift`, and its contract is exclusive: exactly those keys are held for the click, and whatever was pressed before is restored afterwards. `position` is an `{ x, y }` offset from the top-left of the element's **padding box**, useful when only a handle on a card is the real hit target; without it Playwright picks some visible point. `clickCount` defaults to `1` and becomes the event's `detail`, so `clickCount: 2` is what `dblclick()` sends. `delay` is the wait between `mousedown` and `mouseup`, defaulting to `0`, and it also spaces successive clicks.
code
typescript · 12 lines// Issue board: context menu, modifier multi-select, and a positioned click.
const card = page.getByRole('listitem', { name: 'Fix login redirect' });
const other = page.getByRole('listitem', { name: 'Board drag drops nothing' });
await card.click({ button: 'right' });
await page.getByRole('menuitem', { name: 'Move to' }).click();
await card.click({ modifiers: ['ControlOrMeta'] });
await other.click({ modifiers: ['ControlOrMeta'] });
// Land on the drag handle in the card's top-left corner, not its centre.
await card.click({ position: { x: 8, y: 8 }, delay: 120 });go deeper
Know that click takes options and that right-clicking is a click with button set to right. Reach for position only when the default point genuinely lands on the wrong part of the element.
Explain the exclusive contract of modifiers, that position is an offset from the padding box top-left, and that delay is the gap between mousedown and mouseup rather than a wait around the click.
Show when tuning is a smell. A click that works only with a hand-picked position usually means the hit target is wrong or the layout shifts, and that is worth a bug report rather than a magic number.
Decide how much pointer detail belongs in tests at all. Options that encode pixel offsets couple a suite to layout, so set a team line on where they are allowed and what has to be asserted instead.
## What a click actually is `locator.click()` does not call `element.click()` in the page. It resolves the locator to one element, moves Playwright's **virtual mouse pointer** to a point on that element, and issues a real press-and-release, so the page observes `mousemove`, `mousedown`, `mouseup` and `click` in order, with coordinates, a button, and whatever modifiers are held. Everything the options do is a change to that gesture, which is why they read like a description of a hand on a mouse rather than like a DOM API. ## button -- three buttons, one verb `button` takes `'left'`, `'right'` or `'middle'` and defaults to `'left'`. There is no separate right-click method: opening an issue card's context menu is `card.click({ button: 'right' })`. `'middle'` is what you use for the paste-on-middle-click behaviour on Linux or for a page that binds middle-click to "open in background". ## modifiers -- exclusive, then restored `modifiers` takes an array drawn from `'Alt'`, `'Control'`, `'ControlOrMeta'`, `'Meta'` and `'Shift'`, and its contract is the part candidates get wrong. It is **exclusive, not additive**: Playwright ensures that exactly the listed modifiers are pressed for the duration of the click, releasing anything pressed that is not in the array and pressing anything missing, and then restores the modifier state it found. So if `Alt` was already held from an earlier `page.keyboard.down('Alt')`, then `click({ modifiers: ['Shift'] })` clicks with `Shift` only, and `Alt` is held again afterwards. Omitting the option is what makes a click inherit the currently pressed modifiers -- that is the documented meaning of leaving it unspecified. `ControlOrMeta` resolves to `Control` on Windows and Linux and to `Meta` on macOS, so a multi-select on the board works on every runner from one line. ## position -- an offset from the padding box `position` is an `{ x, y }` pair measured from the **top-left corner of the element's padding box**, not from its centre and not from the viewport. Without it, Playwright picks some visible point of the element. You reach for it when only part of the element is the real hit target: an issue card whose drag handle is an eight-pixel strip in the corner, or a wide row where the left edge toggles selection and the rest opens the detail view. ## clickCount and delay -- the timing of the gesture `clickCount` defaults to `1` and becomes the event's `UIEvent.detail`. Setting it to `2` sends the same thing `dblclick()` sends -- the page sees two `click` events and one `dblclick` -- and setting it to `3` is how you triple-click to select a paragraph in the composer. Prefer the named `dblclick()` verb when you mean a double click; `clickCount` earns its keep past two. `delay` is the pause **inside** the click, between `mousedown` and `mouseup`, defaulting to `0`. It is not a wait before the click and not a wait after it. When `clickCount` is above one, the same delay also separates the successive clicks. Pages that distinguish a tap from a press-and-hold, or that start a drag after a hold threshold, need a non-zero delay to be driven honestly. | option | values | default | changes | |---|---|---|---| | `button` | `left`, `right`, `middle` | `left` | which button is pressed | | `modifiers` | `Alt`, `Control`, `ControlOrMeta`, `Meta`, `Shift` | current state | which keys are held, exclusively | | `position` | `{ x, y }` | some visible point | where in the padding box the pointer lands | | `clickCount` | number | `1` | the `detail` value and how many clicks are sent | | `delay` | milliseconds | `0` | the gap between `mousedown` and `mouseup` | | `steps` | number | `1` | how many interpolated `mousemove` events precede the click | ## When tuning is a smell The options exist to describe a gesture a user could really make, and that is the line to hold in review: - a `position` chosen because the default point works fine is noise, and it couples the test to a layout that will move - a `position` that is the *only* way the click lands is usually a finding about the product -- an overlay, a hit target smaller than it looks, a card whose height shifts on hover -- and deserves a bug rather than a magic offset - a `delay` added to "make it less flaky" is almost always covering for something else; the delay is a property of the gesture, not a retry budget - `modifiers` on every click in a helper is a sign the helper is doing two jobs In the issue tracker this shows up concretely: right-click for the card menu is genuine, `ControlOrMeta` multi-select is genuine, and a hand-picked `{ x: 8, y: 8 }` on the drag handle is genuine only while the handle really is in that corner.
- Alt is already held from an earlier page.keyboard.down() call. What does click({ modifiers: ['Shift'] }) hold?Only `Shift`. The option is exclusive: Playwright releases anything pressed that is not in the array, presses what is missing, performs the click, then restores the modifier state it found. Omitting the option entirely is what makes a click inherit whatever modifiers are currently pressed.
- Is click({ clickCount: 2 }) the same thing as dblclick()?At the mouse level, effectively yes -- `dblclick()` is a click with `clickCount: 2`, so the page sees two `click` events and one `dblclick`. Prefer the named verb when you mean a double click; `clickCount` earns its keep at three or more, such as a triple-click to select a paragraph.
- A click only works when you pass a hand-picked position. What should you conclude?Usually that the product has a problem worth reporting rather than encoding: an overlay covering part of the element, a hit target smaller than it looks, or a card whose height shifts on hover. A magic offset makes the test pass and couples it to a layout that will move.
saying these in an interview costs you the question
- Thinks modifiers stay held for later actions
- Believes position is measured from the element's centre
- Assumes right-click needs a separate rightClick method
- Treats delay as a wait before the click starts
- Adds a position offset the default point did not need