К списку уроков
Блок 5 · Архитектура и стабильность тестов

Урок 11. Флак — это правило, которое не внедрили

Линтер и код-ревью

Темы урока

Термины: флак, правило, линтер, ESLint, плагин, warning и error. Когда внедрять правило в линтер, а когда в кодекс. Почему сначала машина, потом ревью

Видео урока

Пройти тест по уроку

Конспект урока

Конспект — Урок 11: Флак — это правило, которое не внедрили

Главное за урок

Тест сегодня зелёный. Завтра красный. Продукт не деплоили, Playwright не ломался.

Это флак: один и тот же код даёт два исхода. На уроке его разбирали не как «магию CI», а как дырку в правилах.

Правило не внедрили — в одном из трёх мест:

  1. Не написали (нет договора, что так нельзя).
  2. Не включили в линтер (машина могла поймать, но молчит).
  3. Не посмотрели в PR (машина не умеет, человек не открыл).

Три фильтра идут в этом порядке: правило → линтер → ревью. Trace и UI Mode — если фильтры уже проспали. Не вместо них.


Словарь

Без этих слов остальной конспект не читается.

Термин Что это Зачем слово на уроке
Флак (flaky test) Один код, два исхода. Продукт не меняли Это симптом. Лечим внедрением правила, не retries
Правило Договор команды: «так не пишем» Пока его нет, ревьюер спорит вкусом
Внедрить правило Записать + включить в линтер или в чеклист ревью Записали в чат — ещё не внедрили
Линтер Программа, которая читает код без запуска тестов и орёт на запрещённые конструкции Ловит sleep и force до пуша, за секунды
ESLint Движок линтера в JS/TS Сам по себе не знает Playwright
Правило линтера Одна проверка внутри ESLint, например playwright/no-wait-for-timeout Можно выключить, оставить warning или поднять до error
Плагин Набор правил под инструмент Для тестов — eslint-plugin-playwright
warning Линтер пишет замечание, npm run lint всё равно зелёный Sleep с warning «линт ноль» не ломает
error Линтер валит команду. Код выхода не ноль Домашка «линт = 0» смотрит сюда
Ревью Человек читает чужой PR по чеклисту Ловит смысл: общий email, ложный expect
Кодекс Файл CODEX.md: десять пунктов, по ним пишут комментарий Не вкус. Номер правила
STYLEGUIDE Таблица: какое правило ловит машина, какое — глаза Чтобы не искать sleep глазами

typescript-eslint — не «ещё один линтер тестов». Это парсер: ESLint понимает type Page. Правила Playwright живут в плагине.


Когда что внедрять

Не всё кладём в линтер. Не всё оставляем на глаза.

Проблема повторилась
        │
        ▼
 Есть ли синтаксис, который машина узнает
 (waitForTimeout, force: true, нет await)?
        │
   да   │   нет — это смысл
        ▼                    ▼
 Внедряем в линтер       Внедряем в кодекс
 как error               и смотрим на ревью
        │                    │
        ▼                    ▼
 npm run lint            комментарий «кодекс N»
Ситуация Куда внедрять Почему так, а не иначе
В коде есть waitForTimeout / { force: true } / забытый await Линтер, сразу error Это буквы в файле. Человек не должен искать sleep
Общий email, expect сразу после fill, retries «чтобы зелёное» Кодекс + ревью Для ESLint это обычная строка и обычный вызов
Проблему видели один раз в одном PR Пока правило в кодексе, без нового плагина Плагин пишем, когда проблема повторяется
Правило есть только в чате Ещё не внедрили Завтра новый человек его не прочитает
Линтер есть, правило в recommended как warning Для курса — поднять до error Warning не валит npm run lint
Хочется выключить правило, «чтобы CI был зелёный» Не внедрение, а дырка Если очень надо — комментарий с причиной у одной строки, не off на файл

Порядок не наоборот. Сначала линтер съедает то, что видит сам. Потом ревьюер смотрит смысл. Если на ревью пишешь про waitForTimeout — сначала спроси, почему линтер молчал: его нет или правило выключили.


Что такое линтер на нашем репо

Три пакета, три роли.

Пакет Роль Когда ставить
eslint Движок: читает файлы, применяет правила Когда в репо ещё нет команды lint
typescript-eslint Парсит TypeScript Когда тесты на TS, иначе ESLint не поймёт type Page
eslint-plugin-playwright Правила именно автотестов Когда уже есть eslint, и нужно ловить sleep / force / .only

Сегодня не включаем весь TypeScript-recommended (any, unused). Иначе утонем в чужом шуме. Внедряем только правила тестов.

Установи зависимости:

npm install -D eslint typescript-eslint eslint-plugin-playwright

Создай в корне проекта файл eslint.config.mjs:

import tseslint from "typescript-eslint";
import playwright from "eslint-plugin-playwright";

const playwrightRecommended = playwright.configs["flat/recommended"];

export default [
  {
    ...playwrightRecommended,
    files: ["tests/**/*.ts"],
    languageOptions: {
      parser: tseslint.parser,
    },
    rules: {
      ...playwrightRecommended.rules,
      "playwright/no-wait-for-timeout": "error",
      "playwright/no-force-option": "error",
      "playwright/missing-playwright-await": "error",
      "playwright/no-commented-out-tests": "error",
      "playwright/no-page-pause": "error",
      "playwright/no-focused-test": "error",
      "playwright/expect-expect": "error",
      "playwright/no-conditional-in-test": "off",
      "playwright/valid-title": "warn",
    },
  },
];

В package.json добавь скрипт:

{
  "scripts": {
    "lint": "eslint tests"
  }
}

Команда одна: npm run lint. Она смотрит папку tests. Ноль ошибок — можно отдавать на ревью. Красный линт — не проси ревьюера искать sleep.

Документация плагина: https://github.com/mskelton/eslint-plugin-playwright


Правила, которые внедряем в линтер

В recommended плагина часть из них — warning. Warning не роняет npm run lint. В курсе их подняли до error, иначе домашка «линт ноль» пропускает sleep.

playwright/no-wait-for-timeout

Запрещает page.waitForTimeout(200).

Когда внедрять: как только в репо появились UI-тесты. Sleep — первая привычка «на всякий случай».

Почему: 200 мс на ноуте хватает, в CI очередь — мало. Это лотерея по сети, не ожидание. Жди сигнал: URL, ответ сервера, видимость элемента.

Документация: no-wait-for-timeout

playwright/no-force-option

Запрещает { force: true } на клике и подобных действиях.

Когда внедрять: вместе с первым правилом. force обходит actionability с Урока 8.

Почему: клик вслепую. Кнопка под оверлеем, элемент ещё не готов — тест «прошёл», в проде пользователь не нажмёт. Флак куплен заранее.

Документация: no-force-option

playwright/missing-playwright-await

Запрещает вызов Playwright без await.

Когда внедрять: в том же конфиге. Это Урок 4, теперь машина тоже видит.

Почему: в коде порядок есть, в браузере нет. Тест идёт дальше, пока expect ещё не закончился — зелёный вранье или гонка.

Документация: missing-playwright-await

Ещё четыре, тоже error в курсе

Правило Что запрещает Почему внедряем как error
playwright/expect-expect Тест без expect Слепой прогон: клики прошли, результат не смотрели
playwright/no-page-pause page.pause() CI встанет и будет ждать человека
playwright/no-focused-test test.only В CI уедет один тест, остальные как будто зелёные
playwright/no-commented-out-tests // test(...) Мёртвый код. Нужен test.skip / test.fixme с причиной

Два исключения в конфиге курса — не «правило плохое», а история репо:

  • no-conditional-in-test выключен: гонка guest2 через if/throw так учили с Урока 4.
  • valid-title оставлен warning: в unit/api эталона есть пробел в конце describe. Не валим домашку из-за этого.

Правило не выключаем пачкой, «чтобы CI был зелёный». Если одной строке правда нужен exception — комментарий с причиной рядом, не тихий off на файл.


Что в линтер не внедряем

Машина не читает смысл. Три дырки с эфира — только кодекс и глаза.

Общий email. fill("shared-hw11@example.com") для ESLint — строка как строка. Для двух воркеров — гонка за один ящик. Правило: свои данные на тест, makeUser(role, Date.now()). Пароль можно хардкодить. Имя и почту — нет. Это кодекс 7.

Ложный expect. fill("Новое имя") и сразу toHaveValue("Новое имя") — синтаксис идеальный. Проверяешь сам fill, не имя с сервера. Имя с сервера видно после reload, до reload дождись POST. Линтер молчит. Это кодекс 9.

Retries вместо фикса. retries: 2 — второй заход зелёный, в отчёте flaky, причина жива. В CI retries — страховка после того, как линтер и ревью уже стоят. Вместо правила их не внедряем. В ДЗ не пишем.

Если проблему нельзя выразить синтаксисом — не мучай ESLint кастомным правилом в первый день. Запиши в кодекс, гоняй на ревью. На Уроке 14 те же файлы прочитает ИИ.


Кодекс и ревью — второй фильтр, не замена линтеру

CODEX.md — десять пунктов. REVIEW.md — те же номера галочками.

Комментарий: кодекс 4, нет test.step. Не «тут плохо».

Ревью не ищет sleep. Если линт красный — сначала себе. Пустой LGTM без чеклиста не считается.

Кратко, что в кодексе, кроме «нет timeout»:

Пункт О чём Линтер это видит?
1–2 Где лежит: e2e / helpers / pages. expect в тесте Нет
3–6 Каркас, test.step, имена, нарезка тестов Нет
7–8 Свои данные, локаторы в странице Нет
9 Timeout, force, ложный expect Частично: sleep и force — да; ложный expect — нет
10 Зелёный PR, линт 0, описание Нет

Ключевые тезисы для теста

  • Флак — один код, два исхода. Лечится внедрением правила, не retries и не Trace.
  • Внедрить = записать правило и включить его в линтер (если машина видит) или в кодекс (если нужен смысл).
  • Линтер читает код без запуска тестов. ESLint — движок. eslint-plugin-playwright — правила автотестов.
  • В линтер внедряем то, что узнаётся по буквам: waitForTimeout, force, забытый await, .only, pause, закомментированный test.
  • warning не валит npm run lint. Правило, без которого домашка врёт, поднимаем до error.
  • Смысл (общий email, expect до reload) в линтер не внедряем — это ревью.
  • Сначала линтер, потом глаза. Искать sleep на ревью — фильтр поставили не туда.

Домашнее задание

Индивидуальная проверка ДЗ — на Boosty

Конспект, видео и тест открыты всем. Текст задания и проверка работы — по подписке.

Индивидуальная проверка ДЗ на Boosty
База для QA — вопросы на собеседование, тренажёры и материалы