Q30 of 48 · Cypress

How do you test drag-and-drop interactions in Cypress?

CypressMidcypressdrag-and-dropeventsmid

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 mousedownmousemovemouseup. 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');
});

// WHAT INTERVIEWERS LOOK FOR

Distinguishing HTML5 drag from pointer-based DnD libraries and naming the events to fire for each. Bonus for noting that fine-grained DnD belongs in component tests.

// COMMON PITFALL

Trying to call `.click().drag()` and being surprised it doesn't work — Cypress has no native drag command.