Q30 of 48 · Cypress
How do you test drag-and-drop interactions in Cypress?
Short answer
Short answer: Cypress doesn't have a native drag-and-drop command. For HTML5 drag/drop, fire `dragstart`, `drop`, `dragend` events with `cy.trigger`. For pointer-based libraries (react-dnd, dnd-kit), simulate `mousedown`, `mousemove`, `mouseup` with positions. Plugins like `@4tw/cypress-drag-drop` wrap these patterns.
Detail
Drag-and-drop is one of Cypress's known gaps. The right approach depends on which DnD style the app uses:
1. HTML5 native drag/drop (draggable, ondrop). Fire the actual events:
cy.get('[data-test=item-1]').trigger('dragstart', { dataTransfer: new DataTransfer() });
cy.get('[data-test=drop-zone]').trigger('drop', { dataTransfer });
cy.get('[data-test=item-1]').trigger('dragend');
The empty DataTransfer() is a stand-in; some apps inspect the payload, in which case set the data with .setData('text/plain', '...').
2. Pointer-based libraries (react-dnd's HTML5 backend, dnd-kit, draggable.js). These listen for mousedown → mousemove → mouseup. Simulate with positional events:
cy.get('[data-test=item-1]')
.trigger('mousedown', { which: 1, pageX: 100, pageY: 100 });
cy.get('[data-test=drop-zone]')
.trigger('mousemove', { which: 1, pageX: 400, pageY: 200 })
.trigger('mouseup', { force: true });
The exact pixel coordinates often matter — the library may track them to decide which drop target activates. Inspect the library's expected events to know what's needed.
3. Plugins. @4tw/cypress-drag-drop provides cy.get(...).drag('selector') that wraps the common patterns. Useful for shallow drag/drop where you don't need fine-grained control.
For touch-based DnD (mobile-style), substitute touchstart/touchmove/touchend events and adapt the pattern.
A pragmatic note: drag-and-drop tests are often best left as a small E2E sanity check while the core sorting/reordering logic is unit-tested at the component level. Don't try to test 50 drag-drop variations in E2E.
// EXAMPLE
kanban.cy.ts
// HTML5 drag/drop
it('moves a card to the Done column', () => {
cy.visit('/board');
const dataTransfer = new DataTransfer();
cy.get('[data-test=card-1]').trigger('dragstart', { dataTransfer });
cy.get('[data-test=column-done]')
.trigger('dragover', { dataTransfer })
.trigger('drop', { dataTransfer });
cy.get('[data-test=card-1]').trigger('dragend');
cy.get('[data-test=column-done]').contains('[data-test=card-1]');
});
// Pointer-based (e.g., dnd-kit)
it('reorders list items via pointer events', () => {
cy.visit('/list');
cy.get('[data-test=item-1]').trigger('mousedown', { button: 0 });
cy.get('[data-test=item-3]')
.trigger('mousemove', { force: true })
.trigger('mouseup', { force: true });
cy.get('[data-test=list] [data-test^=item-]')
.first()
.should('have.attr', 'data-test', 'item-2');
});