Урок 11. Флак — это правило, которое не внедрили
Линтер и код-ревью
Темы урока
Термины: флак, правило, линтер, ESLint, плагин, warning и error. Когда внедрять правило в линтер, а когда в кодекс. Почему сначала машина, потом ревью
Видео урока
Конспект урока
Конспект — Урок 11: Флак — это правило, которое не внедрили
Главное за урок
Тест сегодня зелёный. Завтра красный. Продукт не деплоили, Playwright не ломался.
Это флак: один и тот же код даёт два исхода. На уроке его разбирали не как «магию CI», а как дырку в правилах.
Правило не внедрили — в одном из трёх мест:
- Не написали (нет договора, что так нельзя).
- Не включили в линтер (машина могла поймать, но молчит).
- Не посмотрели в 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