E2E-тестирование

Библиотека Focus-trap предназначена для управления фокусом внутри заданного DOM-элемента, предотвращая выход фокуса за его пределы. Это критически важно для модальных окон, всплывающих меню, кастомных диалогов и других интерфейсных компонентов, где пользователь должен оставаться в пределах интерактивной области.

Focus-trap работает за счёт перехвата событий Tab и Shift+Tab, а также программного управления фокусом, обеспечивая циклическое перемещение между фокусируемыми элементами внутри контейнера.

Ключевые понятия:

  • Активная ловушка (active trap) — состояние, при котором фокус ограничен контейнером.
  • Пассивная ловушка — настроенная, но не активная ловушка. Фокус перемещается свободно.
  • Initial focus — элемент, который получает фокус при активации ловушки.
  • Escape deactivation — возможность деактивации ловушки по нажатию клавиши Escape.

Установка и базовое использование

Focus-trap распространяется через npm:

npm install focus-trap

Подключение в проекте:

import { createFocusTrap } from 'focus-trap';

Создание простой ловушки для модального окна:

const modal = document.getElementById('modal');
const trap = createFocusTrap(modal, {
  initialFocus: '#modal input:first-of-type',
  escapeDeactivates: true,
  clickOutsideDeactivates: true
});

// Активация ловушки
trap.activate();

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

  • initialFocus — CSS-селектор или DOM-элемент, который получит фокус при активации.
  • escapeDeactivatestrue/false, определяет возможность выхода из ловушки клавишей Escape.
  • clickOutsideDeactivatestrue/false, разрешает деактивацию при клике вне контейнера.
  • returnFocusOnDeactivate — возвращает фокус к элементу, который был активен до активации.

Управление фокусом

Focus-trap отслеживает все элементы, способные получать фокус внутри контейнера (tabindex, button, input, a[href], select, textarea, iframe).

  • Перемещение фокуса вперёд (Tab): если текущий элемент последний в списке, фокус переносится на первый.
  • Перемещение фокуса назад (Shift+Tab): если текущий элемент первый, фокус переносится на последний.

Можно вручную управлять фокусом:

trap.focusFirstElement(); // Фокус на первый элемент
trap.focusLastElement();  // Фокус на последний элемент

Динамическое содержимое

При изменении DOM внутри ловушки необходимо обновлять список фокусируемых элементов. Focus-trap автоматически отслеживает добавление/удаление элементов через MutationObserver.

Пример:

const trap = createFocusTrap('#modal', {
  allowOutsideClick: true,
  fallbackFocus: '#modal'
});

// Добавление динамического контента
const input = document.createElement('input');
modal.appendChild(input);

// После добавления нового элемента фокус корректно будет циклично перемещаться

Параметр fallbackFocus используется, если внутри контейнера нет фокусируемых элементов — фокус будет установлен на указанный элемент.


Интеграция с React и другими фреймворками

Для React часто используют ref для контейнера:

import { useRef, useEffect } from 'react';
import { createFocusTrap } from 'focus-trap';

function Modal({ isOpen }) {
  const modalRef = useRef(null);

  useEffect(() => {
    const trap = createFocusTrap(modalRef.current, { escapeDeactivates: true });
    if (isOpen) trap.activate();
    return () => trap.deactivate();
  }, [isOpen]);

  return <div ref={modalRef} role="dialog">...</div>;
}

Важно корректно деактивировать ловушку при закрытии компонента, иначе фокус может “застрять” вне интерфейса.


Расширенные возможности

Focus-trap поддерживает следующие расширенные сценарии:

  • Nested traps: вложенные модальные окна. При этом активная ловушка корректно передаёт фокус обратно при закрытии вложенной.
  • Custom tab order: можно вручную переопределять порядок элементов через tabindex или модифицировать focusableElements при создании ловушки.
  • Deferred activation: активация ловушки с задержкой или по определённым событиям.

Пример вложенной ловушки:

const parentTrap = createFocusTrap('#parent', { escapeDeactivates: false });
const childTrap = createFocusTrap('#child', { escapeDeactivates: true });

// При открытии дочернего окна
childTrap.activate();
// При закрытии — возвращение к родительской
childTrap.deactivate();
parentTrap.activate();

Интеграция с E2E-тестированием

Focus-trap облегчает тестирование пользовательского взаимодействия:

  1. Проверка цикличности фокуса:
const trap = createFocusTrap('#modal');
trap.activate();

const focusable = trap.getElements();
focusable[focusable.length - 1].focus();
simulateTab(); // пользователь нажал Tab
assert(document.activeElement === focusable[0]);
  1. Тестирование деактивации:
trap.activate();
simulateEscape();
assert(!trap.active); // ловушка деактивирована
  1. Проверка возврата фокуса:
const button = document.querySelector('#open-modal');
button.focus();
trap.activate();
trap.deactivate();
assert(document.activeElement === button);

Библиотека совместима с Cypress, Playwright и Puppeteer, позволяя эмулировать нажатия клавиш Tab, Shift+Tab и Escape.


Оптимизация для доступности

Использование Focus-trap улучшает доступность:

  • Роль dialog или alertdialog помогает скринридерам.
  • Return focus предотвращает потерю контекста после закрытия модального окна.
  • Escape key позволяет пользователю безопасно покинуть интерактивный контейнер.

Правильная конфигурация обеспечивает соответствие стандартам WCAG 2.1 и ARIA.


Focus-trap — универсальный инструмент для контроля фокуса, особенно важный для комплексных интерфейсов, модальных окон, всплывающих форм и приложений с динамическим контентом. Он обеспечивает цикличность фокуса, безопасность навигации клавиатурой и простую интеграцию с E2E-тестами и современными фреймворками.