Writes UI test cases from SRS criteria and screen specs, then runs them as Playwright tests against the frames. Use for «тест-кейсы», «прогони тест-кейсы», «приёмочное тестирование». One-off check: playwright-cli.
Resources
2Install
npx skillscat add idontlikedrinkpivo/web-dev-pipeline-codex-plugin/ui-test-cases Install via the SkillsCat registry.
Codex runtime. Before using this skill, read
../executor-catalog/references/codex-runtime.md. It defines tool mappings, named-agent dispatch and stage-chat orchestration; use it for inherited Claude Code terms below.
UI Test Cases
The SRS says what the system must do; the mockups and the screen specs say
how a person does it on screen; nothing so far says how anyone will check, through the
interface, that the built product does both. A unit test proves a method,
an API test proves a status code, and neither notices that the «Отменить»
button never appears, that the error text differs from the spec, or that a
screen looks nothing like its frame. These cases close that gap: written
once from the documents while the design is fresh, run once the code
exists, kept as a Playwright suite that CI repeats on every change.
Write cases and reports in Russian. Do not translate ids (TC-, UC-,AC-, S-), operationIds, paths, or the Verdict: line.
Not this skill's job
| Not here | Owner |
|---|---|
| Unit, use-case, adapter, or API tests | the plan's units in work |
| Deciding how a screen behaves | ui-design and screen-spec — a case that needs a decision the screen spec did not make is a gap, not a guess |
| Fixing the app when a case fails | a new plan increment (plan → work); this skill reports |
| CI wiring | ci-pipeline runs test:e2e when it exists |
Which mode
The stage that offers this skill names the mode. Opened directly: no
cases file beside the screen specs → write; cases exist and the app's code
exists → run; asked for both («составь и прогони») → write, then run in
the same sitting. Unclear → one question.
Handed to another person? In the session of a person who took
mode write (pipeline → references/team.md): the version comes from the
branch iteration/<version>, inputs arrive by git pull --rebase, only this
stage's documents are written — each committed the moment it is finished
and pushed by the plugin once final, at the stage's close (pipeline →
Commit each document) — and the stage closes as usual, its stamp included.
After the last stage this person took, the «готовы и запушены»
line replaces the offer of a stage someone else has.
Mode write
When: after screen-spec finished. Without screen specs, stop and namescreen-spec: there is nothing to step through.
Inputs: the SRS documentation/requirements/srs/srs.md when this repo
has it (acceptance criteria, BRs, roles; a frontend repo whose SRS lives
elsewhere works from the screen specs alone, and its coverage table lists UC
and state rows instead of AC ids), every screen spec (elements and their
conditions, «Поля ввода», «Состояния экрана», «Ошибки ответов», exact texts
quoted from the frames, Figma ids), the frames register (screens, app-wide
states). Not ui-wishes.md: it is never a source of expected behaviour.
Dispatch: resolve ui-test-writer through executor-catalog and dispatch
it under the Dispatch contract with references/writer-prompt.md, without
asking which model. The session writes no case; it only moves an old
single file first (Artifact below). A BLOCKED with a question only the
user can answer: ask it in the chat (executor-catalog → Escalation),
paste the answer into the packet's ANSWERS and dispatch the writer again.
Progress file. Before the dispatch, createdocumentation/plans/<version>/progress-test-cases.md perpipeline → references/progress-files.md — one row per section of the
set (below) plus «Без интерфейса», all ⏳ ждёт — and pass it to the
writer as PROGRESS FILE. A full set runs to hundreds of cases; the
writer marks each section as its cases are written. For the chat, runpython3 "<folder of this SKILL.md>/../pipeline/scripts/progress_link.py" documentation/plans/<version>/progress-test-cases.md (add --desktop
when the session has the built-in browser) and paste the line it prints as
is — before the dispatch and each time one returns; each time one returns, check the context too
(pipeline → references/context.md → When to hand over). It carries
the count and the link to the page that reloads itself; a link written by
hand opens the Markdown source or a page that never refreshes.
What a case set must hold:
- One case per acceptance criterion that a person can reach through the
interface — main flow, every Alt and every Exc. An AC with no screen (a
scheduled job, an email content check) is listed under «Без интерфейса»
with the reason, not dropped. So is a check the interface cannot reach —
input the client itself never sends (a malformed email the form blocks
before sending): «Без интерфейса — проверяется API-тестами», not a UI case
that a run would later have to drop. - One case per row of «Состояния экрана» (Empty, Error, Forbidden,
Loading where it is observable) and per user-causable row of «Ошибки
ответов» that no AC case already covers. - One case per «Поля ввода» rule: an invalid value, the moment it is
checked, the exact error text. - Roles: a case for every forbidden action per role the SRS names (the
resident who tries to cancel someone else's booking).
Two readers, two layers. A case is read by a person — a tester, an
analyst, the owner checking what will be tested — and turned into a test
by the runner. The body is written for the person; everything only the
runner needs goes into one «Трассировка» line under it.
- The body speaks the user's language. Roles and screens by name, the
id in brackets on first mention («Резидент», «Расписание переговорных
(S-1)»). Steps are things a person does, named by the label the user
sees on the screen, without a widget, a gesture, CSS or coordinates:
«Выбрать «Отменить бронь»», not «нажать красную кнопку справа» and not
«выполнить действие эл. 13». A designer who turns a dropdown into a
context menu must not have to rewrite a case; a label survives that, and
the e2e test finds the element by the same accessible name. - Every step is an action; «Посмотреть на …» when the step only reads
the screen, never a bare «Проверить эл. 4». - Take the path a test can walk. When several paths reach the same
state, step through controls the screen spec names (a field, a button
with its label) rather than one it leaves without an accessible name (a
free cell of a grid, an icon with no label). A state reachable only
through an unnamed element still gets its case, and the gap goes to the
set's spec gaps («эл. 9 без доступного имени»), soscreen-speccan fix
it. - Real values, never placeholders. Dates, times, names and counts in
steps and expected texts are concrete, computed from the preconditions
(«завтра, 21.10», «Отменено 2 брони»); a date relative to the run is
written relative («Д+1») and defined once in the README.{дата}or
«по кадру» inside an expected text is a case the runner cannot check. - «Что должно быть» is what a person sees. The exact texts the screen
spec quotes from the frame — its «Тексты» table, or a text layer marked
«текст слоя» in a designer's file with no annotation — each labelled — «заголовок
«…», ниже текст «…»», «под полем «Дата»: «…»», «уведомление: «…»». No
field names, derived values, response bodies, status codes or ARIA
attributes in the body; accessibility in words: «сообщение зачитывает
экранный диктор», «следующий Tab — «Повторить загрузку»». A text the
spec marks missing (текст — нет в «Тексты», orтекст — нет на кадре
in a designer's file) is neither composed nor marked «по кадру»: the case names the state without a quote, and the missing text
goes to the set's spec gaps forscreen-spec. - A server failure is a precondition in words («сервер не может отдать
расписание»), its mock goes into the trace line. - Preconditions name the data («у резидента бронь «Байкал» завтра
10:00–11:00»), so the runner can seed it and a person can set it up. - «Проверяет» opens the case: the requirement ids it proves (AC, BR,
NFR, or the spec row), so an analyst traces it in a glance. - The trace line is mandatory and exact, in one fixed syntax a script
can check:кадр <nodeId>[, <nodeId>]· the state row · per stepшаг N → эл. M[, эл. K] (<nodeId>)·`operationId` → <status>
(mockbefore the status for a forced outcome) · any ARIA expectation.
A case without it is not done: the runner builds its locators, mocks and
frame comparison from it. - The trace is checked at three points, never trusted.
scripts/check_trace.pyresolves every reference against the current
documents — the step exists in the table, the element in the screen spec,
the frame in the spec or the register, the operation and its status in
the OpenAPI. It runs at this stage's close (below), at the start ofscreen-spec(when cases already exist) and indocs-consistencybefore
the plan; each sends what it finds
toui-test-writer, which rewrites that case's trace from the current
spec — never patches a number.
Artifact: the folder documentation/ui/test-cases/, beside the frames
register and screen-specs/ (with several UI products, one perdocumentation/ui/<product>/). A full set runs to hundreds of cases and
thousands of lines; one file per section lets the writer, the runner and a
reviewer read only the part they work on, and keeps a change to one screen
in one or two files.
documentation/ui/test-cases/
├── README.md index and everything the sections share
├── 01-login-session.md 1. Вход, сессия и выход — TC-1 … TC-25
├── 02-room-schedule.md 2. Расписание переговорных
└── …README.md— the frontmatter (title,updated,sources:
pinning the SRS and every screen spec aspath@<version>); «Как читать
кейс», the roles and test data, the general handler's texts — whatever
every section shares; the «Разделы» table№ | Файл | Экраны и сценарии | Кейсы(theTC-range and the count); then the coverage table, the
screen-states → cases table, «Без интерфейса» and the spec gaps found
while writing.- One file per section,
NN-<slug>.mdwith a Latin slug: the heading# N. <Раздел> (S-ids, UC-ids)and its cases, nothing shared repeated.
A section holds one screen's flow or a tight group of flows; a screen with
many flows spans several sections, and a section past ~600 lines is
split. TC-ids run across the whole set, never renumbered or reused; a
new case takes the next free number, in the section where it belongs.- Unversioned: an increment edits the files in place.
- An existing single
test-cases.md(written before this layout) is
moved into the folder by the session before the writer is dispatched (the
writer does not commit): its## N.sections become section files, the
shared parts and the closing tables go toREADME.md, every case is
copied unchanged with its id, and the old file is deleted — one commit
of its own («docs: тест-кейсы разложены по разделам»; a move needs no
request from the user, git keeps the old file's history). The move
rewrites no case; the writer then gets the folder.
## TC-7. Резидент не может отменить бронь, которая уже началась
**Проверяет:** UC-2 AC-Exc-2 · BR-4
**Кто:** резидент · **Экран:** Расписание переговорных (S-1), окно «Отмена брони»
**Подготовка:** резидент вошёл; у него бронь «Байкал», начавшаяся 10 минут назад
| # | Действие | Что должно быть |
|---|---|---|
| 1 | Открыть расписание на сегодня | бронь «Байкал» отмечена как своя |
| 2 | Выбрать свою бронь «Байкал» | открылось окно «Отмена брони» |
| 3 | Выбрать «Отменить бронь» | в окне текст «Бронь уже началась, отменить нельзя»; в расписании бронь осталась |
<sub>Трассировка: кадр 12:160 · состояние «Уже началась» (M2) · шаг 2 → эл. 11; шаг 3 → эл. 3 (12:160) · `cancelBooking` → 409 BOOKING_STARTED</sub>README.md ends with the coverage table, one row per AC: UC-n AC-… →
its TC- ids, or «Без интерфейса» with the reason. An AC with neither is
the writer's failure, not the reader's.
Two scripts close the stage, never trusted to the writer's word. When
the writer returns, the session runs, from the repository root:
python3 "<folder of this SKILL.md>/scripts/check_trace.py" --docs documentation --quiet
— every «Трассировка» resolves: the step, the element, the frame, the
operation and its status;python3 "<folder of this SKILL.md>/scripts/check_coverage.py" --docs documentation
— every AC of the SRS has a row naming an existingTC-or «Без
интерфейса», and no row or «Проверяет» line names an AC the SRS lacks
(a repository without the SRS is not checked).
What either prints goes back to ui-test-writer (MODE increment, only
those lines), then both run again — round after round while each round
fixes something (pipeline → references/convergence.md). A line no round
can fix — the case needs an element, a frame or an operation the documents
do not have — is a spec gap in README.md and a row in open-questions.md,
named at the close. Only with both scripts at exit 0, or every remaining
line written down this way, is the set final: its commit, then the push
(pipeline → Commit each document).
Mode run
When: after work closed its run and code-review-full passed (or the
user chose to go on without it). Cases must exist; without them, offer
mode write first.
The app must start locally. Read the project's verify skill
(.agents/skills/verify/) and run its doctor first; with none, write and
prove it before the dispatch (deploy-topology →references/verify-skill.md), from .claude/launch.json, the README and
the test compose file. Pass its path to the runner as VERIFY. The runner
starts the database and the app, seeds the preconditions through the API
or the seed scripts the project has, and signs in with test credentials
from the project's seed or example config. External sign-in (SSO, SMS)
uses the test stub the architecture names; when there is none, that is aBLOCKED with the gap, not an improvised bypass.
Parallel stands. Tests that share one stand run one at a time; speed
comes from more stands, each with its own database and storage and its
share of the test files. Before the dispatch, readreferences/parallel-stands.md: measure how many stands this machine
holds, start them with the project's scripts/e2e-stands.sh, and pass them to the runner
as STANDS. A stand that cannot be copied yet (a fixed port, acontainer_name, no scripts/e2e-stands.sh) is not a reason to run long
on one: when the machine holds more than one stand and the run has more
than one test file, ask once before the run — make it copyable now and run
on N, or one stand (references/parallel-stands.md → A stand that cannot
be copied yet).
A frontend repo without its backend (the API is a separate service): the
runner mocks the API at the network boundary (Playwright page.route) with
responses shaped by documentation/api/openapi.yaml — each case's
preconditions become the mocked data, each error case the mocked status. The
report says the screens were checked against the contract, not a live
backend.
Two passes. The run is two passes with a bar each, so the user always
sees how far the full picture is:
- Pass 1 — the whole set, once. Every case is written and run once and
gets a pass-1 result:✅ прошёл,❌ не прошёл(with what was seen
against what was expected),⛔ заблокирован: <причина>(a missing
piece that can be added — a test stub for sign-in or SMS), or⏭ не автоматизирован: <причина>(the case cannot be checked honestly
by an interface test on the shared stand — it needs a storage failure
that would break the stand for everyone, an app restart with other
settings, or a state the stand never has; the comment says how to check
it by hand). No test is
fixed and nothing is triaged in this pass — the point is the whole set at
100% first, so repeated causes show up as repeats instead of being fixed
one case at a time. When the cases run in several modes (two sign-in
modes, two roles of one case), each case × mode is its own row and its
own unit of the bar. - The one exception — a shared break. When several cases in a row fail
for one cause in shared test code (sign-in under a role, data setup,
opening a section), the pass pauses: the shared helper is fixed and the
cases it failed are re-run, then the pass goes on. Hundreds of identical
failures say nothing about the app. - Pass 2 — triage of the failed. A second section of the same report,
with its own bar over the pass-1❌ не прошёлcases. Each is triaged:
test defect — the test is fixed and the case re-run, round after
round while each fix changes the failure (✅ тест исправлен, прошёл);
after two failed fixes on one assumption the assumption is checked
before a third (pipeline→references/convergence.md→ Attack the
premise); a test that fails the same way after a fix is⛔ тест не удалось исправить;
app defect — the app contradicts the spec (❌ дефект приложения);
mockup mismatch — works, but the screen differs from the frame
(🖼 расхождение с макетом); spec gap — the spec never said
(❓ пробел в ТЗ). App defects are not re-run: a fix plan fixes them.
The verdict is set only after pass 2.
The report is live. documentation/plans/<version>/test-run.md, in
the open version folder; read references/run-report.md before creating it
— the one place for its shape: a section per run and repeat, «Общий итог»,
bars, statuses, the verdict and the lists under it. No open version folder
→ say so and ask per pipeline → "Asking before a transition" which
version this run belongs to; do not invent one. Before the first run's
dispatch create it as run-report.md → Where and what describes, every case⏳ ждёт and Verdict: идёт прогон; before a later run, append its section
and turn the verdict to идёт перепрогон. Then run python3 "<folder of this SKILL.md>/../pipeline/scripts/progress_link.py" documentation/plans/<version>/test-run.md (add --desktop when the
session has the built-in browser) and paste the line it prints in the chat
as is — never a link written by hand (it gives the page that reloads
itself, served in the desktop app). Paste it again each time a runner
dispatch returns, after each repeat, and when a run's разбор is added. A
run resumed after a break or a context compaction keeps the report: every
case without a result in the current pass goes back to ⏳ ждёт, the line
is pasted again, and only then the runner goes on. The runner keeps the
report per run-report.md → How it is kept.
Dispatch: resolve ui-test-runner through executor-catalog withreferences/runner-prompt.md — the one place for the runner's rules: a
spec file per section file, the parts of playwright-cli it takes, thetest:e2e script ci-pipeline runs, the cases one at a time on each stand
(references/parallel-stands.md), each frame
compared once per nodeId, the ux-patterns screen-check items, the
triage. Two of them bind the session too: the E2E folder and its config
belong to this suite — ui-test-cases owns them, and a screen unit inwork adds no spec there (frontend → Verification); and the runner
never edits application code. After pass 2 it sets the Verdict: line —PASS, DEFECTS or BLOCKED (run-report.md → The verdict and the lists
under it).
A DEFECTS verdict names, per defect, the unit or screen it points at, so
the next step is one increment plan of fix units, not a hunt. Ask about that
per pipeline; do not fix here.
A spec gap goes to the user as one plain line per grill-me → How a
question is shown — the situation in everyday words, what the screen does
now, the recommendation, and whether it means a spec edit — never as a
bare «пробел в ТЗ S-13». A spec gap, or a defect the user rules is the spec's and not the app's
(«так и должно быть»), is a changed decision: the screen spec — and the SRS
or the API when the rule lives there — and the cases that test it are
fixed and committed at once, with no version change (pipeline →references/decision-changes.md), and those cases are re-run against the
new text before the verdict.
The session commits the suite, test(e2e): пользовательские тесты <version>, so CI and the next run start from it. The report stays on disk
in the version folder — documentation/plans/ is out of git.
A failing test for a real defect stays in the suite: it is the check the
fix plan must turn green.
Before you finish
- write:
check_trace.pyandcheck_coverage.pyexit 0, or each line
they still print is a spec gap with itsopen-questions.mdrow; every
AC has aTC-or a «Без интерфейса» line; every state row,
user-causable response outcome and «Поля ввода» rule has a case; steps name actions, not widgets; expected texts are
the ones the screen spec quotes, and each text it lacks is a spec gap forscreen-spec, not a case marker. - run: the suite is ready to commit under the E2E folder with
test:e2e;
pass 1 reached 100% and every failed case went through pass 2; no
application file changed; theVerdict:line under the last table is the
verdict.
References
references/writer-prompt.md— packet forui-test-writer.references/runner-prompt.md— packet forui-test-runner.references/run-report.md— the run reporttest-run.md: bars,
sections per run and repeat, example, statuses, verdict. Read before creating it.references/parallel-stands.md— several identical stands for a long
run: what makes a stand copyable, how many the machine holds, the split.playwright-cli—references/test-config.md(the stand-ready
config) and the parts ofreferences/test-generation.mdthatreferences/runner-prompt.mdnames; locators, traces, screenshots.ux-patterns— what a frame comparison may flag besides the spec.pipeline— where both modes sit.