Setup
Two paths. A project with no tests yet, and a project that already has a suite and a house style. The second one is the common case and it is the one most of this page is about.
What you need
Always
- Claude Code
python3, any 3.8 or later, forqa-contextandqa-policy. Both are stdlib only, nothing to installgit
Only for the stack you actually use
| Profile | Needs |
|---|---|
ts-playwright | Node 18+ |
py-pytest-playwright | Python 3.9+, a virtualenv |
mobile-appium-wdio | Node 18+, Java, Android SDK with an emulator or a connected device |
| your own | whatever your runner already needs |
Only if you want the thing it enables
| For | You need |
|---|---|
/qa:explore in a real browser | Node, for the Playwright MCP. It ships enabled and installs itself on first use |
| Jira or Confluence as the source of requirements and the home for bugs | The Atlassian MCP, connected once per machine |
| GitHub Issues instead | The gh CLI, authenticated. No MCP |
/qa:explore on a device | An Appium or WebdriverIO MCP, plus a running Appium server |
Install
/plugin marketplace add eduramos21/qa-automation-framework
/plugin install qa@eduramos21-qa
If the install summary says Run /reload-plugins to activate, do that.
To try it without installing, clone and point Claude Code at the clone:
git clone https://github.com/eduramos21/qa-automation-framework
cd qa-automation-framework
claude --plugin-dir .
Check it loaded. /help should list /qa:audit, /qa:refine and the rest, and qa-context should be on the path:
$ qa-context
No qa.config.yml found in /your/repo or any parent.
That error is the correct output before setup. It means the tooling is there and the project is not configured yet.
Connecting Jira
Two steps. The first is once per machine, the second is once per project.
1. Connect the MCP
claude mcp add --transport http atlassian https://mcp.atlassian.com/v1/mcp
Then /mcp inside Claude Code to run the OAuth flow. Check it took:
$ claude mcp list
atlassian: https://mcp.atlassian.com/v1/mcp - ✔ Connected
If you already reach Jira through a claude.ai connector, it is connected and there is nothing to do here.
2. Point the project at it
# qa.config.yml
tracker:
provider: jira
project_key: SHOP
mcp: atlassian
requirements:
source: jira # or confluence, if the specs live there
What that turns on
| Command | With a tracker configured |
|---|---|
/qa:refine SHOP-142 | Fetches the issue, its linked issues and its comments. The comments matter: half the decisions on any ticket live there and never make it into the description |
/qa:explore | Files what it finds into SHOP, using the bug report shape, with the trace or screenshot attached |
/qa:audit | Checks coverage against the real requirements rather than guessing what the suite was supposed to cover |
GitHub Issues instead
tracker:
provider: github-issues
requirements:
source: github-issues
No MCP. It shells out to gh issue view --comments and gh issue create, so you need gh auth login done and nothing else.
Neither
tracker:
provider: none
requirements:
source: markdown
path: docs/requirements/
Requirements are read from files and bug reports are written to files. Everything still works, it just does not file anything for you. This is what both demo projects in the repo use.
Path A, a project with no tests yet
cd your-repo
claude
/qa:init
init reads the repo, works out the language, runner and CI, asks about what it cannot see, writes qa.config.yml and drops in a pipeline file. Then:
/qa:refine SHOP-142 # or a path to a markdown requirement
/qa:design qa/requirements/SHOP-142.md
/qa:generate qa/design/SHOP-142.md
That is the whole loop. The worked example on the overview page follows exactly this, start to finish, with real output.
Path B, a repo that already has tests
The common case, and the one with real friction. Being upfront about it is more useful than pretending there is none.
Start with the audit, not the config
cd your-repo
claude
/qa:init # five minutes, mostly confirming what it already worked out
/qa:audit
/qa:audit is the thing to run first on an existing suite. It needs nothing but the config, it reads code you already have, it writes nothing, and it hands back nine scores with evidence and a ranked list of findings. If the tool is going to be useful to you, you will know from that one report.
Then fix the profile, because the shipped one is not your house style
If init matched your runner, the profile knows how to run your tests. It does not know how you write them. The shipped CONVENTIONS.md holds one particular set of opinions, and if your repo does something else, generated code will fight your reviewers.
So before generating anything:
/qa:profile
That reads your suite, weighted towards the most recently touched files because recent code is the current style, and drafts a profile.yaml and a CONVENTIONS.md that describe what your repo actually does. Every example in it is lifted from your code, with the path it came from.
It writes into <your-repo>/profiles/, which wins over the plugin's copy. Nothing is forked, nothing is patched, and updating the plugin later does not overwrite your description.
Where your suite contradicts itself, and any suite over a year old does, it asks which pattern new tests should follow rather than picking silently. That question takes ten seconds to answer and is the highest value part of the process.
No profile matches your runner at all
Java plus Selenium, Cypress, Robot Framework, C# plus NUnit. Same command, /qa:profile, starting from the blank template instead of a near match. Meanwhile /qa:refine, /qa:design and /qa:explore already work, because none of them writes code.
The policy hook will not fight your legacy code
Worth knowing, because the obvious implementation of this would be unusable. policies.forbid lists patterns like fixed sleeps. A suite that has been around a while has plenty.
The edit hook only reports violations on lines an edit actually touched. Editing a legacy file that already contains three sleeps, for an unrelated reason, reports nothing. Adding a new sleep reports that one. A brand new file is checked in full.
Pre-existing violations are still real and still show up in an audit, which is where a decision about them can be made, rather than being forced on you one file at a time.
# the full picture, any time you want it
qa-policy --stack web
# only what this edit introduced, which is what the hook uses
qa-policy --changed-only path/to/spec.ts
Running the check at commit time, without the plugin
qa-policy is a standalone, stdlib only Python 3 script. It does not need Claude Code, the plugin, or anything installed beyond Python to run — the --hook flag is the only part coupled to the edit hook's JSON. A developer who wants just this check can pull it on its own.
Add this repo as a pre-commit hook source:
# .pre-commit-config.yaml, in the target project
repos:
- repo: https://github.com/eduramos21/qa-automation-framework
rev: <tag or commit>
hooks:
- id: qa-policy
Or wire it into husky, lint-staged, or a raw git hook directly, since it is just a script that takes staged file paths as arguments:
bin/qa-policy --changed-only <staged files>
qa.config.yml with a policies.forbid list — that is what qa-policy checks against. A bare policies: block with no stacks: is enough, the rest of the setup here is not required. With the plugin installed, the same thing is /qa:prevalidate.
A monorepo with several suites
stacks:
web: { profile: acme-web, root: apps/web/e2e/ }
api: { profile: acme-api, root: services/api/tests/ }
mobile: { profile: acme-mobile, root: apps/mobile/e2e/ }
Every command takes the stack name. qa-context api, /qa:audit api, qa-policy --stack api.
Roughly what it costs
| Step | Time | Worth it when |
|---|---|---|
/qa:init | 5 minutes | always, everything else needs it |
/qa:audit | one run | always, and it is the thing that tells you whether to continue |
/qa:profile | 20 to 40 minutes, mostly you answering questions | before generating or fixing any code |
| Writing a profile from the blank template by hand | half a day | only if you would rather not have it read your suite |
When something is wrong
| Output | Means | Do |
|---|---|---|
No qa.config.yml found | Not set up, or you are in the wrong directory | /qa:init, or qa-context --from <path> |
profile 'x' NOT FOUND | The config names a profile that does not exist | Check the spelling, or /qa:profile |
declares no stacks | The config is a stub | Fill in the stacks block |
qa-context: command not found | The plugin is not loaded | /reload-plugins, or run bin/qa-context from a clone |
| Generated tests do not look like your repo | The profile's conventions are not yours | /qa:profile |
| The hook complains about code you did not write | Should not happen, the hook is changed-only | Check the file is committed. Git cannot tell what changed in a file it has never seen, so an untracked file is checked in full |
getByTestId finds nothing | policies.test_id_attribute and the runner config disagree | Make them match. This one is silent and wastes an afternoon |