Интеграционные тесты проверяют взаимодействие нескольких частей системы одновременно. В контексте интерфейсов, построенных на Interact.js, такие тесты охватывают:
В отличие от модульных тестов, которые изолируют функции, интеграционные тесты воспроизводят реальное поведение интерфейса, включая последовательности событий мыши или тач-устройств.
Основная цель — убедиться, что пользовательский сценарий работает корректно в условиях, максимально приближенных к реальному браузеру.
Типичные сценарии для проверки:
Интерактивные системы с Interact.js обычно имеют несколько уровней:
1. DOM-структура
HTML-разметка, содержащая интерактивные элементы.
2. Конфигурация Interact.js
Определяет поведение элементов:
3. Бизнес-логика
Обработчики событий, обновляющие состояние интерфейса.
4. Сторонние зависимости
Например:
Интеграционные тесты должны проверять совместную работу всех этих уровней.
Для тестирования интерфейсов чаще всего используются инструменты:
Интерактивные события требуют среды, поддерживающей:
Headless браузеры
Playwright или Cypress запускают браузер и выполняют сценарии пользователя.
DOM-эмуляция
JSDOM позволяет тестировать логику, но не всегда корректно обрабатывает сложные pointer-события.
Для Interact.js наиболее надёжны браузерные интеграционные тесты.
Установка инструментов:
npm install --save-dev playwright
Инициализация проекта тестирования:
npx playwright init
Создаётся структура:
tests/
playwright.config.js
HTML:
<div id="draggable" class="box"></div>
<div id="dropzone" class="zone"></div>
Jav * aScript:
interact('#draggable').draggable({
listeners: {
move(event) {
const target = event.target
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
target.style.transform = `translate(${x}px, ${y}px)`
target.setAttribute('data-x', x)
target.setAttribute('data-y', y)
}
}
})
interact('#dropzone').dropzone({
ondrop(event) {
event.target.classList.add('active')
}
})
Тест должен проверять полный пользовательский сценарий.
Основные этапы:
import { test, expect } from '@playwright/test'
test('перетаскивание элемента в dropzone', async ({ page }) => {
await page.goto('http://localhost:3000')
const draggable = page.locator('#draggable')
const dropzone = page.locator('#dropzone')
const start = await draggable.boundingBox()
const end = await dropzone.boundingBox()
await page.mouse.move(start.x + 10, start.y + 10)
await page.mouse.down()
await page.mouse.move(end.x + 50, end.y + 50)
await page.mouse.up()
await expect(dropzone).toHaveClass(/active/)
})
Проверяется:
Иногда требуется убедиться, что элемент действительно переместился.
test('изменение позиции элемента', async ({ page }) => {
await page.goto('/')
const element = page.locator('#draggable')
const before = await element.boundingBox()
await page.mouse.move(before.x + 5, before.y + 5)
await page.mouse.down()
await page.mouse.move(before.x + 200, before.y + 200)
await page.mouse.up()
const after = await element.boundingBox()
expect(after.x).toBeGreaterThan(before.x)
expect(after.y).toBeGreaterThan(before.y)
})
Проверяется изменение координат элемента в DOM.
Interact.js поддерживает изменение размеров элементов.
Конфигурация:
interact('.resizable').resizable({
edges: { right: true, bottom: true }
})
Интеграционный тест:
test('изменение размера элемента', async ({ page }) => {
await page.goto('/')
const element = page.locator('.resizable')
const box = await element.boundingBox()
await page.mouse.move(box.x + box.width - 2, box.y + box.height - 2)
await page.mouse.down()
await page.mouse.move(box.x + box.width + 100, box.y + box.height + 100)
await page.mouse.up()
const resized = await element.boundingBox()
expect(resized.width).toBeGreaterThan(box.width)
expect(resized.height).toBeGreaterThan(box.height)
})
Interact.js поддерживает ограничения (restrict).
Конфигурация:
interact('.item').draggable({
modifiers: [
interact.modifiers.restrict({
restriction: 'parent'
})
]
})
Интеграционный тест проверяет, что элемент не выходит за границы.
test('ограничение перемещения родителем', async ({ page }) => {
await page.goto('/')
const element = page.locator('.item')
const parent = page.locator('.container')
const parentBox = await parent.boundingBox()
await element.dragTo(parent)
const after = await element.boundingBox()
expect(after.x).toBeGreaterThanOrEqual(parentBox.x)
})
Dropzone может иметь сложные правила.
Пример конфигурации:
interact('.dropzone').dropzone({
accept: '.draggable',
overlap: 0.5
})
Тест должен проверять:
test('dropzone принимает допустимый элемент', async ({ page }) => {
await page.goto('/')
const item = page.locator('.draggable')
const zone = page.locator('.dropzone')
await item.dragTo(zone)
await expect(zone).toHaveClass(/active/)
})
Иногда требуется проверка цепочки действий пользователя.
Пример:
test('комбинированный сценарий', async ({ page }) => {
await page.goto('/')
const item = page.locator('.widget')
const zone = page.locator('.dashboard')
await item.dragTo(zone)
const box = await item.boundingBox()
await page.mouse.move(box.x + box.width, box.y + box.height)
await page.mouse.down()
await page.mouse.move(box.x + box.width + 50, box.y + box.height + 50)
await page.mouse.up()
await expect(item).toBeVisible()
})
Такие тесты выявляют ошибки интеграции между компонентами.
Interact.js активно используется на мобильных устройствах.
Playwright поддерживает эмуляцию тач-взаимодействий.
await page.touchscreen.tap(200, 200)
Drag-сценарий:
await page.touchscreen.tap(startX, startY)
await page.touchscreen.down()
await page.touchscreen.move(endX, endY)
await page.touchscreen.up()
Проверяются:
Interact.js может использовать:
Тесты должны учитывать задержки.
Пример:
await page.waitForTimeout(300)
Или ожидание состояния:
await expect(element).toHaveCSS('transform', /translate/)
Некоторые сценарии требуют проверки срабатывания событий.
Например:
Можно добавить логирование:
window.events = []
interact('.item').draggable({
listeners: {
start() {
window.events.push('start')
},
end() {
window.events.push('end')
}
}
})
Тест:
const events = await page.evaluate(() => window.events)
expect(events).toContain('start')
expect(events).toContain('end')
Каждый интеграционный тест должен:
Playwright автоматически создаёт новый контекст браузера для каждого теста.
Необходимо проверять поведение при некорректных условиях:
Пример:
test('dropzone отклоняет недопустимый элемент', async ({ page }) => {
await page.goto('/')
const item = page.locator('.invalid')
const zone = page.locator('.dropzone')
await item.dragTo(zone)
await expect(zone).not.toHaveClass(/active/)
})
Интерактивные тесты могут быть медленными.
Методы оптимизации:
1. Минимальные страницы
Тестировать только нужный компонент.
2. Параллельное выполнение
Playwright поддерживает parallel-режим.
3. Стабильные селекторы
Использование:
data-testid
пример:
<div data-testid="drag-item"></div>
Иногда необходимо проверять не только DOM, но и состояние JavaScript.
Пример:
const state = await page.evaluate(() => window.appState)
expect(state.items.length).toBe(1)
Это подтверждает, что взаимодействие пользователя обновило внутреннюю логику приложения.
Интерактивные интерфейсы требуют особого подхода к покрытию.
Ключевые сценарии:
Интеграционные тесты лучше строить вокруг поведения пользователя, а не внутренней реализации.
Примеры сценариев:
Такой подход делает тесты:
Некоторые тестовые среды генерируют только mouse-события.
Interact.js использует:
DOM-элементы могут смещаться из-за:
Иногда обработчики обновляют состояние через:
Тесты должны учитывать такие задержки.
Интеграционные тесты требуют регулярного обновления.
Основные практики:
Пример вспомогательной функции:
async function drag(page, start, end) {
await page.mouse.move(start.x, start.y)
await page.mouse.down()
await page.mouse.move(end.x, end.y)
await page.mouse.up()
}
Это уменьшает дублирование кода и упрощает поддержку тестового набора.