Your Playwright dragTo() leaves an issue-board card in place. How do you diagnose it and hand-build the drag?
answer
- Four steps, only one move
- The board never sees dragover
- Repeat the hover on the target
- Aim with source and target positions
- More interpolated moves along the path
basics
~20 sdragTo moves to the source, presses, moves to the target and releases. A board built on HTML5 drag and drop needs at least two mouse moves before dragover fires, so rebuild the gesture and hover the target twice.
solid answer
~40 s`source.dragTo(target)` performs four steps: move to the source element, `mousedown`, move to the target, `mouseup`. That is enough for a pointer-driven board, but a widget built on the HTML5 drag-and-drop API reorders in a `drop` handler, and browsers need **at least two mouse moves** before `dragover` fires reliably. One `dragTo()` issues a single move, so the drop zone never activates and the card is picked up and dropped with nothing accepting it. Drive the gesture yourself instead: `source.hover()`, `page.mouse.down()`, `target.hover()`, `target.hover()` again, then `page.mouse.up()`. If the drop only registers on a narrow strip, aim with `sourcePosition` and `targetPosition`; if the widget samples the path, raise `steps` so more interpolated `mousemove` events are sent.
code
typescript · 11 lines// Issue-board drag that an HTML5 drop zone will actually accept.
const card = page.getByRole('listitem', { name: 'Fix login redirect' });
const done = page.getByRole('region', { name: 'Done' });
await card.hover();
await page.mouse.down();
await done.hover();
await done.hover(); // second move -- this is what raises dragover
await page.mouse.up();
await expect(done.getByRole('listitem', { name: 'Fix login redirect' })).toBeVisible();go deeper
Know that dragTo drags one locator onto another. If the card does not move, say so and investigate rather than adding a sleep and hoping the next run is kinder.
Explain the four steps dragTo performs and why one move is not enough for a board that listens for dragover. Name the manual sequence and say exactly where the second hover goes.
Diagnose from the events the widget needs: read its listeners, confirm whether dragover ever fires, then choose between a positioned dragTo, more interpolated steps, and the hand-built sequence.
Drag is the most expensive gesture a suite owns. Decide how many drag paths are worth covering end to end and where a direct state change is the honest, cheaper substitute for a simulated one.
## What dragTo actually performs `source.dragTo(target)` is a pointer gesture, not a drag-and-drop API call. Playwright performs four steps: 1. move the virtual pointer to the source element 2. `mousedown` 3. move the pointer to the target element or the given target position 4. `mouseup` That is the entire sequence. Playwright does not fabricate `dragstart`, `dragover` or `drop` events -- it moves a mouse and presses a button, and it is the browser and the page that decide what that means. ## Why an HTML5 board ignores it An issue board built on the HTML5 drag-and-drop API does not reorder on `mouseup`. It reorders in a `drop` handler, and the browser only raises `drop` on an element that has been accepting the drag through `dragover`. That is where the failure lives: **browsers need at least two mouse moves before `dragover` fires reliably across engines.** A single `dragTo()` issues one move to the destination, the drop zone never activates, and the card is picked up and dropped with the column never having seen a valid target. The test passes its actions and fails its assertion, or silently leaves the card where it was. This is also why the failure looks browser-dependent and gets misfiled as flakiness. The gesture is genuinely incomplete; different engines are just differently forgiving about it. ## The hand-built drag The fix is to drive the lower-level verbs yourself and issue the second move explicitly: 1. `await source.hover()` -- pointer arrives over the card 2. `await page.mouse.down()` -- press and hold 3. `await target.hover()` -- first move onto the drop zone 4. `await target.hover()` -- second move, and this is the one that raises `dragover` 5. `await page.mouse.up()` -- release, and the board's `drop` handler runs The repeated `hover()` is the entire trick, and a second `page.mouse.move(x, y)` does the same job. What does **not** work is a fixed wait before the release: the browser is waiting for an event, not for time, so a sleep sends nothing and the run just fails more slowly. ## Aiming and shaping the drag When the gesture is right but the geometry is wrong, `dragTo` has options for it: | option | what it controls | |---|---| | `sourcePosition` | the grab point, as an `{ x, y }` offset from the source element's padding box | | `targetPosition` | the drop point, as an `{ x, y }` offset from the target element's padding box | | `steps` | how many interpolated `mousemove` events are sent between the press and the release | `sourcePosition` matters when only a handle is draggable rather than the whole card. `targetPosition` matters when a column accepts a drop only on a narrow insertion strip, or when dropping in the dead centre of a tall column is ambiguous between two positions. `steps` matters when the widget samples the pointer path -- a board that renders a live insertion indicator as you move needs intermediate positions to compute one, and a single jump to the destination gives it nothing to work with. ## Diagnosing before rewriting Work from the events the widget needs rather than from the symptom: - read the drop zone's listeners; if it binds `dragover` and `drop`, you are in the HTML5 case and you need the second move - if it binds `pointermove` and `pointerup` instead, plain `dragTo()` is the right verb and the problem is geometry -- reach for the position options - confirm which of the two it is before changing anything, because the two fixes look nothing alike - check whether the card is picked up at all; if the source never receives `mousedown` where you expect, the grab handle is the issue, not the drop - treat a passing drag with a wrong final order as a separate bug: the gesture worked and the app placed the card badly One more distinction worth keeping straight: dragging a card between columns is this pointer gesture, whereas dropping a file or clipboard payload **onto** the page from outside it is a different verb -- `locator.drop()` -- which synthesises `dragenter`, `dragover` and `drop` with a constructed `DataTransfer`. It looks similar and solves a different problem. ## What to take away `dragTo` is a mouse gesture spelled conveniently, so it succeeds exactly when a mouse gesture is what the page listens for. The hand-built version exists because one class of widget needs a shape the convenience method does not produce, and the missing ingredient is always the same: a second mouse move over the target before the button comes up.
- When is the built-in dragTo() enough, and which of its options do you reach for first?It is enough whenever the board reorders on plain pointer events rather than the HTML5 drag API. Reach first for `sourcePosition` and `targetPosition` when the grab handle or the drop strip is a small part of the element, and for `steps` when the widget samples the pointer path between press and release.
- Why is repeating locator.hover() better than inserting a fixed wait before the mouse-up?Because the missing ingredient is a second `mousemove`, not elapsed time. A wait sends no events at all, so `dragover` still never fires and the run just fails more slowly. Repeating the hover, or issuing a second `page.mouse.move()`, produces the move the browser is waiting for.
- How do you tell which kind of drop zone you are dealing with before rewriting the test?Read the drop zone's listeners. If it binds `dragover` and `drop`, you are in the HTML5 case and you need the second move. If it binds `pointermove` and `pointerup`, plain `dragTo()` is the right verb and any failure is geometry, which the position options fix.
saying these in an interview costs you the question
- Adds a fixed sleep instead of a second mouse move
- Thinks dragTo dispatches dragstart and drop directly
- Blames the browser rather than the missing move
- Retries the same dragTo call hoping it sticks
- Assumes any point inside the column is a valid drop