The real gains I get from an AI coding agent in QA work aren't in the clever bits. They're in the menial crap around the tests: pasting a ticket's acceptance criteria into a prompt, explaining again that we don't use waitForTimeout, naming the branch, writing the PR description in the team format. Three pieces of configuration take most of that off the table: a rules file, a few skills, and an MCP server for the issue tracker. Below are copy-pasteable files for a TypeScript + Playwright suite (plus a short pytest version), checked against the Claude Code, Cursor and Atlassian docs in October 2026.
Rules, skills and reach do different jobs
They're easy to conflate:
- A rules file is house style. The agent reads it at the start of every session: how tests are written here, what's banned, which commands to run.
- A skill extends the agent's reasoning for one task. It's a named workflow that tells the agent how to do something, step by step.
- An MCP server extends the agent's reach. It's a connector that gives the agent API access to an external service, like Jira or Confluence.
Skills are the "how." MCP servers are the "what it can touch." The useful combinations use both: a shipping skill that calls the issue-tracker MCP server to pull a ticket's summary, then uses it to name the branch and seed the commit message.
Write one rules file and point every tool at it
The file I care about most is AGENTS.md in the repo root, because both tools read it:
| Tool | Project instructions | Rules for some files |
|---|---|---|
| Claude Code | CLAUDE.md, or AGENTS.md when there's no CLAUDE.md |
.claude/rules/*.md with paths: |
| Cursor | AGENTS.md, plain markdown with no metadata |
.cursor/rules/*.mdc with globs: |
Claude Code reads AGENTS.md directly only when there's no CLAUDE.md in or above the working directory (v2.1.277 or later). If you already have a CLAUDE.md, put @AGENTS.md at the top of it to import the shared file, and keep any Claude-only notes below.
A starting point for a Playwright suite (the paths are illustrative):
AGENTS.md# Test suite rulesStack: TypeScript, Playwright Test. Run `npx playwright test` before saying a change works.## Writing testsLocate elements with `getByRole`, `getByLabel` or `getByTestId`. No CSS or XPath tied to DOM structure.Use web-first assertions: `await expect(locator).toBeVisible()`, never `expect(await locator.isVisible()).toBe(true)`.No `page.waitForTimeout()`. Wait for the state you need with an assertion.Each test creates its own data through fixtures or the API. No test relies on another test's leftovers.Mock third-party services at the network boundary with `page.route()`. Don't mock our own modules.Page objects live in `tests/pages/` and come from the fixtures in `tests/fixtures.ts`.## NeverNever print, log or commit secrets, tokens or `.env` values.Never point a test at production. Base URLs come from `playwright.config.ts`.Never use real card numbers or customer data. Use the factories in `tests/data/`.Never add a retry or a longer timeout to make a flaky test pass. Report it instead.
The locator, assertion, isolation and page.route() rules come straight from Playwright's best practices. The "Never" list is mine, and on a payments codebase it's the part that matters.
If you also run a Python suite, say pytest against a GraphQL API backed by Neo4j, the same file takes a short section:
AGENTS.md## pytest suite (api-tests/)Setup and teardown go in pytest fixtures in `conftest.py`, not `setUp`/`tearDown` methods.No `time.sleep()`. Poll for the condition with a timeout.Mock HTTP at the client boundary, not our own functions.Each test cleans up the graph data it creates. Never run against a shared database.
For rules that only apply to some files, both tools support scoping. In Cursor a project rule must use the .mdc extension (a plain .md file in .cursor/rules is ignored):
.cursor/rules/playwright-specs.mdc--globs: tests/**/*.spec.tsalwaysApply: false--One `test.describe` per user journey.Tag smoke tests with `@smoke` in the title.
The Claude Code equivalent is .claude/rules/playwright-specs.md, with a paths: list of quoted globs in place of globs:. For a bigger worked example, see this site's own AGENTS.md: working rules, content rules and a repo map.
One caveat from the Claude Code docs: these files are context, not enforced configuration. To actually block an action you need permission rules or a sandbox, which is what How to Sandbox Your AI Coding Agent with a Dev Container covers.
Package the routine workflows as skills
A good prompt helps you once. A skill captures it as a workflow anyone can invoke without remembering the details. In Claude Code a skill is a folder with a SKILL.md in it: .claude/skills/<name>/ in the repo (commit it to share), or ~/.claude/skills/<name>/ for just you. You run it as /<name>, and Claude can also load it on its own when a request matches the description. Custom slash commands have been merged into skills, so an old .claude/commands/ship.md file still works.
The one I use most is shipping a change. Done ad hoc, every engineer prompts differently and you get different branch names, commit styles and PR descriptions. As a skill it runs the same steps every time:
.claude/skills/commit-and-mr/SKILL.md--name: commit-and-mrdescription: Branch, commit, push and open a merge request for the current change using team conventions.disable-model-invocation: trueallowed-tools: Bash(git diff *)--1. Use the ticket key from $ARGUMENTS, or ask for one. Fetch the ticket summary.2. Run `git status` and `git diff` to see what's staged and unstaged.3. Create a branch named `<type>/<TICKET-KEY>-<short-summary>`.4. Commit with `<TICKET-KEY>: <imperative summary>`.5. Push to origin.6. Open the merge request with three sections: What, Files Changed, How to Test.7. Report the link and a one-line summary.
disable-model-invocation: true means only a person can start it, which is what I want for anything that pushes. allowed-tools lets the read-only git diff run without a prompt while the skill is active. Nobody has to remember the branch-naming rule, because the skill is the rule.
Other QA chores with a "correct" shape that's tedious to re-explain: cutting a hotfix (cherry-pick a merged PR's commits onto a release branch), scaffolding a spec from a ticket's acceptance criteria, and writing tests for the current diff in house style. A wiki page relies on people remembering it under deadline pressure. Write the convention down once, as a skill, and it stops being something you enforce.
Give the agent reach with an MCP server
An agent is good at code but blind to everything around it: the ticket, the design doc, the wiki page. An MCP server fixes that. For Jira and Confluence, Atlassian hosts one, the Rovo MCP Server, at https://mcp.atlassian.com/v2/mcp. You sign in with OAuth 2.1, and it acts with your existing Atlassian permissions and no more. It reads, and it can also create, edit, transition and comment on Jira work items and create or update Confluence pages.
In Claude Code:
claude mcp add --transport http --scope project atlassian https://mcp.atlassian.com/v2/mcp# then, inside a session, run /mcp and follow the browser login
--scope project writes .mcp.json in the repo root so the team shares one definition. The default local scope keeps it private to you in ~/.claude.json, and user makes it live in every project. Cursor reads the same idea from .cursor/mcp.json:
.cursor/mcp.json{"mcpServers": {"atlassian": {"url": "https://mcp.atlassian.com/v2/mcp"}}}
Now "look up ABC-1234 and start a branch for it" works without copy-paste, and the commit-and-mr skill can fetch the ticket summary itself.
Keep write access on a leash
Reach cuts both ways. A connector that can read your issue tracker is low risk. A connector that can write is acting on shared systems your teammates rely on, with your name on every change. The MCP spec makes authorization optional, uses OAuth 2.1 for HTTP servers, and has local stdio servers take credentials from the environment, so check which kind you're installing.
- Project scope, so it's only live in repos that need it
- OAuth sign-in, scoped and revocable
- Reads allowed, writes behind a prompt (the rules below)
- Read the ticket, not the attachments with customer data
- User scope, live in every directory you open
- A long-lived API token pasted into a config file
- Every tool auto-approved so the prompts go away
- One connector shared between a test site and production
In Claude Code, a bare "ask": ["mcp__atlassian"] matches every tool on that server, so it prompts on reads too. Tool-name globs scope the rules instead:
.claude/settings.json{"permissions": {"allow": ["mcp__atlassian__get*","mcp__atlassian__list*","mcp__atlassian__search*"],"ask": ["mcp__atlassian__create*","mcp__atlassian__edit*","mcp__atlassian__update*","mcp__atlassian__transition*","mcp__atlassian__addOrEdit*"]}}
Ask rules are checked before allow rules, and only an allow rule skips the prompt, so a tool on neither list (a delete, an attachment download) still asks. Cursor asks for approval before MCP tools by default. Either way the agent's output still needs review, and the AI code smells checklist is what I run over its diffs.
Put each convention where the agent reads it every run: house style in AGENTS.md, routine workflows in skills, and ticket access behind an MCP server that asks before it writes.
Why it matters for your team
On a team shipping payment code, this setup lets the agent read the ticket and follow the house rules while it stays away from customer data, PCI scope and production credentials.
The enforcement side is in How to Sandbox Your AI Coding Agent with a Dev Container. For the page objects the rules file refers to, see Vanilla Playwright with Page Object Model.