E2E тестирование drag and drop

Библиотека Muuri реализует систему перетаскивания элементов через встроенный механизм dragEnabled. При активации этой опции каждый элемент сетки получает внутренний контроллер перетаскивания, который отслеживает:

  • события указателя (pointer, mouse, touch)
  • текущее положение элемента
  • возможные цели перемещения
  • изменение порядка элементов в сетке

Перетаскивание в Muuri состоит из нескольких этапов:

  1. Начало перетаскивания — захват элемента.
  2. Перемещение — обновление позиции и поиск новой позиции в сетке.
  3. Сортировка — изменение порядка элементов.
  4. Завершение — фиксация позиции.

E2E-тестирование (End-to-End) проверяет весь этот процесс полностью: от пользовательского жеста до итогового состояния DOM.


Почему drag and drop требует E2E-тестирования

Unit-тесты проверяют только отдельные функции:

  • сортировку элементов
  • логику вычисления позиций
  • обработку событий

Однако drag and drop — это пользовательское взаимодействие, которое зависит от:

  • координат указателя
  • событий браузера
  • DOM-обновлений
  • анимации
  • асинхронных операций

Поэтому проверка корректной работы требует E2E-тестов, имитирующих реальное поведение пользователя.

Основные аспекты, которые проверяются:

  • возможность захвата элемента
  • корректность перемещения
  • изменение порядка элементов
  • обновление DOM
  • сохранение состояния после перетаскивания

Инструменты для E2E тестирования drag and drop

Наиболее распространённые инструменты:

Инструмент Особенности
Playwright точная эмуляция pointer-событий
Cypress удобные команды тестирования интерфейса
WebdriverIO работа через WebDriver API
Puppeteer управление браузером через DevTools

На практике чаще всего используются:

  • Playwright — для сложных drag-сценариев
  • Cypress — для тестирования интерфейсов

Подготовка тестовой среды

Минимальная конфигурация Muuri:

const grid = new Muuri('.grid', {
  dragEnabled: true
});

HTML структура:

<div class="grid">
  <div class="item">A</div>
  <div class="item">B</div>
  <div class="item">C</div>
  <div class="item">D</div>
</div>

Каждый элемент должен иметь контейнер:

.grid {
  position: relative;
}

.item {
  width: 100px;
  height: 100px;
}

Основная логика drag-событий

Muuri использует несколько ключевых событий:

Событие Описание
dragStart начало перетаскивания
dragMove движение элемента
dragEnd завершение
move изменение порядка

Пример обработки:

grid.on('dragStart', item => {
  console.log('start', item);
});

grid.on('move', data => {
  console.log('moved', data);
});

grid.on('dragEnd', item => {
  console.log('end', item);
});

E2E-тесты проверяют, что после пользовательского взаимодействия эти события происходят в правильной последовательности.


Базовый E2E-тест drag and drop (Playwright)

Playwright поддерживает команду dragTo.

Пример теста:

import { test, expect } from '@playwright/test';

test('drag item changes order', async ({ page }) => {
  await page.goto('http://localhost:3000');

  const first = page.locator('.item').nth(0);
  const third = page.locator('.item').nth(2);

  await first.dragTo(third);

  const items = await page.locator('.item').allTextContents();

  expect(items[2]).toBe('A');
});

Тест выполняет следующие действия:

  1. открывает страницу
  2. находит элементы
  3. выполняет перетаскивание
  4. проверяет новый порядок элементов

Тестирование через pointer events

Иногда встроенная команда dragTo работает нестабильно с кастомными drag-системами. В таких случаях используются ручные pointer-события.

Пример:

await page.mouse.move(100, 100);
await page.mouse.down();

await page.mouse.move(300, 100, { steps: 10 });

await page.mouse.up();

Такой подход максимально приближен к реальному поведению пользователя.


Проверка изменения порядка элементов

Muuri физически меняет порядок элементов внутри массива grid.items, а затем обновляет DOM.

Проверка может выполняться двумя способами.

Проверка DOM-порядка

const items = await page.$$eval('.item', nodes =>
  nodes.map(n => n.textContent)
);

expect(items).toEqual(['B', 'C', 'A', 'D']);

Проверка координат

Иногда порядок DOM не меняется, а элементы перестраиваются через позиционирование.

Проверка позиции:

const box = await page.locator('.item').nth(0).boundingBox();

expect(box.x).toBeGreaterThan(200);

Ожидание завершения анимации

Muuri активно использует анимацию при перестановке элементов.

Если тест проверяет состояние слишком рано, возникает ошибка.

Решение — ожидание завершения анимации.

Способ 1: ожидание события

await page.waitForFunction(() => {
  return document.querySelector('.grid').classList.contains('muuri-ready');
});

Способ 2: задержка

await page.waitForTimeout(500);

Предпочтителен первый вариант.


Проверка события move

E2E-тест может проверять, что событие move действительно произошло.

Пример:

await page.exposeFunction('onMove', () => {
  window.__moved = true;
});

await page.evaluate(() => {
  grid.on('move', () => window.onMove());
});

После перетаскивания:

const moved = await page.evaluate(() => window.__moved);
expect(moved).toBe(true);

Тестирование ограничения drag-зоны

Muuri поддерживает параметр dragContainer.

Пример конфигурации:

const grid = new Muuri('.grid', {
  dragEnabled: true,
  dragContainer: document.body
});

E2E-тест должен убедиться, что элемент может покидать пределы сетки.

Проверка:

const box = await page.locator('.item').nth(0).boundingBox();

expect(box.y).toBeGreaterThan(300);

Тестирование dragSort

Muuri может сортировать элементы между несколькими сетками.

Конфигурация:

const gridA = new Muuri('.grid-a', {
  dragEnabled: true,
  dragSort: () => [gridA, gridB]
});

const gridB = new Muuri('.grid-b', {
  dragEnabled: true
});

E2E-тест проверяет перенос элемента.

await source.dragTo(target);

const countA = await page.locator('.grid-a .item').count();
const countB = await page.locator('.grid-b .item').count();

expect(countA).toBe(3);
expect(countB).toBe(1);

Тестирование dragHandle

Перетаскивание может быть ограничено только частью элемента.

Конфигурация:

const grid = new Muuri('.grid', {
  dragEnabled: true,
  dragHandle: '.handle'
});

HTML:

<div class="item">
  <div class="handle">⇅</div>
  <div class="content">Item</div>
</div>

E2E-тест:

const handle = page.locator('.handle').first();

await handle.dragTo(page.locator('.item').nth(2));

Попытка перетаскивания за .content должна завершаться без изменения позиции.


Тестирование dragRelease

Muuri может анимировать возврат элемента.

Опция:

dragRelease: {
  duration: 300
}

E2E-тест должен учитывать время анимации.

await page.waitForTimeout(350);

После этого выполняется проверка позиции.


Тестирование мобильных устройств

Drag-and-drop активно используется на touch-устройствах.

Playwright позволяет эмулировать мобильное устройство:

test.use({
  viewport: { width: 375, height: 812 },
  hasTouch: true
});

Тест выполняет жесты:

await page.touchscreen.tap(100, 100);

И движение:

await page.mouse.move(200, 200);

Типичные проблемы E2E тестов drag and drop

1. Нестабильность координат

Причина:

  • разные размеры окна
  • responsive layout

Решение:

test.use({
  viewport: { width: 1280, height: 720 }
});

2. Анимации

Muuri активно анимирует перемещения.

Решение — отключение анимации в тестах.

const grid = new Muuri('.grid', {
  layoutDuration: 0,
  dragRelease: {
    duration: 0
  }
});

3. Перекрытие элементов

Если элементы перекрываются, dragTo может выбрать неправильную цель.

Используется точное указание координат:

await page.mouse.move(150, 150);

Тестирование сохранения порядка

Часто порядок элементов сохраняется в базе данных.

Пример функции:

function saveOrder() {
  const order = grid.getItems().map(item =>
    item.getElement().textContent
  );

  localStorage.setItem('order', JSON.stringify(order));
}

E2E-тест:

await page.reload();

const order = await page.evaluate(() =>
  JSON.parse(localStorage.getItem('order'))
);

expect(order[0]).toBe('B');

Проверка производительности drag

Большие сетки могут содержать сотни элементов.

E2E-тест измеряет время операции.

const start = Date.now();

await item.dragTo(target);

const duration = Date.now() - start;

expect(duration).toBeLessThan(1000);

Это помогает выявлять деградацию производительности.


Стратегия построения E2E тестов

Оптимальный набор тестов:

  1. Базовое перетаскивание
  2. Изменение порядка
  3. Перемещение между сетками
  4. dragHandle
  5. touch-drag
  6. сохранение состояния
  7. анимации
  8. ограничение контейнера

Такая структура покрывает практически все сценарии использования Muuri drag-and-drop.