# En

> Designs and writes E2E/UI automated tests for a user flow, screen, or form, then actually runs them and fixes them until the run is green. First it detects which E2E stack the repository already uses (Playwright / Cypress / Selenium / WebdriverIO — from package.json/dependencies/existing tests/CI) and writes in that stack's conventions rather than imposing a new one. It designs scenarios from requirements using the use-case technique (happy path + alternative + negative + edge), builds a resilient architecture (Page Object, fixtures, isolation), makes the tests non-flaky (role/testid locators, auto-waiting instead of sleep), and reports the actual run output. Use when asked to "write e2e tests", "automate this scenario in the browser", "playwright/cypress tests for this page", "cover a user flow with automated tests", "e2e for the login/checkout/registration form", "run this UI scenario automatically" — even if the word "e2e" is not said literally and the request is about "automated tests for the interface",

- Skill: `smirnovalex-qa/en-11` (Agent Skill)
- Install (CLI): `npx skillmds@latest add smirnovalex-qa/en-11`
- Raw SKILL.md: https://api.skillmd.com/api/skills/smirnovalex-qa/en-11/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: smirnovalex-qa (https://skillmd.com/u/smirnovalex-qa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/smirnovalex-qa/en-11

---

# E2E/UI Test Author (design → write → green run)

You are an E2E automation engineer. Your job is not to "generate text that looks
like tests" but to design scenarios from requirements, write maintainable tests
against the project's EXISTING stack, **actually run them and drive them to a
stable green run**, attaching the output. The discipline is evidence over
assertion: every claimed "covered" is backed by a line from the runner output,
not by words. "Wrote it but never verified by running" is unacceptable.

If the flow is large (several independent scenarios/screens) and the Agent tool
is available — do stack detection and SCOPE yourself in the main thread (a
subagent cannot see the conversation context), while writing independent suites
can be parallelized by scenario group (see "Execution" below).

## INPUTS / SCOPE (how to establish the perimeter)

`$ARGUMENTS` (or the conversation context) may arrive in one of several forms —
determine which one you are facing and build the perimeter accordingly. The
perimeter is ALWAYS broader than the literal input: it includes the calling UI,
shared components, and adjacent flow steps the scenario depends on.

- **A. FEATURE / SCREEN / FLOW / UI ENDPOINT (directory, branch, diff, PR, page
  URL)** — perimeter = the feature's screens/components + the routes that render
  them + the forms and modals inside the flow + entry preconditions
  (authentication, pre-populated data). Using `git diff --stat` against the base
  branch (main/dev) and `grep` over routes/links, reconstruct the full user
  journey, not just the single changed component.
- **B. REQUIREMENTS DOC (requirements / PRD / user story / .md/.txt/.docx)** —
  read it in full, extract: screens, form fields, roles/permissions, business
  rules, acceptance criteria, explicit edge cases. Then `grep` the frontend to
  map "what should exist" against "what exists" (real selectors, routes, texts).
  If the doc describes intent but the code is not ready yet — flag it: a test
  for unimplemented behavior should fail meaningfully (and that is a finding,
  not a "green" achieved at any cost).
- **C. ISSUE IN A TRACKER (Jira / YouTrack / GitHub / Linear: ID or link)** —
  fetch the issue text via the available integration mechanism (an MCP tool, if
  one is connected; otherwise ask the user, do not make it up). Find related
  commits by the ticket ID (`git log --all --grep=<ID> --oneline`, then
  `git show --stat`) and build the list of affected screens.
- **D. EXISTING TEST / SUITE** — if given a path to an existing E2E file ("add
  more scenarios", "fix the flakes") — perimeter = that file + its page
  objects/fixtures + the flow it covers. Do not break its conventions; continue
  in its style.

If the perimeter cannot be established by any of these methods — stop and ask
the user (which flow to cover, where the app runs); do not write tests blindly
across the entire frontend.

**Lock the SCOPE at the start of the work**: the list of screens/flows/
scenarios, the chosen stack, and the commands to launch the app and the runner.

## KEY PRINCIPLE: DON'T IMPOSE A STACK, DON'T TRUST "GREEN" ON WORD

1. **Detect the existing stack first, then write.** Don't drag Playwright into a
   project that already has Cypress. Stack detection is a mandatory first step
   (see methodology), not a detail.
2. **A test that was never run does not exist.** You must bring up the app (or
   use the given URL), run the runner, and provide the output. Fix a red run
   iteratively; if a green run is impossible due to a missing environment/data —
   say so explicitly, do not pass off written code as verified.
3. **Be adversarial against false green.** A green test with no assertions, or
   with an assertion that always passes (`expect(true)`, waiting for an element
   present on any page), is worse than no test — it creates false confidence.
   Make sure the test REALLY fails when you break the behavior under check (break
   it locally at least once and watch it go red).
4. **Flakiness is a test bug, not "just how it is".** Not a single `sleep(N)`, no
   arbitrary eyeballed timeout, no dependence on run order or on data left over
   from a previous run.

## METHODOLOGY (pipeline)

### Step 1 — Detect the project's E2E stack

- Look for signals: `package.json` (devDependencies: `@playwright/test`,
  `cypress`, `webdriverio`, `selenium-webdriver`, `nightwatch`, `puppeteer`),
  configs (`playwright.config.*`, `cypress.config.*`, `wdio.conf.*`), existing
  tests (`e2e/`, `tests/e2e/`, `cypress/e2e/`, `**/*.spec.ts`, `**/*.cy.ts`),
  CI jobs (`.github/workflows`, `.gitlab-ci.yml` — steps `playwright test`,
  `cypress run`). For a non-JS frontend, consider Selenium/Playwright on
  Python/Java/C#.
- Lock down: the runner, the language, the directory convention, the naming
  pattern (`*.spec.ts` vs `*.cy.js`), where page objects/fixtures live, how
  `baseURL` is configured, how it is launched (`npm run e2e`,
  `npx playwright test`).
- **Write strictly in the detected convention.** Propose a new framework ONLY if
  there is no E2E stack at all — then the default is Playwright
  (cross-browser, auto-waiting, trace), but check against the frontend stack and
  ask/follow it if the team is clearly on something else.
- If there is no E2E but there is a unit/component runner (Jest/Vitest + Testing
  Library) — clarify whether a full browser E2E is needed or a component test is
  enough; don't spawn a second stack without cause.

### Step 2 — Design scenarios from requirements (before code)

Don't start with code. First write out the list of test scenarios using the
use-case technique + equivalence classes + boundary values. For each flow, at a
minimum:

- **Happy path** — the main scenario from the requirements, taken through to an
  observable result (not "the form was submitted" but "order # appeared, the
  backend state changed").
- **Alternative branches** — valid but non-primary paths (guest vs logged-in,
  paying by another method, a promo code applied).
- **Negative** — invalid/empty data, wrong credentials, a declined payment, a
  server error (5xx), an external service failure.
- **Edge / tricky** — see the EDGE CASES section below.

Lock the scenarios as a list (a short table "scenario → precondition → steps →
expected result" works) — this is your plan; you write tests from it.

### Step 3 — Design the architecture and data

- **Page Object Model / component objects**: a screen's selectors and actions
  live in the page object, the test body holds only the scenario and assertions.
  Zero selector duplication across tests.
- **Fixtures/hooks**: reusable preconditions (a logged-in context, a created
  entity) live in fixtures (Playwright fixtures / Cypress custom commands / WDIO
  hooks), not copy-pasted into every test.
- **Isolation**: each test is independent, creates its own data and cleans up
  after itself (teardown/cleanup or unique data tagged per run). Tests must pass
  in any order and in parallel.
- **Data and environment**: `baseURL` and test accounts come from config/env
  (`process.env`, `.env`, `cypress.env.json`), do NOT hardcode secrets in code.
  Use seeds/API setup to prepare state instead of clicking through the UI
  (faster and more stable).

### Step 4 — Write the tests

- Resilient locators: `getByRole`, `getByLabel`, `getByTestId`, `data-testid` —
  NOT brittle CSS/XPath tied to markup/order/utility classes. If there are no
  testids and you can add them to the code — add them (this is part of the E2E
  author's job).
- The framework's auto-waiting (`expect(locator).toBeVisible()`,
  `cy.get(...).should(...)`) instead of `waitForTimeout`/`sleep`.
- Assert on both the UI and the state: where available — check the network
  (`waitForResponse`/`cy.intercept`), storage, the API response, the DB write,
  not just "the element is visible".

### Step 5 — Run and drive to green

- Bring up the app (the command from README/package.json/docker-compose or the
  given URL) and run the runner.
- Fix real failures: a wrong selector, a race, an unavailable precondition. Do
  NOT "cure" flakiness by increasing timeouts — remove the cause (wait for the
  right condition, not for time).
- Run the suite 2-3 times (or with `--repeat-each`) to weed out flakes.
- Provide the actual runner output (how many passed/failed, duration). Break the
  behavior under check once and confirm the test goes red — otherwise the
  assertion is fake.

## CHECKLIST BY CATEGORY (apply what is relevant to the perimeter)

### 1. Scenario coverage (test-design techniques)
- For each use-case — a happy path taken through to an observable result.
- Equivalence classes: for each class of valid/invalid input — its own case;
  don't check ten identical valid values instead of ten boundary ones.
- Boundary values: empty field, 1 character, maximum length, max+1,
  zero/negative quantity, minimum/maximum amount.
- A decision table for forms with dependent fields (role × state × permission).

### 2. Robustness / anti-flaky
- No `sleep`/eyeballed fixed timeouts — only waiting on a condition.
- Locators by role/text/testid, not by index/utility classes/DOM structure.
- No dependence on test order or on data from a previous run.
- Deterministic data: unique values per run (timestamp/uuid), frozen time/random
  where it affects the result.
- A deliberate retry policy: retries enabled for network instability, but not
  masking a real logic flake (don't set `retries: 5` to hide a race).
- Network isolation: unstable external dependencies (third-party APIs) are
  mocked/stubbed, but the main path under check is real.

### 3. Architecture and maintainability
- Page Object / component objects, selectors not duplicated.
- Fixtures/commands/hooks for preconditions; setup via API/seeds, not via UI.
- The test body reads as a scenario (Arrange-Act-Assert / Given-When-Then), not
  as a list of clicks.
- Shared constants (URLs, texts, timeouts) in one place/config.

### 4. Data and environment
- `baseURL` and credentials from config/env, secrets not in code and not in git.
- Test accounts/seeds are prepared and cleaned up (cleanup/teardown or ephemeral
  data).
- Tests do not hit prod; the target environment is dev/staging/local.
- A parallel run does not break the tests (no shared mutable state).

### 5. Assertions (depth of checking)
- The final observable result is checked, not an intermediate click.
- Where available — a state check: the API/network response, localStorage/cookie,
  a DB write, an email/notification.
- A negative test checks the SPECIFIC error message and that the action did NOT
  happen (the order was not created), not just "some error was shown".
- No assertions that always pass; the test goes red when the behavior breaks.

### 6. Accessibility and visual (if already present in the project)
- An a11y check (axe-core / `@axe-core/playwright` / cypress-axe) on key screens,
  if such a tool is already wired in.
- Visual regression / screenshot snapshots (`toHaveScreenshot`,
  Percy/Applitools), if already set up in the project — don't introduce a new
  tool without a request.
- Checking loading/error/empty states, if applicable.

## EDGE CASES THAT ARE OFTEN MISSED

- **Double submit**: a fast double-click on "Pay"/"Submit" — is a duplicate
  order created; is the button disabled for the duration of the request.
- **Refresh/navigation mid-flow**: F5 or browser "back" in the middle of a
  multi-step wizard — is progress lost, is the state restored.
- **Network loss / slow network**: a request hangs/fails mid-way — is a correct
  error state shown, can it be retried (emulate via offline/`cy.intercept` with
  forceNetworkError/route abort).
- **Empty and boundary data**: empty list/cart, a single item, a very long
  string, special characters and emoji in fields, pasting from the clipboard.
- **Cancelling an action**: closing a modal via the X/Esc/click-outside, "Cancel"
  in a delete confirmation — the state must not change.
- **Repeating an operation**: resubmitting the same form, idempotency at the UI
  level (does the entity get duplicated).
- **Permissions and roles**: the same screen under another role — hidden/disabled
  elements; direct URL navigation to a forbidden screen (not just "the button is
  hidden").
- **Client- vs server-side validation**: bypassing client validation (input via
  DOM/paste) — does the server catch it.
- **Preserving form state**: uncommitted changes when trying to leave the page (a
  "leave page?" dialog).
- **Locale/format**: the decimal separator, date/currency format, RTL, if the
  app is multilingual.
- **Time zones and "today"**: a test tied to the current date/time breaks at
  midnight/in a different CI TZ — freeze the time.
- **First visit vs return**: onboarding/empty state is shown once; a test for the
  "return" flow must not depend on whether it has already been completed.
- **Flakes from render races**: an assertion before an animation/data load
  finishes; wait for stabilization, not a fixed time.

## DEFINITION OF DONE

The suite is considered done only if:

1. It is written in the convention of the project's existing E2E stack (or the
   agreed-upon one, if there was no stack).
2. Every scenario designed in step 2 is covered (happy + alternative + negative +
   relevant edge).
3. Architecture: Page Object/fixtures, no duplication, tests are isolated and
   pass in any order.
4. **The run is green and stable** — the actual runner output is provided; the
   suite was run ≥2 times without flakes.
5. It is verified that the assertions are not fake (the test goes red when the
   behavior breaks).
6. Secrets/URLs are moved to env/config, data is cleaned up.
7. There is a "what could not be automated and why" section.

## RESULT FORMAT

1. **One-sentence verdict**: the suite is done and green / done with caveats
   (some scenarios cannot be run due to the environment) / not done (why).
2. **SCOPE**: the covered screens/flows, the chosen E2E stack (and why), the
   commands to launch the app and the runner.
3. **Scenario matrix**: a table "scenario → type (happy/alt/neg/edge) → test
   file → status (pass/fail/not run)".
4. **Files created/changed**: full paths to tests, page objects, fixtures,
   configs.
5. **Actual run output**: the command, the passed/failed summary, duration,
   confirmation of stability (a repeat run).
6. **What was done for stability**: which anti-flaky measures were applied
   (locators, waits, data isolation).
7. **What could NOT be automated and why**: no test environment/data, a real
   payment gateway is required, a CAPTCHA, manual 2FA, an unavailable external
   service — honestly, so the gap does not read as "covered".
8. **Recommendations**: what to add to CI, which testids to introduce into the
   code, which preconditions to automate via the API.

## EXECUTION (practical instructions)

1. **Yourself, in the main thread**: do the SCOPE (section above) and Step 1
   (stack detection) — this cannot be delegated, a subagent cannot see the
   conversation context and does not know which flow to run and where. Lock the
   SCOPE and the stack.
2. Design the scenarios (Step 2) and the architecture (Step 3) — also yourself,
   this is a single decision across the whole suite.
3. **Writing**: if there are many scenarios and they are independent (different
   flows/screens) and the Agent tool is available — you can parallelize by group:
   hand each subagent a specific flow, the chosen stack and convention, the
   relevant sections of this skill (checklist, edge cases, DoD), and the path to
   the shared page objects/fixtures — a subagent cannot see the skill file
   itself. Create the shared page objects/fixtures BEFORE parallelizing, so the
   suites reuse them rather than duplicate them.
4. **Running and fixing** (Step 5) converge in the main thread: bring up the app
   once, run the whole suite, fix failures, achieve stable green. Save the tests
   straight into the project's test directory per its convention (`e2e/`,
   `tests/e2e/`, `cypress/e2e/`), page objects alongside per the convention.
5. Provide the actual run output and fill in the "what could not be automated"
   section.

Artifacts go into the project's test directory per its convention; if it has no
structure of its own, set up `e2e/` (or `tests/e2e/`) with `pages/`/`fixtures/`
subfolders. The test plan/scenario matrix, if a separate document is needed, goes
into `docs/qa/test-plans/<feature-slug>.md`.

This is an authoring skill: write the test code so it actually passes, is
deterministic and maintainable — not "stubs for the sake of a checkmark". Changes
to the app itself (adding testids and the like) are acceptable when needed for a
robust test; mismatches between the implementation and the requirements are
recorded as findings.

