A skill pack for manual QA engineers working on Optimizely CMS + frontend projects. Works with Claude Code and GitHub Copilot CLI.
End-to-end QA workflow plus on-demand local exports:
- test-plan — fetch a Jira/ADO ticket and produce a test plan
- test-case-design — generate labeled test cases (
automatable/assisted/manual) - cms-test-data — generate an importable Optimizely CMS content package (
.episerverdata) for the content a ticket's test cases need - auto-execute — run automatable cases in a real browser via Playwright MCP, hand off manual cases with evidence packs, and record a Playwright regression script
- cms-content-check — Optimizely edit-mode ↔ frontend checklist
- ui-ux-check — numeric design/responsive/a11y measurement (no LLM eyeballing)
- bug-report — standardized English bug reports
- test-summary — client-facing summary with honest automated/manual coverage
- artifact-export — export existing Markdown QA artifacts to supported client-shareable formats on demand
- qa-workflow — orchestrates the QA flow per ticket
Written for QA engineers. You do not need to know anything about AI to use this.
"The agent" is Claude Code or GitHub Copilot CLI — a chat window in your terminal. This repo adds no intelligence to it; it adds written procedures. Each skill is a checklist that tells the agent what to do, what to write down, and — most importantly — when to stop and ask you.
Everything it produces is an ordinary Markdown file under .qa/<ticket-id>/ in the project you are testing. Nothing goes into a database or the cloud. You can open, edit, or delete any of those files at any time, and the next stage reads your edited version.
You are still the tester. The agent does the typing, the clicking, and the paperwork; every judgment call stays yours.
flowchart TD
P1["1. test-plan<br/>reads the ticket, writes the plan"]
H1{{"YOU approve the plan"}}
P2["2. test-case-design<br/>writes the test cases"]
H2{{"YOU approve the cases"}}
Q{"Do the cases need CMS content<br/>the environment does not have yet?"}
P3["3. cms-test-data<br/>lists the content it intends to create"]
H3{{"YOU approve the content list"}}
P3B["builds an .episerverdata file"]
H4{{"YOU import it in the CMS<br/>and check it landed"}}
P4["4. auto-execute<br/>runs cases in a real browser"]
H5{{"YOU test the manual hand-off list<br/>and give the verdicts"}}
P5["5. cms-content-check / ui-ux-check<br/>only if the plan scoped them"]
P6["6. bug-report<br/>one Markdown file per finding"]
P7["7. test-summary<br/>the client-facing report"]
P8["optional. artifact-export<br/>Excel / Word / PDF"]
P1 --> H1 --> P2 --> H2 --> Q
Q -- yes --> P3 --> H3 --> P3B --> H4 --> P4
Q -- no --> P4
P4 --> H5 --> P5 --> P6 --> P7 --> P8
| Stage | What the agent does | What you do |
|---|---|---|
| test-plan | Reads the ticket from Jira/ADO — read-only — and writes a plan: scope, risks, what to test and how | Read it and correct anything it misunderstood. It is your file |
| test-case-design | Turns the plan into a numbered test case table and labels each case automatable, assisted, or manual |
Check the cases cover what actually matters. On a re-test, approve its rerun / skip / amend list — a wrong skip is the one mistake nothing else catches |
| cms-test-data | Works out what CMS content the cases need and lists it in test-data.md before building anything |
Approve or amend the list, then import the generated file yourself under Admin → Import Data |
| auto-execute | Opens a real browser and runs the automatable cases, checking the page's actual content. Pauses on assisted cases at the point a human is required (login, captcha, payment) |
Take over when it pauses. Then work through the manual hand-off list and record your verdicts |
| cms-content-check / ui-ux-check | Walks an Optimizely edit-mode checklist; measures spacing, sizes and breakpoints as numbers read off the page | Answer the checklist questions and give the visual verdicts |
| bug-report | Writes a standard bug report per confirmed finding into bugs/ |
Confirm it is genuinely a bug first, then copy it into Jira/ADO yourself |
| test-summary | Aggregates this round's results and open bugs into a client-facing summary | Read it before it goes anywhere. You own what reaches the client |
It never chains stages silently. Before every transition it shows one line — what it produced and where — and waits for your go-ahead. Four of those stops are hard ones:
- Before it changes existing test cases on a re-test. It shows you what it would rerun, skip, amend, or retire, with a reason per case, and does nothing until you approve.
- Before it builds a CMS package. You see the full list of content and property values first, including the fields it knows it cannot fill.
- After it builds the package. It waits while you import it and confirm the content is really there.
- After the browser run. Cases it could not judge come back to you as a hand-off list with screenshots — and, where a Figma link exists, the design export next to them.
You can skip any stage by saying so ("skip the UI check"). The skip is recorded in the summary's Remaining Risks, so nobody is misled about what was covered.
- It never writes to Jira or Azure DevOps. It only reads the ticket. Filing bugs in the tracker stays a manual step.
- It never touches the CMS. It writes a file; you import it.
- It never uploads anything. Exports stay local under
.qa/<ticket-id>/exports/. - It never judges a screenshot by eye. If confirming a result needs a human to look at it, the case is demoted to
manualand handed to you with evidence — it will not guess. - A
passmeans one narrow thing: the specific check it wrote held true. It does not mean the page looks right. That verdict is only ever yours. - It never invents a value it cannot verify. Fields the CMS generator has no proven way to fill are left empty and listed by name, so a blank in the CMS is not mistaken for a site bug.
- It never deletes a test case. Retired cases move to a
## Deprecatedsection and IDs are never reused, so history stays readable.
Edit the Markdown. Every artifact is a plain file, later stages read whatever is in it, and your edits survive re-runs and exports. You can also run any stage on its own — "write test cases for PROJ-123" — without going through the whole pipeline.
- Bun 1.3+
- Local Bun-managed export dependencies from the plugin root
package.json/bun.lock:exceljs,docx, andpdfkit - Playwright MCP runs via
npx @playwright/mcp@latest; Figma MCP is remote and optional - For
cms-test-data: a content-types export from the site under test at.qa/cms-schema/content-types.episerverdata, produced once in the CMS under Admin → Export Data with content types only (never a content export — it carries real customer data). Re-export it only when the site's schema changes.
Claude Code:
- Add marketplace
- Install Ritus QA
/plugin marketplace add precise-alloy/ritus-qa
/plugin install ritus-qa
GitHub Copilot CLI:
- Add the marketplace
- Fetch the plugin manifest
- Install Ritus QA
/plugin marketplace add precise-alloy/ritus-qa
/plugin marketplace browse ritus-qa-marketplace
/plugin install ritus-qa@ritus-qa-marketplace
-
Create
.qa/.env.localin the project under test (copytemplates/env.local.exampleor paste the block below) and fill in Jira and/or ADO credentials plus the test environment URL..qa/is git-ignored — never commit real tokens.# Ritus QA configuration — copy this file to .qa/.env.local and fill in values. # .qa/ is git-ignored; never commit real tokens. # --- Jira (leave blank if this project uses ADO only) --- JIRA_BASE_URL=https://client.atlassian.net JIRA_EMAIL=qa@example.com JIRA_PAT= # --- Azure DevOps (leave blank if this project uses Jira only) --- AZURE_DEVOPS_READONLY_PAT= AZURE_DEVOPS_ORG=client-org AZURE_DEVOPS_PROJECT=client-project # --- Test target --- TEST_BASE_URL=https://staging.client-site.com
-
Register the MCP servers in the project under test's
.mcp.json(the plugin ships no.mcp.json— each project owns its config). Add this to the project's.mcp.json:{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--isolated", "--caps=testing", "--output-dir", ".qa/screenshots"] }, "figma": { "type": "http", "url": "https://mcp.figma.com/mcp" } } }- The skills call the servers by the plain names
playwright/figma. If the project already has aplaywrightserver, merge the args above into it rather than adding a duplicate. - Playwright MCP allows all origins by default, so no origin flag is needed for testing across client environments.
- Figma is optional — omit the
figmablock if the project has no designs to reference. - You do not register
"cms-test-data"yourself. The plugin bundles that server in its own.mcp.json, which runs the published npm packagecms-test-data-mcpviabunx. The host starts it when the plugin is enabled;bunmust be on yourPATH. Onlyplaywrightandfigmaare yours to configure: they belong to the project under test, and MCP servers use last-wins precedence, so bundling them would override whatever the project already had under those names.
- The skills call the servers by the plain names
Tell your agent: "Start QA for PROJ-123" (or an ADO work item id). Or use any skill standalone: "write test cases", "run the tests", "check CMS content", "write a bug report", "wrap up testing", or "export test cases to Excel".
Artifacts land in .qa/<ticket-id>/ (git-ignored): <ticket-id>-plan.md, <ticket-id>-test-cases.md, execution-results.md, bugs/, summary.md, screenshots/, automation/, test-data/, rounds/, and exports/. The CMS schema export is shared across tickets at .qa/cms-schema/content-types.episerverdata.
When a ticket is updated after QA already tested it, run the same pipeline again — the skills notice the existing artifacts and amend them instead of starting over.
test-plan snapshots the previous ticket.json to rounds/ticket-r<N-1>.json, diffs the fields that carry QA meaning, and appends a dated entry to the plan's ## Change Log. test-case-design then classifies every existing case as rerun, skip, amend, new, or deprecated, and holds for your approval before touching the file — a wrong skip is the one failure mode no test can catch, so that call is yours. Case IDs are permanent, and retired cases move to a ## Deprecated group rather than being deleted. The approved classification is written into that round's Change Log entry, which is where test-summary later reads the reasons from.
auto-execute appends the round's rows to execution-results.md rather than overwriting it, so a case that failed in round 1 and passed in round 2 shows both. A skipped case gets no row at all — its absence is the record. test-summary reports the current round only, never blending rounds into one pass rate, names every case that was not re-run along with why it was judged unaffected, and reconciles each open bug against this round's result so a defect whose case now passes stops counting against the recommendation.
If the diff shows nothing changed and every case passed, the workflow says so and stops instead of regenerating identical artifacts.
cms-test-data reads the ticket's test cases and plans the content they need. It writes .qa/<ticket-id>/test-data/test-data.md — the review surface, listing every item with the property values it will be given — plus content-plan.json for the generator, and asks the QA to approve the data. Only then does it build .qa/<ticket-id>/test-data/<ticket-id>.episerverdata. The generator never touches the CMS: the QA exports the schema once, and imports the package under Admin → Import Data, choosing the destination there.
Broadly: pages, blocks, nested page trees, raster image media, and the common property kinds — text, HTML, ContentArea, content references and link collections — plus {{date:±Nd}} / {{date:±Nh}} / {{date:±Nm}} tokens for dates relative to now. Images are generated as placeholder PNGs at whatever size the plan asks for, and one image referenced by many pages stays one item and one file.
Some things it deliberately does not produce — SVG, PDF and video media, inline block properties, unpublished content, more than one language — and any property type it has no proven sample for is written empty with a warning rather than guessed. test-data.md lists every such field so a blank value in the CMS is not mistaken for a site bug.
The generator reports its own current capabilities. Its usage_guide tool returns the authoritative list — what it produces, what it refuses, the limits it enforces, and how re-import behaves — rendered from the constants the code actually enforces. The skill reads that at run time, so this summary is an orientation for choosing whether to install the plugin, not a specification. Where the two differ, usage_guide is right.
The generator is developed and released from its own repository, cms-test-data-mcp, and published to npm as cms-test-data-mcp. It is not part of this repo: it releases on its own cadence, and a plugin install is a copy of this repo without node_modules, so running the server from here would leave Bun auto-installing from its global cache — which fails on Windows.
To move to a newer generator, bump the pinned version in .mcp.json. Nothing else changes; bun test checks that the pin is an exact version rather than a floating one, because an unpinned spec is re-resolved on every session start (~11s) instead of being served from cache (~0.7s).
artifact-export exposes the supported local export matrix:
| Markdown source | Export path |
|---|---|
.qa/<ticket-id>/<ticket-id>-test-cases.md |
.qa/<ticket-id>/exports/<ticket-id>-test-cases.xlsx |
.qa/<ticket-id>/<ticket-id>-plan.md |
.qa/<ticket-id>/exports/<ticket-id>-plan.docx, .qa/<ticket-id>/exports/<ticket-id>-plan.pdf |
The Markdown file remains the source of truth. Exporting never regenerates content, so any manual Markdown edits carry straight into the next export. Migration is strict: there is no legacy fallback and no automatic rename. The skills and exporter only read/write the canonical ticket-prefixed Markdown and export filenames.
Plan DOCX/PDF exports render supported GFM Markdown semantics rather than copying Markdown delimiters. Images become alt text; raw HTML becomes text; only http/https/mailto links are clickable, while unsafe links remain plain text labels.
<plugin-root> is the installed plugin directory containing package.json, bun.lock, and scripts/export-artifact.ts. Run every CLI command from <project-root>, the project-under-test root that contains .qa. Invoke the CLI by the plugin-root script path so .qa/<ticket-id>/... remains in the target project. Do not change to <plugin-root> to run an export.
# Current directory: <project-root>
bun "<plugin-root>\scripts\export-artifact.ts" --ticket PROJ-123 --artifact test-cases --format xlsx
bun "<plugin-root>\scripts\export-artifact.ts" --ticket PROJ-123 --artifact test-plan --format docx,pdfTo regenerate the SM26-207 plan exports from the project-under-test root:
bun "<plugin-root>\scripts\export-artifact.ts" --ticket SM26-207 --artifact test-plan --format docx,pdf<ticket-id>-test-cases.xlsx preserves the portable Priority and Completed values as plain text in real Microsoft Excel dropdowns backed by the hidden _Validation sheet workbook named ranges PriorityOptions and CompletionStatusOptions:
-
High -
Medium -
Low -
Not run -
Done -
Fail
When .qa/<ticket-id>/exports/<ticket-id>-test-cases.xlsx already exists, the exporter supports carry-forward of existing Completed values by test-case ID into the regenerated workbook. New IDs default to Not run; removed IDs are not copied forward.
Exports stay local under .qa/<ticket-id>/exports/. The skill and CLI do not upload anything to Microsoft 365, Google Drive, Google Sheets, or any other cloud service.
- Microsoft 365 (Windows/macOS):
<ticket-id>-test-cases.xlsx,<ticket-id>-plan.docx, and<ticket-id>-plan.pdfare first-class local outputs. - Google Drive: QAs can upload the generated DOCX/PDF/XLSX files manually for sharing or preview.
- Google Sheets: opening
<ticket-id>-test-cases.xlsxthrough Drive/Sheets may normalize nonessential formatting, but Google Sheets dropdown conversion/editing forPriorityandCompletedis not supported.
Run this release checklist manually after generating the local exports:
- Open
<ticket-id>-test-cases.xlsx,<ticket-id>-plan.docx, and<ticket-id>-plan.pdfin Microsoft 365 on Windows or macOS. - Upload
<ticket-id>-test-cases.xlsx,<ticket-id>-plan.docx, and<ticket-id>-plan.pdfto Google Drive and verify DOCX/PDF preview. - Confirm the visible headers are exactly
ID,Group,Title,Priority,Triage,Preconditions,Steps,Expected Result,Completed;Completedis the final column; column D isPriority; and column I isCompleted. - Select a
Prioritycell in column D and aCompletedcell in column I in Microsoft Excel. For each selected cell, confirm the dropdown arrow appears and its options are exactlyHigh/Medium/LoworNot run/Done/Fail. - Select
MediumandDone, save the workbook, then re-export it to verify carry-forward by test-case ID.
After auto-execute runs, it records the executed steps into a standalone Playwright spec via scripts/record-playwright.ts. The agent asks once per project which style you want and remembers the answer in .qa/e2e-style:
| Style | What you get | Best when |
|---|---|---|
| structured | Specs follow (or scaffold) a full e2e suite: playwright.config.ts, two-layer page objects, projects by scope (smoke/regression/visual/cms), CMS auth via storageState, eslint/prettier/tsconfig |
You want a maintainable e2e suite to grow over time |
| one-file-per-ticket | A single self-contained spec at .qa/<ticket-id>/automation/<ticket-id>.spec.ts, runnable with bunx playwright test — no page objects, no scaffold |
You just want a runnable record of this ticket's cases with minimal ceremony |
structured, project already has an e2e suite (playwright.config.ts): the spec is written into your existing structure, untouched config.
structured, no e2e suite: the agent offers to scaffold the bundled template (templates/e2e/). Then run once in the scaffolded project:
npm install
npx playwright install
cp .env.example .env # fill in values
npx playwright test --project regressionThe template's own dependencies (@playwright/test, dotenv, lint/format) live in the scaffolded project, not the plugin.
one-file-per-ticket: nothing to install in your project; the spec lives under .qa/ and runs standalone.
bun test