Skip to content
Back to skills

App Qa

ASecurity

Screenshot-verify treq UI and interaction changes by driving the real app (real jj repo via NAPI, jsdom React) with userEvent through scripts/screenshot/. Use on /app-qa, when asked to QA or screenshot a behavior, and proactively after changing UI-affecting code, before reporting done.

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 2, 2026
ai-agentsrustgoreactnodetestingapibackend

Works with

  • cursor
  • terminal
  • cli
  • api

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add treq-dev/treq --skill app-qa --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of App Qa?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for App Qa
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/treq-dev-app-qa/badge)](https://www.skillsdirectory.com/skills/treq-dev-app-qa)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: app-qa
description: >-
  Screenshot-verify treq UI and interaction changes by driving the real app
  (real jj repo via NAPI, jsdom React) with userEvent through
  scripts/screenshot/. Use on /app-qa, when asked to QA or screenshot a
  behavior, and proactively after changing UI-affecting code, before reporting
  done.
---

# App QA (screenshot-verified behavior checks)

## When to use

- User invokes `/app-qa`, optionally naming a flow or component ("app-qa the workspace
  picker", "app-qa the merge conflict banner").
- Proactively, immediately after an Edit/Write/MultiEdit that changes UI-affecting
  code — don't wait to be asked. If you see `additionalContext` from
  `post-edit-app-qa.sh` naming a changed file, that *is* the request.

## Delegate grunt work and discovery to cheaper subagents

Most of the cost of an app-qa run is search and mechanical legwork, not judgement. Push
that to subagents on a cheaper model (`Agent` with `model: "haiku"`, or `"sonnet"` when
the answer needs some reasoning) and keep the expensive context for the parts that
actually need it. Run independent delegations in parallel in a single message.

Good candidates — hand these off:

- **Finding prior art.** Which `test/integration/**` or `test/*.test.tsx` scenario
  already builds the repo/workspace state this spec needs; whether a spec under
  `scripts/screenshot/specs/` already covers this flow; which spec is the closest
  shape to copy. Use `Explore` — it's read-only and returns the conclusion instead of
  dumping files.
- **Locating selectors and wiring.** The `data-testid`, accessible role, or button
  label to drive; which component renders a given piece of copy; which UI flow a
  changed `src-tauri/` or `src/lib/` file actually backs.
- **Tracing command gaps.** When a flow fails inside `invoke`, have a subagent
  find the corresponding `#[tauri::command]` and `core::*` function. `tauri-test`
  dispatches the real command; there is no separate `dispatch.rs` match table.
- **Mechanical sweeps.** Reading a batch of `<name>.json` manifests, chasing down
  which spec produced which capture, diffing a spec against the one you're copying.

Ask for the specific fact you need ("which test file creates a repo with a conflicted
merge, and what does the setup look like") rather than "look into the test setup" —
a subagent starts cold and pays to re-derive whatever you don't tell it.

Keep these yourself — they are the skill, not the legwork:

- Deciding what behavior to verify and what the `expectations` should claim.
- Writing the spec and driving the flow with `userEvent`.
- **Step 5 verification.** Read the PNGs yourself. The whole point is that the agent
  shipping the change looks at the pixels; a subagent's "looks fine" is not that.
- The final report to the user.

## Ground rule: userEvent only, never fireEvent

Every interaction inside a spec must go through `@testing-library/user-event`
(`userEvent.setup()`, then `user.click`, `user.type`, `user.keyboard`, `user.hover`,
`user.tab`, ...). Never use `fireEvent` for driving the scenario.

`fireEvent` dispatches one synthetic DOM event. `userEvent` replays the sequence a
real user actually produces — pointerdown, focus, pointerup, click; or a real
per-character sequence of keydown/input/keyup for typing. Radix components, cmdk,
and treq's own focus/keyboard-shortcut handling key off that full sequence. A spec
that drives state with `fireEvent.click` can look green while the real app is broken
for a real user clicking the same button — it defeats the point of this skill.

This extends to *setup*, not just the behavior under test: if the scenario's
narrative includes "the user creates a workspace" (or renames one, deletes one,
etc.) as a step, create it by clicking through the real dialog (the "Stack" button
on the home repo header, or hover an existing workspace's sidebar row and click its
"Stack a workspace" button to create a stacked child -- workspace headers have no
"Stack" button) rather than calling `createWorkspace()` from `src/lib/api` directly.
The API helper is still fine for *incidental background state* a spec needs but
isn't itself testing (e.g. two throwaway workspaces just so a branch-switcher
dropdown has something to list). `scripts/screenshot/specs/commits-tab-after-push.spec.tsx`
is the worked example: it drives the whole "Stack" dialog (open it, type a branch
name, submit) with `userEvent`, not the API helper, because workspace creation is
part of the scenario being verified.

## Agent sessions need the fake agent

A spec that starts an agent session (Edit/Run/Plan, sidebar agent buttons,
Cmd+]) must call `installFakeAgents()` from `test/fake-agent.ts`, e.g.
`onTestFinished(installFakeAgents())`. Without it no PTY spawns and the
terminal shows an error card. With it, the fake agent's `fake-agent:`,
`mode:`, `model:` and `prompt:` lines render in the terminal, so assert on
them with `within(pane).findByText(...)` before capturing. See
`test/AGENTS.md` ("Fake agent terminals").

## References

- `references/setup.md`: first-time environment setup for a fresh sandbox (npm deps, GTK/webkit packages, proxy CA trust). Read it only when `npm run build:napi` or `npm run screenshot` fails on a missing dependency.
- `references/harness.md`: how `createTestRepo`, `render(<Dashboard/>)`, and `captureDocument` fit together, and why `setup.screenshot.ts` tolerates un-migrated `jj_*` commands. Read it when a spec fails inside the harness or you hit an unknown command.

## Steps

1. **Identify the behavior to verify.** From the user's ask, or from the changed
   file(s) named in the hook's `additionalContext`, work out which user-facing flow
   changed. Search `test/integration/**` and `test/*.test.tsx` for a scenario that
   already sets up the right repo/workspace state (`createTestRepo`, `commitRepoFile`,
   etc.) and reuse that setup instead of inventing your own — it's already proven to
   work against the real backend. Delegate that search to an `Explore` subagent per the
   section above; do the "which behavior matters" call yourself. Remember the rule
   above: if workspace creation is part of the scenario, drive it through the real UI,
   not `createWorkspace()`.

2. **Write or extend a spec** under `scripts/screenshot/specs/<slug>.spec.tsx`. One
   spec per behavior/flow. If an existing spec already covers this flow, add capture
   steps to it rather than duplicating the repo setup in a new file. Shape:

   ```tsx
   import * as React from "react";
   import { it } from "vitest";
   import userEvent from "@testing-library/user-event";
   import { createTestRepo, openRepo } from "../../../test/utils";
   import { render, screen, within } from "../../../test/test-utils";
   import { Dashboard } from "../../../src/components/Dashboard";
   import { captureDocument } from "../capture";

   it("captures <the behavior>", async () => {
     const { repoPath } = createTestRepo(false);
     openRepo(repoPath);
     // ... real repo setup via test/utils + src/lib/api; drive workspace
     // creation/deletion/etc. through the real UI if it's part of the scenario ...

     const user = userEvent.setup();
     render(<Dashboard />);

     // Real DOM assertions -- these prove the state is correct BEFORE capturing.
     // Separate from `expectations` below, which are about the picture, not the DOM.
     await screen.findByTestId("show-workspace-header");
     await captureDocument(document, {
       name: "<slug>-01-before",
       expectations: [
         "Plain-English claim about what this screenshot should show visually.",
         "A second claim, if there's more than one thing worth checking in the image.",
       ],
     });

     // Drive the flow -- ONLY user.* calls, never fireEvent.
     await user.click(await screen.findByRole("button", { name: "..." }));
     await screen.findByText("..."); // wait for the resulting DOM change
     await captureDocument(document, {
       name: "<slug>-02-after",
       expectations: ["What changed, stated as a visual claim about the after image."],
     });
   }, 60000);
   ```

   `scripts/screenshot/specs/workspace-branch-switch.spec.tsx` is a worked example of
   the userEvent + multi-step-capture shape. `commits-tab-after-push.spec.tsx` is the
   worked example of driving workspace creation through the real "Stack" dialog. Copy
   whichever shape fits.

   Give every capture a numbered, descriptive `name` — that string becomes the PNG
   (and manifest JSON) filename, so name it as `<slug>-<NN>-<what-it-shows>`.

3. **`expectations` are for the picture, not the DOM.** `captureDocument` requires a
   non-empty `expectations: string[]` — plain-English claims about what a viewer
   should be able to confirm by *looking at the screenshot* (colors, layout, which
   button is visible, what a toast says, whether a list has the right items). These
   are not code assertions and `captureDocument` does not execute them; they're
   written to `<name>.json` next to the PNG specifically so that step 5 has a
   concrete, per-screenshot checklist instead of "eyeball it and hope you notice
   something wrong." Keep the real `screen.findBy*`/`expect` calls in the spec body
   too (still required, still what proves the DOM reached that state) — the two are
   complementary, not a replacement for each other.

   **At most 3 expectations per capture** — enforced by the
   `max-3-capture-document-expectations` ast-grep rule. Three claims is what a
   reviewer actually re-reads against an image; a list of eight gets skimmed. If a
   screenshot genuinely needs more than three, that is a sign it is showing more
   than one thing: take a second capture with its own `name` and split the claims
   across the two.

4. **Run it.** If this is a fresh environment (no `node_modules/`, or `cargo build`
   fails on GTK/webkit headers), follow `references/setup.md`
   above first.
   - First run in a session, or after touching `src-tauri`, or
     adding new Tailwind classes: `npm run screenshot` (rebuilds the NAPI addon,
     recompiles CSS, runs every spec — slow but complete).
   - Fast iteration on one spec once the addon/CSS are already built:
     `npx vitest run --config vitest.screenshot.config.ts scripts/screenshot/specs/<slug>.spec.tsx`
   - If only Tailwind classes changed (no Rust change): `npm run screenshot:css` first,
     then the targeted vitest run above.

5. **Verify each screenshot against its expectations before saying the task is done.**
   For every capture: read `scripts/screenshot/.generated/<name>.json` for its
   expectations list, then read `scripts/screenshot/.generated/<name>.png` (multimodal
   Read) and go through the list confirming or refuting each one against what the
   image actually shows. A spec whose `expect`/`findBy*` calls all passed can still
   render a visibly broken layout, a missing state, or wrong copy — that's a real bug
   to fix, not a false alarm, and the expectations checklist is what catches it
   instead of a cursory glance.

6. **Show the result.** Use SendUserFile to deliver the before/after PNGs together,
   with a short caption naming what changed and what to look at, and call out any
   expectation that didn't hold.

7. **Lint, format, and typecheck the whole change, last.** Only after the behavior is
   visually verified and shown, run:
   - `npm run format` — Biome (`./src ./test`) + `cargo fmt`, auto-fixes what it can.
   - `npm run lint` — oxlint + eslint over `./src ./test/integration`, plus
     `ast-grep scan --warning`.
   - `npm run check` — `tsc` (typecheck) + `ast-grep test`.

   Fix everything these flag — including in any spec file you added or extended in
   step 2. Doing this last, after all edits (source and spec) have settled, catches
   what an earlier check-then-edit ordering would miss and leaves the working tree in
   a genuinely clean state before you tell the user the task is done.

## Keep specs around

`scripts/screenshot/specs/` is a growing visual-regression library, not a scratch
directory. Don't delete a spec after using it — if a later change touches the same
flow, extend its capture steps instead of writing a near-duplicate file.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.