agentleFS
a-novel-kita-novel-kit/stack/.agents/skills/write-frontend-tests/SKILL.md

write-frontend-tests

Skill2 starsChanged 4 days agoMITView on GitHub

Test frontend behavior and change tests, stories, fixtures, mocks, or test configuration. Load when behavior needs coverage; always pair with write-frontend.

3 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

39 rules. 0 are common, 35 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 35

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Read the production behavior before writing tests.

    line 11
  2. Distinctive:

    Follow develop-feature. Early stories make a draft reviewable.

    line 17
  3. Distinctive:

    Use the smallest layer that proves the user-visible contract:

    line 25
  4. Distinctive:

    Test pure parsing, formatting, and state transitions as unit tests.

    line 27
  5. Distinctive:

    Test component rendering and interaction through the DOM with Testing Library.

    line 28
  6. Distinctive:

    Use Storybook stories as the exhaustive catalog of reusable component states.

    line 29
  7. Distinctive:

    Use real-browser end-to-end tests for routing, focus across pages, browser APIs, hydration, multi-component journeys, and behavior an …

    line 31
  8. Distinctive:

    Do not duplicate the same assertion at every layer.

    line 34
  9. Distinctive:

    Use test IDs only when no semantic query exists.

    line 40
  10. Distinctive:

    Use async queries for async UI.

    line 45
  11. Distinctive:

    Prefer explicit assertions over broad snapshots.

    line 47
  12. Distinctive:

    Default, supported variants, and boundary values.

    line 54
  13. Distinctive:

    Do not manufacture cases that the public contract cannot reach.

    line 65
  14. Distinctive:

    Keep tests deterministic and independent of order.

    line 69
  15. Distinctive:

    Use real parsing, validation, and component composition when inexpensive.

    line 73
  16. Distinctive:

    Use framework-supported request interception for network tests.

    line 76
  17. Distinctive:

    Use Vitest APIs documented for the installed version.

    line 91
  18. Very specific:

    Keep vi.mock scope and hoisting rules visible.

    line 104
  19. Distinctive:

    Use the repository user-interaction helper when present.

    line 108
  20. Distinctive:

    Put native lifecycle and teardown races in a browser project, not jsdom.

    line 110
  21. Distinctive:

    Keep docs wrappers interaction-neutral so table or tile hover styles cannot masquerade as component behavior.

    line 123
  22. Distinctive:

    Keep stories deterministic, self-contained, and free of production side effects.

    line 125
  23. Distinctive:

    Use realistic interface content in visual examples.

    line 127
  24. Distinctive:

    Add a docs page for every public component: intent, composition, accessibility contract, and examples.

    line 138
  25. Distinctive:

    Run contrast assertions in the real browser against the exact rendered semantic pairs and states, not palette swatches alone.

    line 144
  26. Distinctive:

    Treat a near-threshold contrast violation as a real failure.

    line 148
  27. Distinctive:

    Treat automated accessibility output as a first pass.

    line 151
  28. Distinctive:

    Keep stories and docs in a private workbench.

    line 153
  29. Distinctive:

    Keep a representative framework or extraction fixture with the shared nodelib preset it validates.

    line 161
  30. Distinctive:

    Test critical journeys and browser contracts, not every visual variant.

    line 183
  31. Distinctive:

    Use accessible locators and observable readiness conditions.

    line 184
  32. Distinctive:

    Keep test data isolated and clean it through supported product interfaces.

    line 185
  33. Very specific:

    Run Authentication in a platform's stack with the callers production gives it.

    line 200
  34. Very specific:

    Run the account platform's image in a client's stack, with a token drawn from the issuer at start, and register the client in OAUTH_CLIENTS …

    line 206
  35. Distinctive:

    Run at least the repository browser baseline for compatibility-sensitive behavior.

    line 222
  36. Distinctive:

    Before a repository first compares its screenshots against Drive reference batches, read Subscribe a repository to Drive visual tests for …

    line 224
  37. Very specific:

    Treat the annotations as the inventory.

    line 242
  38. Distinctive:

    Treat coverage as a map, not a percentage target.

    line 251
  39. Distinctive:

    Run focused checks during iteration.

    line 254

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Test Conventions

Load write-frontend, plus write-svelte for Svelte targets and write-design-system for uikit. Read the production behavior before writing tests. Read neighboring tests and the official documentation for the installed testing tools. Preserve existing tests unless the behavior they specify has been intentionally removed.

Timing

Follow develop-feature. Early stories make a draft reviewable; early tests should only resolve uncertainty, reproduce a defect, or protect a risky invariant. Complete the behavior matrix and full relevant regression suite after issue scope is approved. These conventions govern the quality of any test written at either stage, without requiring exhaustive coverage during exploration.

Test at the nearest truthful layer

Use the smallest layer that proves the user-visible contract:

  1. Test pure parsing, formatting, and state transitions as unit tests.
  2. Test component rendering and interaction through the DOM with Testing Library.
  3. Use Storybook stories as the exhaustive catalog of reusable component states; add interaction and accessibility assertions where supported.
  4. Use real-browser end-to-end tests for routing, focus across pages, browser APIs, hydration, multi-component journeys, and behavior an emulated DOM cannot prove.

Do not duplicate the same assertion at every layer. Keep one strong contract test and add broader tests only for integration risks.

User-centered assertions

  • Query elements by accessible role and name first, then label, text, or other user-visible meaning. Use test IDs only when no semantic query exists.
  • Interact as a user would: focus, type, click, submit, and press keys. Do not call component methods or mutate internal state to reach a condition.
  • Assert observable output, accessible state, navigation, focus, and network contract. Do not assert private classes, framework internals, reactive calls, or implementation sequence.
  • Use async queries for async UI. Wait for the condition the user observes, not an arbitrary timeout.
  • Cover keyboard behavior and focus order for every custom interaction pattern.
  • Prefer explicit assertions over broad snapshots. Use a snapshot only for a stable serialized contract whose meaningful review is easier as a whole than as targeted assertions.

Behavior matrix

Cover the states relevant to the changed contract:

  • Default, supported variants, and boundary values.
  • Loading, empty, error, retry, stale, and success.
  • Disabled, readonly, required, invalid, expanded, selected, and pressed states where applicable.
  • Text shorthand, composed snippets, and omitted optional regions for reusable content APIs.
  • Keyboard-only operation, focus entry/exit, and focus restoration.
  • Long text, missing optional content, localization, RTL, narrow viewport, and zoom/reflow.
  • Dark theme, forced colors, reduced motion, and contrast-sensitive states for shared UI.
  • Variant-by-state comparisons on every materially different backdrop: canvas, opaque island, translucent surface, popover, and dialog where applicable.
  • Cancellation, out-of-order responses, and duplicate submission for asynchronous actions.

Do not manufacture cases that the public contract cannot reach.

Isolation and doubles

  • Keep tests deterministic and independent of order. Restore timers, globals, storage, fetch mocks, and DOM mutations after each test.
  • Mock at system boundaries, not inside the unit under test. Prefer a lightweight fake with behavior over a deep mock that repeats implementation details.
  • Use real parsing, validation, and component composition when inexpensive. Over-mocking creates a test for the mock graph rather than the product.
  • Control clock, randomness, locale, and network explicitly when they affect output.
  • Use framework-supported request interception for network tests. Never call a live third-party service from unit or component tests. The unit project of nodelib-config's SvelteKitVitest runs offline: its global fetch rejects every request a test does not stub, and vi.unstubAllGlobals() restores that rejection.
  • Make mock failures loud: reject unhandled requests and unexpected calls.
  • Build a fresh Response per call (mockImplementation(async () => Response.json(…))). A body reads once, so mockResolvedValue(response) hands every later call a consumed body, and the failure looks like a parsing bug in the code under test.
  • Pin performance contracts with direct assertions. To prove a body is released, keep the stubbed Response and assert response.bodyUsed after the call. To prove calls run in parallel, return a promise that never resolves and assert synchronously that every independent call has started. A serial loop then fails at once instead of timing out.

Vitest and Testing Library

  • Use Vitest APIs documented for the installed version. Prefer typed dynamic module paths when a module mock is genuinely necessary.
  • Give every Vitest project config file an explicit root: fileURLToPath(new URL(".", import.meta.url)). Since Vitest 5.0.4, a project gets its root after svelte() has read it. Without one, the plugin starts from the workspace root, leaves Svelte libraries untransformed (Unknown file extension ".svelte") and loads a second Svelte runtime (lifecycle_outside_component).
  • Name a config that holds only Vitest options vitest.config.ts, never vite.config.ts. Since 4.7.7, svelte-check reads .svelte files from symlinked workspace packages at their real path and configures each from the nearest vite.config.* or svelte.config.*, the Vite one winning within a directory. A Vite config without the Svelte plugin then fails with No Svelte configuration found in vite config, as uikit's packages/storybook-config did. Vitest tries vitest.config.* first, so the rename costs nothing.
  • Keep vi.mock scope and hoisting rules visible. Refactor an inseparable module boundary rather than relying on a mock the runtime cannot intercept.
  • Render DOM, not component instances. Follow Testing Library query priority and guiding principle: tests should resemble real use.
  • Use the repository user-interaction helper when present. Reserve low-level event dispatch for the browser primitive being tested.
  • Put native lifecycle and teardown races in a browser project, not jsdom. Exercise the real element API and event queue, including events delivered as a component unmounts, and assert the user-visible outcome rather than Svelte internals.
  • Name tests as observable behavior in plain language. Group by public function, component, or user journey.

Storybook

  • Give every public component stories for meaningful variants and states, including interactive, disabled/error, long-content, and narrow-layout cases.
  • Show related variants together. Include the concise text API and one realistic composed example; verify that optional regions can be omitted without leaving empty structure or spacing.
  • Render sizes, colors, variants, and persistent/transient state combinations in aligned matrices. Keep docs wrappers interaction-neutral so table or tile hover styles cannot masquerade as component behavior.
  • Keep stories deterministic, self-contained, and free of production side effects. Use loaders and decorators only for shared, explicit environment contracts.
  • Use realistic interface content in visual examples. Explain controller transitions and keyboard mechanics in docs and tests instead of making them the example's title or body.
  • Isolate screens and top-layer content in subdocuments on docs pages. Show desktop/mobile examples together with responsive wrapping; modest scaling helps comparison, but verify geometry and touch targets at the native viewport size. Check the docs page itself as well as individual stories.
  • Pin visual-review stories through fixed-state controllers. Clicking, submitting, pressing Escape, or clicking outside must not change the displayed scenario. Show distinct states as separate examples; exercise live transitions in controller/component tests. Keep locale, theme, and story inputs configurable instead of duplicating scenarios per locale. Preserve native focus, scrolling, and text selection; do not freeze previews with event-blocking overlays, inert, or pointer-events: none.
  • Add a docs page for every public component: intent, composition, accessibility contract, and examples. Use generated controls and ArgTypes for API reference instead of repeating a manual table. Document foundations separately from components.
  • Enable accessibility analysis globally and make violations fail automated story tests when the installed Storybook integration supports it. Any exception must identify a documented false positive or intentional antipattern story.
  • Run contrast assertions in the real browser against the exact rendered semantic pairs and states, not palette swatches alone. Automated tooling does not credit glow and may not understand every gradient, transparency stack, pseudo-element, or forced-colors result; inspect computed output and visually compare state separation in the supported surface contexts.
  • Treat a near-threshold contrast violation as a real failure. Fix the owning semantic token and keep a rendered safety margin; do not disable the rule or add a consumer override merely because a calculated source value appeared to meet the minimum.
  • Treat automated accessibility output as a first pass. Manually verify keyboard/focus behavior and relevant browser/assistive-technology combinations for complex widgets.
  • Keep stories and docs in a private workbench. Keep reusable Storybook theme, preview, and test configuration in a separately consumable development package; never publish stories or runtime workbench code as part of the component package.
  • List each dependency subpath a story imports in Storybook's optimizeDeps.include (@a-novel-kit/uikit-storybook/ScrollPreview.svelte, not only the package). Vite matches entries exactly; an unlisted subpath is optimized mid-run, which reloads the iframes and fails every story file on a cold cache. CI is always cold, so prove such fixes after deleting node_modules/.cache and node_modules/.vite.
  • Keep a representative framework or extraction fixture with the shared nodelib preset it validates. A platform tests its product catalogs, screen states, and wiring instead of copying that generic fixture locally.

End-to-end tests and screenshot comparisons

During stage 3 of develop-feature, create or update all affected Playwright tests for client-side work. Cover the changed journeys and browser contracts, then run the relevant browser suite. Reuse the existing harness; when one is missing, include the smallest suitable Playwright setup in the agreed scope, following choose-dependency. Do not silently defer required browser validation.

Separate a journey's phases with blank lines. When a loop wraps test.step, move the step body into a named function, so the loop reads as the list of cases.

For rendered changes, capture deterministic screenshots of the affected states and supported viewports and compare them with reviewed baselines. Stabilize data, fonts, locale, theme, and motion. Inspect expected, actual, and diff images. Fix unexpected differences; update a baseline only when the new rendering matches the agreed UI, and review initial baselines for new screens. Screenshots complement live app/Storybook review and behavioral assertions. For non-visual client changes, verify relevant existing visual baselines where applicable and state when no rendered surface is affected instead of inventing a screenshot test.

  • Test critical journeys and browser contracts, not every visual variant.
  • Use accessible locators and observable readiness conditions. Never use fixed sleeps.
  • Keep test data isolated and clean it through supported product interfaces.
  • Point every URL the server under test reads at a local fixture started in a global setup, never at a live or production file. When the server caches what it reads, give each state its own webServer entry: entries start in order, so later ones run node build on the first entry's build. platform-studio's downtime journeys follow this pattern.
  • Make a compose service that a journey stops and starts, as the outage journeys stop authentication, join a holder's network: network_mode: service:<holder>, where a registry.k8s.io/pause holder publishes the ports. A restarted container otherwise gets a new host interface, and Chromium fails the page then loading with net::ERR_NETWORK_CHANGED. platform-template's builds/compose.e2e.yaml follows this pattern.
  • Serve another platform's production image over plain http with PROTOCOL_HEADER: x-forwarded-proto on its container, and send X-Forwarded-Proto: http from the journeys' browser (Playwright extraHTTPHeaders). adapter-node otherwise assumes https, and SvelteKit then rejects every form post to that platform as cross-site. The account platform also names an https issuer, which its clients refuse.
  • Run Authentication in a platform's stack with the callers production gives it. service-authentication's service-token-issuer image stands in for the cluster's issuer, which Authentication trusts through SERVICE_TOKEN_ISSUER, SERVICE_TOKEN_AUDIENCE and SERVICE_TOKEN_CALLERS. A Playwright global setup draws the platform's token from the issuer into the file SERVICE_TOKEN_PATH names. Fixtures call Authentication through a fetch that adds a caller's token from the issuer in X-Service-Authorization.
  • Run the account platform's image in a client's stack, with a token drawn from the issuer at start, and register the client in OAUTH_CLIENTS on the address of the journeys' server. The account platform's own stack registers a stub client instead, whose callback the test serves and whose code the test exchanges. platform-studio's builds/compose.e2e.yaml and tests/e2e/service-token.ts follow this pattern.
  • A change to a platform's shell or pages can break the sibling journeys that drive its published image, and they fail only when that image's pin moves. Search every other platform's tests/e2e/ for each accessible name you move or remove, and fix the journeys that reach it in a pull request that also bumps their pin. platform-account v1.2 moved its "Agora apps" list into the rail, and Studio's sign-in journey, still clicking the removed button, broke Renovate's account image bump until platform-studio#439.
  • A page.route handler sees only the first URL of a redirect chain. To alter a later step, walk the chain with page.request.get(url, { maxRedirects: 0 }), read each location, and open the altered URL with page.goto. platform-studio's failed-exchange journey forges its code this way.
  • Capture traces/screenshots on failure where the existing runner supports them; do not commit transient artifacts.
  • Run at least the repository browser baseline for compatibility-sensitive behavior.

Before a repository first compares its screenshots against Drive reference batches, read Subscribe a repository to Drive visual tests for its identities, folders, variables and seeding order. Read its recovery section when master's test-browser keeps failing on screenshot changes that merged pull requests already approved.

A Vitest browser project, such as uikit's Storybook stories, compares its screenshots against Drive reference batches through node-actions/test-playwright with runner: vitest. The workflows repo's docs/migrations/v1.64.0.md is the adoption guide. Vitest's browser mode adds these rules:

  • Capture expect(page).toMatchScreenshot(). Modal dialogs and popovers render in the top layer, outside every container, so a container capture misses them. In Storybook's Vitest run, the first body div is the hidden sb-preparing-story wrapper.
  • Declare each screenshot with a visual-snapshot annotation in beforeEach or before the first assertion. Vitest accepts annotate() only while the test state is run, and rejects it in afterEach or after a failed soft assertion.
  • Name each screenshot flat, from [A-Za-z0-9_-] with single hyphens: `${task.file.name}-${task.name}`.replace(/\W+/g, "-"). Vitest resolves only the base name and strips every other character, so a Storybook story ID or a dotted name stops matching its annotation.
  • Treat the annotations as the inventory. Vitest records a toMatchScreenshot artifact only on failure, so the Drive runner lists screenshots from annotations and fails a run that writes an undeclared one.
  • A style assertion in a play function turns a visual change into a functional failure, which allow-screenshot-change cannot waive. uikit's Toolbar story asserts that its link keeps the button radius, so a Button radius change fails until the link follows.

Coverage and completion

Treat coverage as a map, not a percentage target; 100% is not required. Missing error, keyboard, security, or state-transition paths matter; uncovered generated glue does not justify a test.

Run focused checks during iteration. After scope approval, complete meaningful coverage and run the applicable suites; after cleanup, rerun checks affected by the final edits:

pnpm lint
a-novel test --type=pnpm -y
a-novel build --type=pnpm -y

The change is complete only when new behavior has a regression test at the truthful layer and all affected existing tests remain green.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.