Сервис развивается: тестируем формат, собираем идеи, улучшаем сервис. Есть идеи?

Написать
Войти
Дайджесты
Иллюстрация процесса отладки и линтинга автотестов Playwright

Почему зелёный линтер не гарантирует надежность Playwright-тестов: строгий ESLint и правила код-ревью

Рекомендованная конфигурация eslint-plugin-playwright пропускает критические проблемы автотестов в режиме предупреждения. Перевод правил в статус ошибок, настройка строгого static-анализа и смысловой чек-лист код-ревью устраняют скрытые гонки, слепые паузы и нестабильные проверки в CI-пайплайне.

Почему зелёный линтер не гарантирует надежность Playwright-тестов: строгий ESLint и правила код-ревью

В автоматизированном тестировании веб-приложений успешное прохождение CI-пайплайна не всегда гарантирует надежность тестов. Фреймворк Playwright предоставляет встроенные механизмы автоожидания (auto-waiting) и веб-проверок с повторными попытками (web-first assertions). Однако на практике разработчики часто обходят эти защитные барьеры, внедряя в код слепые паузы, принудительные клики и одноразовые проверки состояния.

Главная проблема заключается в том, что стандартный рекомендованный пресет плагина eslint-plugin-playwright маркирует ключевые антипаттерны как предупреждения (warning). В CI-сборщике предупреждения не блокируют мёрж Pull Request, если в проекте не установлен флаг --max-warnings 0. В результате код со скрытыми гонками попадает в основную ветку, а тесты начинают «флакать» (случайно падать) при ночных прогонах.

В этом материале показано, как перевести статический анализ Playwright-тестов в строгий режим контроля через ESLint Flat Config и внедрить практический чек-лист для код-ревью.

Три уровня защиты тестового пайплайна

Для построения устойчивого тестового пайплайна необходимо разделять зоны ответственности на трех уровнях:

  1. Базовый статический анализ: стандартный ESLint не знает специфики тестовых фреймворков. Он не отличит оставленный в коде отладочный test.only, вызов page.pause(), пропущенный await или явную задержку времени.
  2. Мягкая политика линтинга (Recommended Preset): плагин eslint-plugin-playwright установлен, но работает в стандартном режиме. Авторы плагина оставляют часть правил на уровне warning, чтобы избежать ложных срабатываний в специфичных проектах. Без локального переопределения правил опасные паттерны пропускаются в репозиторий.
  3. Смысловая логика сценария: статический анализ не способен проверить бизнес-логику. Линтер не проверяет, объявлен ли перехватчик сети page.waitForResponse() до клика по кнопке или вызвана ли проверка после полной стабилизации DOM-дерева.

Статический анализ служит автоматическим фильтром синтаксических ошибок, а смысловую корректность и изоляцию данных обеспечивают правила код-ревью и архитектура тестов.

Механика Playwright: автоожидание и веб-проверки

Перед выполнением каждого действия (например, locator.click()) Playwright автоматически выполняет серию проверок доступности (actionability checks):

  • Привязка к селектору: элемент присутствует в DOM-дереве.
  • Видимость: элемент отображается на экране, не скрыт display: none и имеет размер.
  • Стабильность: элемент завершил анимацию и не перемещается.
  • Получение событий: элемент не перекрыт оверлеем или спиннером.
  • Включенность: у элемента отсутствует атрибут disabled.

Если условие не выполняется, Playwright повторно проверяет элемент до истечения таймаута (по умолчанию 30 секунд).

Передача опции { force: true } принудительно отключает эти проверки. Клиентский клик отправляется по координатам элемента, даже если поверх него находится оверлей. Если за этим кликом следует пауза page.waitForTimeout(3000), тест становится хрупким.

Применение вызова expect(await locator.isVisible()).toBe(true) ломает логику фреймворка. Это одноразовый замер (non-retrying assertion). Если элемент отрисуется через 50 миллисекунд после вызова, проверка сразу вернет false. Взамен следует применять асинхронные проверки await expect(locator).toBeVisible(), которые повторно запрашивают состояние до таймаута.

Настройка строгой конфигурации ESLint Flat Config

Для проектов с современным ESLint 9+ на основе Flat Config рекомендуется явно задать правила для тестовых директорий.

Пример конфигурации eslint.config.js:

import playwright from "eslint-plugin-playwright";

export default [
  ...playwright.configs["flat/recommended"],
  {
    files: ["tests/**/*.{ts,tsx}", "e2e/**/*.{ts,tsx}"],
    rules: {
      "playwright/no-wait-for-timeout": "error",
      "playwright/no-force-option": "error",
      "playwright/expect-expect": "error",
      "playwright/missing-playwright-await": "error",
      "playwright/prefer-web-first-assertions": "error",
      "playwright/no-focused-test": "error",
      "playwright/no-skipped-test": "warn",
    },
  },
];

Проверьте применимость правил через команду npx eslint --print-config tests/example.spec.ts. Если в тесте необходимо отступить от правила, используйте точечный комментарий // eslint-disable-next-line playwright/no-force-option с текстовым пояснением причины.

Порядок внедрения и валидации

Порядок перевода тестового проекта на строгие стандарты:

  1. Изоляция области: ограничьте применение плагина eslint-plugin-playwright только файлами .spec.ts или .test.ts.
  2. Базовый аудит: запустите npx eslint tests/ и зафиксируйте список текущих предупреждений.
  3. Рефакторинг кода:
    • Замените page.waitForTimeout(N) на веб-проверку await expect(locator).toBeVisible() или ожидание сети page.waitForResponse().
    • Замените expect(await locator.isVisible()).toBe(true) на await expect(locator).toBeVisible().
    • Удалите опцию { force: true }, скорректировав селекторы.
  4. Включение ошибок: переведите правила в статус 'error' в eslint.config.js.
  5. Проверка CI: создайте тестовый файл с паузой page.waitForTimeout(1000) и убедитесь, что CI блокирует сборку.

Спецификации доступны в руководстве Playwright Best Practices, разделах Actionability, Test Assertions и в репозитории eslint-plugin-playwright.

Сравнение подходов: хрупкий vs устойчивый код

Пример рефакторинга сценария отправки формы.

Ненадёжный подход:

test("Отправка формы (ненадёжно)", async ({ page }) => {
  await page.goto("/contact");
  await page.getByRole("button", { name: "Отправить" }).click({ force: true });
  await page.waitForTimeout(3000);
  const isSuccess = await page.getByText("Заявка принята").isVisible();
  expect(isSuccess).toBe(true);
});

Устойчивый подход:

test("Отправка формы (надёжно)", async ({ page }) => {
  await page.goto("/contact");
  const responsePromise = page.waitForResponse(
    (resp) => resp.url().includes("/api/contact") && resp.status() === 200
  );
  await page.getByRole("button", { name: "Отправить" }).click();
  await responsePromise;
  await expect(page.getByRole("status")).toHaveText("Заявка принята");
});

Устойчивый вариант декларирует целевой результат: при сетевой задержке в 4 секунды веб-проверка дождет ответа в рамках таймаута.

Чек-лист для проведения код-ревью

Блокирующие ошибки (Blocker / Error)
  • Наличие page.waitForTimeout() без диагностического обоснования.
  • Использование принудительных кликов click({ force: true }) без комментария.
  • Отсутствие await перед веб-ассертами expect(locator)....
  • Попадание в коммит отладочных инструкций test.only() или page.pause().
  • Тесты с действиями над UI, но без проверок expect.
Критические замечания (Major)
  • Использование одноразовых проверок expect(await locator.isVisible()).toBe(true) вместо await expect(locator).toBeVisible().
  • Объявление page.waitForResponse() после выполнения клика.
  • Использование try/catch для скрытия падений шагов.
  • Хрупкие CSS-селекторы вместо ролей getByRole() или атрибутов getByTestId().
Рекомендации (Minor)
  • Использование test.skip() без ссылки на задачу в трекере.
  • Отсутствие сброса тестовой базы данных перед запуском сценария.
  • Дублирование логики авторизации вместо использования сохраненной сессии (storage state).

Заключение

Автоматизация приносит пользу только при доверии к результатам CI. Перевод правил eslint-plugin-playwright в статус ошибок устраняет опасные паттерны на этапе написания кода, а чек-лист код-ревью защищает тесты от скрытых гонок. Это снижает расходы на поддержку автотестов и избавляет инженеров от ручных перезапусков падающих сборок.