Skip to main content

How to Test Drag-and-Drop and Sortable Lists

Short answer

Drag-and-drop UIs fail when library-specific handlers ignore synthetic events, order is not persisted, or permissions differ by column—not when pixels move. Prefer probe Assert on persisted order after dragTo or library test hooks; fall back to Playwright ai.act for complex DnD; never rely on DOM order alone without save.

Part of Testing Guides by UI patterns.

Who this is for

Teams shipping Kanban boards, sortable tables, file upload dropzones, tree reordering, or dashboard tile layouts. Typical stacks: @dnd-kit, react-beautiful-dnd, SortableJS, HTML5 DnD, custom pointer handlers.

Why testing drag-and-drop matters

Order and placement drive workflow and authorization:

  • Wrong workflow state — ticket moves to "Done" without review; SLA reports skew.
  • Permission bypass — user drags item into admin-only column; UI animates but server rejects—or worse, accepts.
  • Lost ordering — refresh reverts sort; users reorder daily and lose trust.
  • Mobile gaps — touch long-press paths untested while desktop dragTo passes.
  • Accessibility — keyboard reorder buttons missing; only pointer paths exist in prod.

Assert persisted order via probe after explicit save or debounced persist—not intermediate animation frames.

Complexity map

ScenarioEdge caseWhy tests breakApproach
HTML5 DnDdragTo insufficientNo drop eventLibrary-specific dataTransfer
dnd-kitPointer sensor onlyHeadless misspage.mouse sequence or test hook
Cross-columnDifferent droppable idsDrop rejected silentlyProbe column_id on item
Sortable tableVirtual rowsSource not in DOMScroll into view first
Dropzone uploadOverlay interceptsClick hits wrong layersetInputFiles bypass for upload
Cancel dragEscape mid-dragPartial stateProbe unchanged order
Optimistic UIReverts on 409Test passes then failsPoll probe after save
Nested listsParent vs child dropWrong containerProbe parent_id
TouchLong press delayDesktop-only specTouchscreen project or API reorder
PermissionsRead-only columnUI allows dragProbe 403 on persist
Multi-select dragBatch movePartial moveProbe all ids moved
Auto-scrollDrag near edgeFlaky coordinatesFixed container height in test

Playwright dragTo baseline

const source = page.getByTestId('card-101');
const target = page.getByTestId('column-done');
await source.scrollIntoViewIfNeeded();
await source.dragTo(target);
await page.getByRole('button', { name: 'Save board' }).click();
const order = await request.get(`/api/test/board-order?runId=${runId}`).then(r => r.json());
expect(order.column.done).toEqual(['101', '102', '103']);

Always probe order—DOM order before save is not proof.

Library-specific: dnd-kit / pointer sensors

When dragTo fails, use stepped mouse events:

const box = await source.boundingBox();
const targetBox = await target.boundingBox();
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(targetBox.x + 10, targetBox.y + 10, { steps: 10 });
await page.mouse.up();

Some teams expose test-only reorder API in Arrange/Act:

await request.post('/api/test/reorder', { data: { runId, itemId: '101', column: 'done', index: 0 } });
await page.reload();
// Assert UI reflects probe order

Dropzone file upload

For file dropzones, setInputFiles on hidden input is more reliable than simulating file drag:

await page.setInputFiles('input[type=file]', 'fixtures/report.pdf');
const probe = await request.get(`/api/test/uploads?runId=${runId}`);
expect((await probe.json()).files).toContainEqual(expect.objectContaining({ name: 'report.pdf' }));

See file uploads guide for MIME and size validation overlap.

Permission negative path

await source.dragTo(page.getByTestId('column-admin-only'));
await expect(page.getByRole('alert')).toContainText(/permission/i);
const probe = await request.get(`/api/test/item/101`);
expect((await probe.json()).column).toBe('in-progress'); // unchanged

CI checklist

  1. Seed list with stable string ids (not array index)
  2. Scroll virtualized sources into view before drag
  3. Probe persisted order after save or debounced PATCH
  4. One negative permission spec per restricted column
  5. Touch project or API reorder fallback for mobile-critical apps
  6. Avoid coordinate-only tests without probe

Anti-patterns

Anti-patternWhy it failsBetter approach
Assert DOM order onlyOptimistic UI liesProbe after save
Single dragTo attemptLibrary ignores eventMouse steps or test hook
Pixel-perfect screenshotsTheme changes flakeOrder probe
Skip permission columnsSecurity bugNegative probe
Drag without scroll in virtual listSource not foundscrollIntoViewIfNeeded
Test every permutationn! explosionCritical paths + probe

Example scenario

Situation: Recruiter drags candidate card from Interview to Hired column.

Expected outcome: Card persists in Hired; pipeline stage updated; audit log entry created.

Why UI-only automation breaks: Card animates to Hired but probe still shows stage=interview—reports wrong.

  1. Arrange: Seed candidate id=c-55 at stage interview for runId.
  2. Act: dragTo Hired column; confirm dialog if present.
  3. Assert: Probe stage=hired; board-order includes c-55 in hired; optional audit probe for stage_change.

TestChimp workflow: Track kanban_column transitions in prod; expand tests when new columns ship via /testchimp evolve.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references

Frequently asked questions

Playwright dragTo vs ai.act for drag-and-drop?

Try locator.dragTo(target) first with scrollIntoViewIfNeeded. For complex libraries, use stepped mouse events, library test hooks, or ai.act("move card X to Done column") then probe persisted order.

How do I assert order without flaky DOM checks?

After save or debounced PATCH, GET a test probe route returning ordered ids per column. DOM order before network completes is unreliable.

How do I test drag into permission-restricted columns?

Attempt drag, assert UI error if shown, then probe item unchanged in source column. Never trust animation alone.

File dropzone vs sortable list?

Dropzones often accept setInputFiles on hidden input reliably. Sortable lists need drag simulation or test reorder API.

How do virtualized lists affect drag tests?

Scroll source row into view before drag; destination column may also need scroll. Probe order regardless of visible DOM rows.

Should mobile touch drag be a separate project?

If mobile is primary, enable Playwright touch and long-press paths—or cover mobile via API reorder plus one manual ExploreChimp session converted to SmartTest.

How does TestChimp help Kanban maintenance?

Record complex board flows in manual session, convert to SmartTests with stable data-id locators, link // @Scenario: to pipeline requirements in markdown plans.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo