waitForElementToBeRemoved: ожидание удаления элементов

waitForElementToBeRemoved — это утилита из React Testing Library, предназначенная для асинхронного тестирования компонентов, в которых элементы DOM исчезают с течением времени. Она упрощает проверку динамических изменений интерфейса, таких как закрытие модальных окон, удаление уведомлений, скрытие индикаторов загрузки и другие сценарии, где элемент должен исчезнуть после определённого события.


Основной синтаксис

await waitForElementToBeRemoved(callbackOrElement, options)
  • callbackOrElement:

    • Функция, возвращающая DOM-элемент (или массив элементов), который должен исчезнуть, например:

      () => screen.getByText('Loading...')
    • Или сам элемент/массив элементов напрямую:

      const loader = screen.getByTestId('loader');
      await waitForElementToBeRemoved(loader);
  • options (необязательно):

    • timeout — максимальное время ожидания в миллисекундах (по умолчанию 1000).
    • interval — интервал проверки DOM (по умолчанию 50).

Принцип работы

waitForElementToBeRemoved выполняет периодическую проверку DOM до тех пор, пока указанный элемент не исчезнет. Если за указанное время элемент не исчез, утилита выбрасывает ошибку. В отличие от обычного waitFor, она специализирована именно для удаления элементов, что делает тесты более читаемыми и точными.


Пример с индикатором загрузки

import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
import LoaderComponent from './LoaderComponent';

test('спиннер исчезает после загрузки данных', async () => {
  render(<LoaderComponent />);
  
  const spinner = screen.getByTestId('loader');
  expect(spinner).toBeInTheDocument();
  
  await waitForElementToBeRemoved(spinner);

  expect(screen.queryByTestId('loader')).toBeNull();
  expect(screen.getByText('Данные загружены')).toBeInTheDocument();
});

В этом примере:

  • Спиннер сначала отображается (toBeInTheDocument).
  • waitForElementToBeRemoved ждёт исчезновения спиннера.
  • После исчезновения проверяется наличие контента.

Использование с функцией-колбэком

Функцию удобно применять, когда элемент может появляться и исчезать динамически:

await waitForElementToBeRemoved(() => screen.queryByText('Загрузка...'));
  • queryByText возвращает null, если элемент отсутствует.
  • Функция будет проверяться повторно, пока элемент не исчезнет или не превысится timeout.

Асинхронные события и userEvent

При взаимодействии с пользователем часто нужно дождаться удаления элемента после действия:

import { render, screen, waitForElementToBeRemoved } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Notification from './Notification';

test('уведомление закрывается после клика', async () => {
  render(<Notification message="Успех!" />);
  
  const closeButton = screen.getByRole('button', { name: /закрыть/i });
  userEvent.click(closeButton);
  
  await waitForElementToBeRemoved(() => screen.queryByText('Успех!'));
  
  expect(screen.queryByText('Успех!')).toBeNull();
});

Здесь проверяется:

  • Реакция интерфейса на пользовательское событие (click).
  • Асинхронное исчезновение уведомления.

Проверка массивов элементов

waitForElementToBeRemoved работает и с массивами:

const items = screen.getAllByTestId('list-item');
await waitForElementToBeRemoved(items);

Элементы исчезают один за другим, и утилита завершит выполнение только когда все элементы массива удалены.


Настройка таймаутов и интервалов

await waitForElementToBeRemoved(() => screen.queryByText('Загрузка...'), {
  timeout: 3000,  // ждём до 3 секунд
  interval: 100   // проверка каждые 100 мс
});
  • Полезно для медленных асинхронных операций.
  • Позволяет контролировать частоту проверок DOM, чтобы уменьшить нагрузку на тестовый раннер.

Частые ошибки

  1. Использование getBy* вместо queryBy* в колбэке:

    • getBy* выбросит исключение, если элемент отсутствует.
    • В колбэке лучше использовать queryBy*, чтобы вернуть null при отсутствии элемента.
  2. Не дожидаться промиса:

    • waitForElementToBeRemoved возвращает Promise. Нужно использовать await.
    • Без await тест может завершиться раньше времени и дать ложноположительный результат.
  3. Попытка удалить уже отсутствующий элемент:

    • Если элемент не существует в момент вызова, промис немедленно выполнится.
    • Иногда это приводит к тому, что тест не проверяет нужное поведение.

Когда использовать

  • Закрытие модальных окон, тултипов, уведомлений.
  • Исчезновение индикаторов загрузки или спиннеров.
  • Удаление элементов из списка после действия пользователя.
  • Тестирование анимаций появления и исчезновения элементов, где важно дождаться удаления DOM.

Отличие от waitFor

  • waitFor — универсальный инструмент для ожидания любого условия, возвращает значение функции, повторяя проверки.
  • waitForElementToBeRemoved — специализирован для исчезновения элементов, делает код короче и выразительнее, повышая читаемость теста.

Практические рекомендации

  • Для динамически появляющихся элементов всегда использовать queryBy* в колбэке.
  • Настраивать timeout для медленных API-запросов, чтобы избежать случайных падений тестов.
  • Проверять состояние после удаления элемента, чтобы убедиться, что UI обновился корректно.

waitForElementToBeRemoved значительно упрощает тестирование асинхронных сценариев, где важен момент исчезновения элемента. Он делает код тестов более декларативным и легко читаемым, снижая риск ошибок, связанных с неправильной синхронизацией.