Test review
Claude Code skills that turn an AI agent into a meticulous QA engineer. Evidence or it didn't happen.
npx -y skills add akovalion/paranoid-qa --skill test-reviewAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 28 days oldThe repository was created 28 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Ревью только что написанных или изменённых автотестов на соответствие best practices TypeScript + Playwright (по официальной документации) и конвенциям вашего проекта. Используй по /test-review либо после написания/правки любого теста (UI E2E, API, UI+API, моки, visual, mobile) или Page Object/фикстуры/констант — до коммита. Выдаёт приоритизированный список замечаний с severity, привязкой к строкам и готовыми фиксами.
SKILL.md
25.7 KB, as published. Nobody here has run it
Ревью автотестов (TypeScript + Playwright)
Проверь только что написанный или изменённый тест-код на соответствие best practices и выдай приоритизированный список замечаний с фиксами. Источник правил — официальная документация Playwright (best-practices, locators, test-assertions) и TypeScript + конвенции конкретного проекта.
Источник правды по проекту — его корневой
CLAUDE.md(если есть) и стиль соседнего кода. Этот скилл — фаза проверки: он дополняет проектные правила, не заменяет их. Развёрнутые❌ до → ✅ послеи ссылки на источники по каждому правилу — вreferences/rules-catalog.md.
Когда применять и режим работы
- Режим по умолчанию — диагностика. Прочитать код, прогнать статический анализ, выдать отчёт. Файлы не править, пока пользователь явно не попросит «применяй / чини». Тогда — итеративно, по одному изменению с прогоном между ними (не big-bang переписывание рабочего теста).
- Scope — только новое/изменённое, не весь suite. По умолчанию — незакоммиченные изменения (
git status+git diff). Если пользователь указал файл/папку — ревьюй их. - Любой тип теста: UI E2E, API, UI+API, visual regression, mobile, моки, а также Page Object, фикстуры, константы.
- Не уходи в автономные действия за пределами ревью (воспроизведение через браузер, прогон всего suite, правки) без подтверждения — задача по умолчанию «прочитать и оценить».
Доказательная дисциплина (без галлюцинаций)
- Каждое замечание — по реально прочитанной строке (
file:line) или по наблюдённому выводу typecheck / lint / прогона. Не выдумывай нарушения «по аналогии» и не ссылайся на строки, которых не видел. - Правило проверяется инструментом (tsc, ESLint, прогон) → сначала запусти инструмент, потом репорти его вывод, а не «вероятно есть».
- Не уверен, что это дефект, а не осознанное решение проекта → помечай «под вопросом», не утверждай. Сверяйся с
CLAUDE.mdпроекта и соседним кодом: часть «анти-паттернов» может быть намеренной (легаси-хелперы, нестандартная разметка, осознанные исключения из правил). Имена файлов/эндпоинтов/селекторов из памяти и прошлого контекста — фон, перепроверяй на живом коде. - Не нашёл нарушений в категории — так и пиши «чисто», не придумывай замечание ради объёма.
Процесс
- Scope. Определи файлы под ревью:
git status --short+git diff --name-only(учитывай untracked), либо переданные пути. Для каждого spec найди связанные Page Object'ы, константы, фикстуры. - Контекст.
Readизменённых файлов + связанных POM/констант/фикстур.Readсоседнего spec в той же папке как эталон стиля. Сверь правила директории/сьюта (smoke / regress / api и т.п.) поCLAUDE.mdпроекта, если он есть. - Статический анализ (обязательно — дёшево и доказательно):
- Typecheck:
tsc --noEmit(или typecheck-скрипт проекта изpackage.json). Любая ошибка типов в новом коде = 🔴 Blocker. - ESLint: найди конфиг проекта и прочитай, какие правила реально включены (особенно из
eslint-plugin-playwright) — не предполагай по памяти. Вывод линта — источник правды. - Что реально ловит линт — проверка двухуровневая. (1) Плагина eslint-plugin-playwright нет вообще → floating promise, ручные ассерты и
networkidleневидимы; предложи подключить recommended. (2) Recommended подключён →missing-playwright-await,prefer-web-first-assertions,no-networkidleуже error и ловятся, ноno-wait-for-timeout,no-force-optionиexpect-expectтам только warn (сверено по v2.10.5) — без--max-warnings 0эти предупреждения не валят CI. Предложи поднять их до error.@typescript-eslint/no-floating-promisesтребует type-aware линтинга — включён мало у кого. Что осталось вне линта — проверяй вручную (A/C/H). - При необходимости — проверка форматирования (Prettier), если настроена в проекте.
- Typecheck:
- Чеклист. Пройди категории A–J ниже + K (правила вашего проекта). На каждое нарушение — severity +
file:line+ фикс. Глубже по правилу —references/rules-catalog.md. - Верификация стабильности (только если пользователь просит убедиться, что тест рабочий, и есть sandbox): прогон только этого теста в нативном параллелизме проекта (НЕ
--workers=1):
Pass-на-ретрае или плавающий результат = флак = 🟠 Major, чинить причину (гонки/гидратация/ожидания), а не прятать за retries.npx playwright test <file> --grep "<id>" --project="<projectName>" --retries=0 --repeat-each=5 - Отчёт — в формате из раздела «Формат отчёта».
Severity
| Метка | Значение | Типичные примеры |
|---|---|---|
| 🔴 Blocker | Тест сломан, недетерминирован или маскирует баг. Не мержить. | Ошибка typecheck; пропущенный await (floating promise); waitForTimeout/in-page setTimeout-пауза; pass только на ретрае; { force: true } / dispatchEvent / прямой setter в обход реального UI; тест без ассертов; test.only; условный expect, который может не выполниться. |
| 🟠 Major | Хрупкость или флак при смене контента/окружения; нарушение ключевого правила проекта. | CSS/XPath-цепочки вместо role/label; мгновенный count()/isVisible()/allTextContents() как gate; точные цены/тексты/даты вместо regex; нарушение конвенции проекта (импорт базового @playwright/test там, где проект требует кастомную фикстуру; пропущена обязательная проектная проверка — напр. монитор сетевых ошибок); waitForLoadState('networkidle'); тест зависит от состояния другого. |
| 🟡 Minor | Стиль/читаемость/поддерживаемость; на стабильность не влияет. | Нет test.step по бизнес-шагам; инлайн-комментарии вместо самодокументирования; .nth() где годится .filter(); рабочий, но не приоритетный локатор; неиспользуемый импорт/константа. |
| ⚪ Nit | Косметика. | Именование, порядок импортов, форматирование (если не ловит Prettier). |
Чеклист ревью
Каждый пункт — что искать; в скобках — severity нарушения. Развёрнутые примеры и пруфы — в каталоге.
A. Детерминизм, ожидания, асинхронность
- Нет
page.waitForTimeout(ms)и нетsetTimeout/sleepвнутриpage.evaluate(грепни оба). Ждать состояние, не время. (🔴) - Нет «висящих» промисов: каждый
expect,test.step, действие (click/fill/goto),waitFor*— подawait/return/void. Пропущенныйawait= молчаливый флак. (🔴) - Сеть — паттерн promise → действие → await:
const p = page.waitForResponse(...); await click(); await p. Объявление после действия = гонка. (🔴) - Нет
waitForLoadState('networkidle'). Навигация —goto(url, { waitUntil: 'domcontentloaded' }), без дублирующегоwaitForLoadStateследом. (🟠) - Производные/составные проверки (несколько связанных условий, замер коллекции сразу после появления) — в
await expect(async () => {...}).toPass({ timeout }), а не цепочкаawait-ов. (🟠) - Учтена SSR-гидратация: SSR-фреймворки (Nuxt/Next и др.) могут перемонтировать контент после гидратации → мгновенный
count()/allTextContents()сразу после появления ловит окно пустоты. Замер черезtoPass. (🟠)
B. Локаторы
- Приоритет:
getByRole({ name })→getByText→getByLabel→getByPlaceholder→getByAltText→getByTitle→getByTestId→ CSS (крайний случай) → XPath (почти никогда). (🟠 при CSS/XPath без причины) - Нет хрупких CSS-цепочек по структуре DOM (
div > div > span,.episode-actions-later). Ломаются при ребрендинге. (🟠) - Strict mode: локатор резолвится в один элемент; уточнение через
{ name }/.filter({ hasText })/.filter({ has }), а не.nth()..nth()— только с обоснованием. (🟡) - Плавающие элементы (дропдауны, тосты, модалки, портальный контент, iframe) ищутся глобально от
page, не от секции. (🟠) - Длинный
getByText('целое предложение')не используется как якорь — хрупко к правкам копирайта; брать стабильный фрагмент/role. (🟡)
C. Ассерты
- Только web-first (авто-ретрай):
toBeVisible/toHaveText/toHaveCount/toHaveValue/toBeChecked/toHaveAttribute/toHaveURL. Нетexpect(await loc.isVisible()).toBe(true)иexpect(await loc.count()).toBe(n)— не ретраятся. (🔴/🟠) - Каждый тест что-то проверяет (нет теста, который только кликает без
expect). (🔴) - Блок независимых проверок одной секции — через
expect.soft, чтобы собрать все падения разом. (🟡) - Нет ассертов на точные цены/числа/даты/динамический контент — regex или диапазон. (🟠)
- Известный незакрытый баг —
test.fail()(с единственным баг-ассертом), неtest.fixme(); рабочее поведение — отдельным обычным тестом. (🟡)
D. Изоляция и независимость
- Тесты независимы: состояние НЕ передаётся между тестами.
let xна уровнеdescribe, переинициализируемый вbeforeEach, — распространённый валидный паттерн; нарушение — когда тест читает результат другого. Прогон в одиночку и в любом порядке должен проходить. (🔴 если ломает изоляцию) -
describe.configure({ mode: 'serial' })— только при реальной зависимости, не «на всякий случай». (🟠) - Setup/teardown — в
beforeEach/фикстурах, без копипасты; созданные сущности (API) удаляются. (🟠) - Тест не зависит от внешних сайтов и third-party виджетов — тестируем только то, что контролируем; внешнее — мок/проверка факта запроса. (🟠)
E. TypeScript и линт
- Typecheck зелёный для нового кода (
tsc --noEmit, strict). (🔴) -
anyв POM / fixtures / utils — нежелателен, типизируй (Locator/Page/Route/APIResponse). Сверь с конфигом проекта:anyможет быть осознанно разрешён в спеках (напр. для мок-данных) — тогда там не флагай.@ts-ignore— только с причиной/тикетом. (🟠 для POM/utils) - Поля POM —
readonly Locator; фикстуры типизированы (base.extend<{...}>); тело ответа API типизируй явно, если на него опираются ассерты. (🟡) - Пропущенный
awaitлинт часто НЕ ловит (проверь конфиг: есть лиno-floating-promises/missing-playwright-await;valid-expectпокрывает лишь часть) → перечитай глазами, см. A2. (🔴) - Сверь модульную систему (ESM vs CJS) и стиль импортов (относительные vs алиасы) с фактическим кодом проекта — следуй существующему стилю, не навязывай свой. Неиспользуемые импорты/переменные/константы/методы POM убрать. (🟡)
F. Сеть и моки
- Моки (
page.route) — только для edge cases (5xx, пустой ответ, таймаут, офлайн). Позитивный happy-path — против реального API. (🟠) - Проверка контракта, где это суть теста:
waitForResponse(статус + тело) /waitForRequest+postDataJSON(). Для форм — инспекция payload на[object Object], пустые/несериализованные поля, а не «кнопка активна». (🟠) - Роуты ставятся до триггерящего действия; область — тест/фикстура, не глобально на suite. (🟠)
- Внешний хост, который может не отвечать (напр. внешний личный кабинет, платёжный шлюз): переход не проверяем «вглубь» — оракул это инициированный навигационный запрос +
route.abort(), иначе pending-навигация подвешивает teardown. (🟠)
G. Структура, читаемость, гигиена
- Логические шаги обёрнуты в
test.step('Императив', …)(видно в Allure/HTML/trace).return— снаружи коллбэка. Не дробить на каждое действие. (🟡) - Нет инлайн-комментариев в тестах — самодокументирование (осмысленные имена, semantic-локаторы, шаги). Контекст — в описании/аннотации репортера (напр.
allure.description), если проект их использует. (🟡) - Параметризация однотипных кейсов через
for...ofснаружиtest.describe, а не копии теста. (🟡) - Нет
test.only, закомментированных тестов, временных файлов/черновиков, отладочныхconsole.log/page.pause(). (🔴 дляtest.only/page.pause, иначе 🟡) - Имена тестов/шагов осмысленны; формат ID/тегов (
@allure.id:N, ключ ТК и т.п.) — как у соседних тестов в файле. (⚪)
H. Маскировка багов и флак
- Нет синтетических обходов реального UX:
{ force: true },dispatchEvent, прямой React/Vue-setter, ручной скролл вместо авто-actionability — если только это не оправдано контролируемым input'ом (напр. кастомныеdisplay:noneинпуты — проверь в браузере). Фикс должен ловить регрессию, если фича сломается, а не прятать её. (🔴) -
retries/mode: serial/увеличенный timeout не используются как «лекарство» от флака. Карантин допустим только временно, со ссылкой на тикет. (🟠) -
try/catchне глушит падения действий/ассертов (auto-waiting встроен;.catch()прячет баг). (🟠) - Пред-релизный тест (написан до выката фичи) падает честно, не спрятан за
skip/флагом. (🟠)
I. Спецслучаи по типу теста
- API: проверяется статус И тело; идентификаторы запросов — свежий
randomUUID()из встроенногоcryptoна каждый запрос (не тащи пакетuuid, если его нет в проекте); учтён rate limit; cleanup созданного. (🟠) - iframe:
frameLocator; контент ищется внутри фрейма. Новый таб:context.waitForEvent('page'). Download:waitForEvent('download')+ проверка имени. Upload:setInputFiles. Время:page.clock. Геолокация/права:grantPermissions/setGeolocation. (🟠 при ручных обходах) - visual:
toHaveScreenshotсanimations:'disabled'иmaskна динамику; эталоны — на платформе CI (macOS-эталон против Linux-CI = гарантированный diff). Только если тест-кейс требует эталон. (🟠)
J. Соответствие намерению (оракул реально проверяет заявленное)
- Тест проверяет то, что обещает имя/описание, а не суррогат. «Валидация формы» → инспекция реального payload, не только «кнопка активна». «Загрузка ещё» → реальная догрузка и сверка, не только клик. (🟠)
- Привязка к контенту структурная (наличие, непустота,
count > 0, regex), чтобы тест пережил смену копирайта/цен — особенно для регрессов после фикса. (🟠) - Оракул адекватен ограничению окружения: где UI не различает 404/5xx (одна заглушка на оба) — проверка сетевая, не «увидел текст ошибки». (🟠)
K. Правила вашего проекта (шаблон — заполните под свой репозиторий)
У зрелого тест-репозитория всегда есть конвенции, которые не проверит ни один универсальный чеклист. Зафиксируйте их здесь или в
CLAUDE.mdпроекта — тогда ревью будет ловить их нарушения. Типовые категории с примерами:
- Кастомные фикстуры: где импортировать
testиз кастомной фикстуры (./fixtures/custom-test) вместо@playwright/test, и какие обязательные проверки она даёт (напр. монитор сетевых ошибок, вызываемый в конце теста / вafterEach). (🟠) - Паттерны директорий: чем отличаются правила smoke / regress / api сьютов — композиция vs фикстуры для POM, репортер-аннотации, testMatch/testIgnore, куда добавлять новые тесты. (🟠)
- Окружения: тест и POM проверены на всех целевых стендах, не только на одном (DOM на тест-стенде может отличаться от прода); известные особенности стендов зафиксированы списком. (🟠)
- Skipped-гигиена: тест не добавляет постоянных skipped в штатные прогоны; окружение-специфичное исключается конфигом (
testIgnore/testMatch), runtimetest.skip— только для динамических условий (фича-флаг, известный баг с тикетом). Итог прогона: passed = ок, failed = проблема, skipped = требует объяснения. (🟡) - Зависимости/конфиг: не бампать версию
@playwright/testи не добавлять зависимости без сверки с CI (Docker-образ, lock-файл); не трогатьplaywright.config.tsбез необходимости. (🔴 если затронуто без запроса)
Формат отчёта
## Ревью: <файлы / scope>
**Статический анализ:** typecheck ✅/❌ · lint ✅/❌ · (прогон: N/N pass, --repeat-each=5)
### 🔴 Blocker (N)
1. `path/to/spec.ts:42` — <что не так>.
Почему: <ссылка на правило/категорию>.
Фикс:
```ts
// ❌ было / ✅ стало
🟠 Major (N)
…
🟡 Minor (N)
…
⚪ Nit (N)
…
✅ Что хорошо
- <что соответствует best practice — кратко>
Вердикт
<Готов к коммиту / К доработке: список Blocker+Major> · <команда для прогона с правильным --project>
Правила:
- Сортировка строго по severity (Blocker → Nit). Внутри — по файлу/строке.
- Каждый Blocker/Major — с конкретным фиксом (сниппет `❌ было → ✅ стало`).
- Чисто в категории — пиши «чисто», не выдумывай.
- В конце — однострочный вердикт и команда запуска с верным `--project`.
- Если просили применить фиксы — делай **итеративно** (одно изменение → typecheck/прогон → следующее), не переписывай рабочий тест целиком.