Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Qa

ASecurity

QA a change by driving the running dev app like a real QA engineer - start the server and a real browser session, write a checklist from the agreed requirements and the diff, execute every item with evidence, then go looking for anything that looks off even where the diff did not touch. Reports findings by severity and separates regressions from pre-existing problems. Use after a change is implemented and the gates pass, when the user asks to "QA this", "test it in the app", or "check it actu...

17 stars
0 votes
0 copies
2 views
Added 9/20/2026
testinggobashgitapidatabase

Works with

cliapimcp

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add michaelst/spendable --skill qa --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Qa?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Qa
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/michaelst-spendable/badge)](https://www.skillsdirectory.com/skills/michaelst-spendable)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: qa
description: QA a change by driving the running dev app like a real QA engineer - start the server and a real browser session, write a checklist from the agreed requirements and the diff, execute every item with evidence, then go looking for anything that looks off even where the diff did not touch. Reports findings by severity and separates regressions from pre-existing problems. Use after a change is implemented and the gates pass, when the user asks to "QA this", "test it in the app", or "check it actually works".
---

# QA

Green tests say the code does what its author thought. QA says the *feature* works, in the real app,
for a user who is trying to use it — and it is the only step that catches what nobody thought to
assert. Approach it as a QA engineer, not as the author defending the change: your job is to find the
bug before the user does, and a pass with nothing found is a weaker result than a pass with three
findings.

Two halves, both required:

1. **Verify the change** against the agreed requirements, item by item, with evidence.
2. **Break it, and look around.** Edges the requirements never mentioned, and anything on adjacent
   screens that looks wrong — whether or not this change caused it.

## 1. Start the session

```bash
.claude/skills/qa/scripts/qa.sh start
```

Starts the dev server if it is down (it leaves a server you already had running alone), launches a
scratch headless Chrome, and signs in by installing a minted session cookie — Google OAuth cannot be
driven headlessly and no password is ever typed. Run every `qa.sh` command through Bash with the
sandbox disabled — Mix needs a real TCP socket.

It finds the port the way the app does: `PORT` from the environment, else this worktree's
`.env.worktree`, else 4000. **In a worktree that means it QAs that worktree's app**, on its own
database, with its own Chrome and state directory, so two worktrees can QA at once. It refuses to
start if what answers on that port is not Spendable.

The dev database must already contain at least one user; `qa.sh` signs in as the oldest one unless
you pass a UXID or Google `external_id`. If it has none, sign in once in a real browser first, or
create one through `Accounts` with `mix run` — never by inserting a row directly.

Then each check is a small script run against the still-live browser:

```bash
.claude/skills/qa/scripts/qa.sh run /path/to/scratchpad/check-01.mjs
```

Copy [scripts/example-check.mjs](scripts/example-check.mjs) and rewrite it. The session is the
[record-demo-video](../record-demo-video/SKILL.md) CDP driver (`click`, `type`, `typeDate`, `press`,
`hover`, `goto`, `evaluate`, `expect`) plus `shot(name)`, `text()`, `resize(w, h)`, and
`drainProblems()`. **Do not use the in-app Browser pane** — its synthetic clicks do not reliably fire
`phx-click`, so a check that "passes" there proves nothing; it cannot screenshot at all unless the
pane happens to be on screen; and it cannot be signed in by hand, because the session cookie is
`httpOnly` and `document.cookie` cannot overwrite one. Reach for `qa.sh`, not for a workaround.

A part of the app that only a non-browser client reaches — an API, a webhook, an MCP endpoint — is
still QA's job. Drive it with `curl` in the same pass, as the real client would, and check the effect
in the browser: the point is that the two agree.

`qa.sh log` tails the dev server log. `qa.sh stop` tears down what it started — hold off on it if a
[record-demo-video](../record-demo-video/SKILL.md) pass is next, since that needs the same server up.

## 2. Write the checklist before you touch anything

Written first, so the pass is not shaped by what happens to work. Sources, in order:

- **The requirements and the approved plan** — one row each, worded as the observable outcome, using
  the worked example numbers where there are any.
- **The diff** — every changed LiveView, route, component, action, migration, and every caller of a
  function whose behavior changed. `git diff` against the base branch.
- **The standing list below**, filtered to what this change can actually reach.

Write it to the scratchpad as a table: check, how to verify, expected, status, evidence. Show it to
the dev, note anything you deliberately left out, and get on with executing it — no approval gate.

### Standing checks

Not a form to fill in. Skip what the change cannot reach, and say you skipped it.

- **Happy path**, with realistic data. Then reload the page: did it actually persist?
- **The write really landed.** Query the DB for the row. The screen showing a number is not evidence
  the number was saved.
- **Money.** Decimal formatting, thousands separators, negatives, rounding to cents, zero. Whether
  the sign is right: an allocation *to* a budget and a spend *from* one differ only by sign, and the
  form's label flips on it. This app's whole job is arithmetic — a wrong number that renders
  beautifully is the worst outcome here.
- **Dates and months.** Budgets and spending are read per month, so drive a transaction dated on the
  first and last day of a month and confirm it lands in the right one. A date that renders one day
  off is a timezone bug, not a display bug.
- **The Spendable remainder.** Any change that touches allocations: allocate part of a transaction,
  all of it, more than it, and none of it, and confirm the remainder in **Spendable** is what is
  left over each time.
- **Validation and errors.** Required fields blank, bad formats, negative and huge amounts, a
  duplicate, a stale record edited in a second tab. Is the message specific, in the right place, and
  in the app's voice?
- **Every new error reaches a field the form renders.** An error on a field the form does not render
  is a save that silently does nothing — the user clicks save, nothing happens, nothing is said. The
  nested allocation rows on `/transactions` and `/splits` are where this bites: an error on a
  child row that the parent form never displays looks exactly like a dead button.
- **Adding and removing nested rows.** Those same allocation rows use Ecto's `sort_param` /
  `drop_param`, so add a row, remove a middle one, remove the last one, and remove all of them —
  then save and reload. This is the least test-covered mechanism in the app.
- **Ownership isolation.** Every record belongs to exactly one user and every action pins it. Take a
  record id belonging to another user, rewrite a `phx-value-id` attribute to it with `evaluate`, and
  click: the action must refuse, never act and never leak the other record's name into the page.
- **Empty, one, many.** Empty state copy, a single row, enough rows to page — then search, filter,
  and scroll in combination, and check the filter survives a reload. `/transactions` pages by
  infinite scroll, so drive it past the first page.
- **Interruptions.** Back button, refresh mid-flow, double submit, rapid clicks, Escape/cancel
  discarding, an unsaved form navigated away from.
- **Everything on the new screen goes somewhere.** Click every link, button, and tab you added, and
  check the nav highlights the tab you are actually on.
- **The browser's own complaints.** `drainProblems()` after every check: console errors, uncaught
  exceptions, 4xx/5xx responses, a LiveView socket that dropped and reconnected.
- **The server log.** `qa.sh log` — stacktraces, 500s, and anything noisy the change introduced.
  Also check nothing secret got logged: Plaid tokens and Google ids are `redact: true` on the schemas
  for a reason.
- **Narrow viewport (375px)**, long strings and long names, keyboard tab order and focus, loading
  states on a slow action. This app is used on a phone more than a desktop.
- **Looks like the rest of the app.** Spacing, alignment, button placement, capitalization, typos,
  terminology matching the context's `CONTEXT.md`.

### What is hard to drive, and what to do instead

**Bank linking.** `/banks` opens Plaid Link, a third-party JS overlay against the Plaid sandbox. Do
not try to drive it click-by-click. Verify what is on our side of the boundary — the link token
request, the bank-limit refusal, the account list after a member exists — and say in the row that the
Plaid overlay itself was not driven.

**Syncing.** Sync runs as an Oban job. Enqueue it the way the app does and check the job ran and the
rows landed, rather than waiting on a webhook.

## 3. Execute with evidence

A row goes green only with something attached: a screenshot you actually read, a queried value, a log
line. **Read the screenshots with the Read tool** — they are PNGs and they render. That is how you
catch what no assertion covers: a form error rendering white and indented instead of red and flush
left, a column clipped, `$1234.5`.

Never mark a check passed because the code looks like it should pass. If a check is impractical to
drive, say so in the row and say what you did instead.

## 4. Then go looking

The part that is actually QA rather than verification. Spend real effort here, after the checklist,
with the app already in a state the change created.

- Walk the screens **around** the change — the list it links from, the other screen that reads the
  same data, the form that writes it.
- Follow the data end to end. A transaction's amount should agree with its allocations, with each
  budget's balance, and with the month's spending. Cross-screen disagreement is the highest-value bug
  in this app.
- Poke at whatever looks fragile, and at anything that made you double-take.

Report anything off, including what this change plainly did not cause. **Do not fix pre-existing
problems here** — that is scope creep on someone else's PR. Write them up and hand them to the dev.

## 5. Report and triage

One table, most severe first:

| # | Check | Result | Severity | Caused by this change? | Evidence |

Severity: **blocker** (wrong number, data loss, one user seeing another's records, the feature does
not work), **major** (a real path is broken or badly confusing), **minor**, **nit**. Be honest about
severity — a nit inflated to a blocker costs the dev the same as a blocker missed.

Then:

- **Blockers and majors caused by this change**: fix them through the [tdd](../tdd/SKILL.md) skill —
  failing test first, since a QA finding is by definition a gap in the suite — re-run the gates from
  the top, then re-run every affected check.
- **Everything else**: present it and let the dev choose.
- Finish with what you could not check and why. A QA report that implies full coverage it did not do
  is worse than a short one.

## Data hygiene

The dev database holds data the user cares about — it is their own budget. **Never reset, drop, or
re-seed it**; any `ecto.reset` / `ecto.drop` is `MIX_ENV=test` only. Create the records your checks
need rather than editing the dev's, avoid destructive actions on data you did not create, and end the
report with the state you left behind — including anything a check deliberately broke.

Attribution

michaelstmichaelst
View sourceMore from michaelst →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

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 (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Screen Reader Testing

Practical guide to testing web applications with screen readers for comprehensive accessibility validation.

397921 votes

Golang Testing

Go测试模式包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循TDD方法论,采用地道的Go实践。

2456590 votes

Python Testing

使用pytest、TDD方法、夹具、模拟、参数化和覆盖率要求的Python测试策略。

2456590 votes

Tdd Workflow

在编写新功能、修复错误或重构代码时使用此技能。强制执行测试驱动开发,包含单元测试、集成测试和端到端测试,覆盖率超过80%。

2456590 votes

Eval Harness

克劳德代码会话的正式评估框架,实施评估驱动开发(EDD)原则

2456590 votes
View all in testing →