Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 46 additions & 22 deletions .cursor/rules/playwrighter.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,77 @@ description: Generate production-quality Playwright tests using official best pr
globs:
- "**/*.spec.ts"
- "**/*.spec.tsx"
- "**/*.setup.ts"
- "**/playwright.config.ts"
- "**/playwright.config.js"
- "**/fixtures.ts"
- "**/tests/**/*.ts"
- "**/tests/*.{ts,tsx}"
- "**/e2e/**/*.ts"
- "**/pages/**/*.ts"
alwaysApply: false
---

# Playwrighter

**Canonical body:** `<workspace>/playwrighter/skill/SKILL.md` (single source of truth). Optional user mirror: symlink `skill/SKILL.md` into your agent's skills directory. Read the canonical SKILL.md in full when this rule loads.
**Canonical body:** `skill/SKILL.md` at the playwrighter repo root (single source of truth). If this repo is vendored as a subfolder, prefix paths with `playwrighter/` (e.g. `playwrighter/skill/SKILL.md`). Optional user mirror: symlink `skill/SKILL.md` into your agent's skills directory. Read the canonical SKILL.md in full when this rule loads.

## Mandatory before writing/editing tests

1. Read `playwrighter/skill/SKILL.md` (full)
2. Read `playwrighter/patterns/locator-strategy.md`
3. Read `playwrighter/patterns/waiting-timing.md`
4. Read `playwrighter/patterns/anti-patterns.md`
5. Read the task-specific pattern from the index in `SKILL.md`
1. Read `skill/SKILL.md` (full)
2. Read `patterns/locator-strategy.md`
3. Read `patterns/waiting-timing.md`
4. Read `patterns/assertions.md`
5. Read `patterns/anti-patterns.md`
6. Read the task-specific pattern from the index in `SKILL.md`

## Core invariants (NEVER break)
## Output contract (every generated spec)

- Import `test` / `expect` from `./fixtures` (or your project's fixture module), not raw `@playwright/test` in spec files
- `[TC-XXX]` ID in each test title
- Priority + category tags (`@P0 @smoke`, etc.)
- Web-first assertions after each action
- Accessible locators (prefer `npx playwright codegen <url>` over hand-written CSS)
- `./tools/validate-suite.sh` exits 0 (errors block; warnings need `--strict` or review)
- `node tools/score-tests.js` ≥ 80/100

## Core invariants (NEVER break in generated specs)

- Locator priority: `getByRole > getByLabel > getByText > getByTestId > CSS`
- NEVER `page.waitForTimeout()`
- NEVER `waitForLoadState('networkidle')`
- NEVER `expect(await x.isVisible()).toBe(true)` — use `await expect(x).toBeVisible()`
- NEVER `.toBeTruthy()` for UI assertions
- NEVER `.toBeTruthy()` / `.toBeFalsy()` for UI assertions (API shape checks: use `toMatchObject` / specific matchers)
- NEVER `test.only()` or `page.pause()` in committed code
- NEVER conditional branching in tests (`if (await ...)`) — split into separate tests
- NEVER `{ force: true }` without justification
- Prefer no `if (await …)` branching — split into separate tests (`validate-suite.sh` warns; scorer −3)
- NEVER `{ force: true }` without justification (`validate-suite.sh` warns; scorer −2)
- POMs: navigation sanity checks after `goto()` only; test assertions stay in spec files (see `patterns/page-object-model.md`)

## Workflow

1. Read patterns relevant to the task
2. Use `templates/` as starting points (don't reinvent config/fixtures/POMs)
3. Write tests per `templates/test-template.ts` shape (AAA, `test.step`, tagged)
4. Validate with `tools/validate-suite.sh` and `tools/score-tests.js`
5. Reference 2026 features when appropriate: Test Agents, component testing, devices registry
1. Read patterns relevant to the task (baseline four + task-specific from `SKILL.md`)
2. `npx playwright codegen <url>` for locators before hand-writing CSS
3. Use `templates/` as starting points; copy `tools/` into the consumer project if using `npm run validate` / `score`
4. Write tests per `templates/test-template.ts` (AAA, `test.step` where traces benefit, tagged)
5. Validate: `tools/validate-suite.sh` then `node tools/score-tests.js`
6. Optional: `patterns/test-agents.md` (Planner/Generator/Healer), `patterns/component-testing.md`, `patterns/mobile-responsive.md` (`devices` from `@playwright/test`)

## Enforcement vs docs

| Layer | Role |
|-------|------|
| `patterns/anti-patterns.md` | Full NEVER list (not all rows are machine-checked) |
| `tools/validate-suite.sh` | Hard errors: timeouts, networkidle, manual isVisible/textContent, toBeTruthy, test.only, page.pause |
| `tools/score-tests.js` | 100-pt rubric; TC IDs, tags, expect density, CSS in `.locator()`, etc. |

## File map
## File map (repo root)

```
playwrighter/
├── skill/SKILL.md ← canonical body
├── patterns/ ← 23 pattern files
├── templates/ ← config, fixtures, POMs, test scaffold
├── tools/validate-suite.sh ← anti-pattern linter
└── tools/score-tests.js ← 100-pt quality scorecard
skill/SKILL.md
patterns/ ← 23 pattern files
templates/ ← config, fixtures, POMs, test scaffold
tools/validate-suite.sh
tools/score-tests.js
```

For task-specific patterns, follow the index in `SKILL.md`.
4 changes: 4 additions & 0 deletions .github/workflows/dogfood-northwind-qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ jobs:
with:
node-version: "22"

- name: Lint northwind-qa with validate-suite
run: |
playwrighter/tools/validate-suite.sh northwind-qa/tests/

- name: Score northwind-qa suite against playwrighter rubric
run: |
node playwrighter/tools/score-tests.js northwind-qa/tests/ --threshold=80
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

## [3.1.1] - 2026-05-24

### Fixed

- `.cursor/rules/playwrighter.mdc`: repo-root paths, vendored-prefix note, `assertions.md` mandatory read, output contract, codegen, enforcement table, expanded globs; removed erroneous "devices registry" wording.
- `templates/`: copy `tools/` documented; `scripts/check-tools.js` guards `npm run validate` / `score`; POM assertions moved out of `LoginPage.expectError` into spec template.
- `patterns/anti-patterns.md`: POM assertion row aligned with `page-object-model.md` sanity-check carve-out.
- `README.md` / `GETTING_STARTED.md`: dual integration paths (repo root vs subfolder); `@playwright/test` ≥ 1.50.
- CI dogfood: run `validate-suite.sh` on northwind-qa before scorer.
- `tools/score-tests.js`: penalize `@playwright/test` imports in spec files (custom fixtures expected).

### Added

- `scripts/cursor-sdk/apply-harness-fixes.mjs`: optional Cursor SDK runner to re-apply harness fixes (`CURSOR_API_KEY` required).

## [3.1.0] - 2026-05-24

### Added
Expand Down
4 changes: 3 additions & 1 deletion GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ Copy this guide to go from zero to passing test.
```bash
mkdir my-tests && cd my-tests

# Copy templates from playwrighter
# Copy templates + quality tools from playwrighter
cp -r /path/to/playwrighter/templates/. .
cp -r /path/to/playwrighter/tools ./tools
chmod +x ./tools/validate-suite.sh

# Install
npm install
Expand Down
4 changes: 2 additions & 2 deletions INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,5 @@ playwrighter/

---

**Version**: 3.0.0
**Updated**: 2026-05-14
**Version**: 3.1.1
**Updated**: 2026-05-24
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ If you want to see a suite that takes playwrighter's rubric seriously, that's th
## Requirements

- **Node.js** ≥ 18
- **`@playwright/test`** ≥ 1.40 (templates use modern features)
- **`@playwright/test`** ≥ 1.50 (templates and Test Agents patterns; see `templates/package.json`)
- For accessibility patterns: **`@axe-core/playwright`** ≥ 4.10

See `templates/package.json` for a full dependency manifest.
Expand All @@ -161,26 +161,36 @@ All patterns trace to primary sources:
Canonical skill body: **`skill/SKILL.md`** at the playwrighter repo root. Patterns and tools live alongside it in the same checkout.

### Cursor
Symlink the rule into your project or user rules:

**From inside the playwrighter repo** (`pwd` is the clone root):
```bash
mkdir -p <your-project>/.cursor/rules
ln -sf "$(pwd)/playwrighter/.cursor/rules/playwrighter.mdc" \
<your-project>/.cursor/rules/playwrighter.mdc
mkdir -p ~/.cursor/rules
ln -sf "$(pwd)/.cursor/rules/playwrighter.mdc" ~/.cursor/rules/playwrighter.mdc
```
Or per-user:

**From a parent monorepo** (playwrighter is a subfolder):
```bash
mkdir -p <your-project>/.cursor/rules
ln -sf "$(pwd)/playwrighter/.cursor/rules/playwrighter.mdc" \
~/.cursor/rules/playwrighter.mdc
<your-project>/.cursor/rules/playwrighter.mdc
```
Triggers via globs (`*.spec.ts`, `playwright.config.ts`, `tests/**/*.ts`) or model decision. The rule instructs the agent to read `skill/SKILL.md` first.

Triggers via globs (`*.spec.ts`, `playwright.config.ts`, `fixtures.ts`, `pages/**/*.ts`, etc.) or model decision. The rule instructs the agent to read `skill/SKILL.md` first (repo-root paths; vendored installs use a `playwrighter/` prefix).

### Other agents (skills.sh, Windsurf, CLI tools with a skills directory)
Symlink the canonical skill file into your agent's skill directory (adjust paths to your tool):

**From playwrighter repo root:**
```bash
mkdir -p ~/.your-agent/skills/playwrighter
ln -sf "$(pwd)/skill/SKILL.md" ~/.your-agent/skills/playwrighter/SKILL.md
```

**From parent monorepo:**
```bash
ln -sf "$(pwd)/playwrighter/skill/SKILL.md" ~/.your-agent/skills/playwrighter/SKILL.md
```
Keep the full playwrighter repo checkout available so `patterns/` and `tools/` resolve.

Keep the full playwrighter checkout available so `patterns/` and `tools/` resolve.

### Reference patterns in your project's AI rules
```markdown
Expand Down
2 changes: 1 addition & 1 deletion patterns/anti-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ A single reference for what NOT to do, with replacements.
| `expect(await x.count()).toBeTruthy()` | `await expect(x).toHaveCount(n)` | Specific failure msg |
| `expect(await x.textContent()).toContain('y')` | `await expect(x).toContainText('y')` | Web-first assertion |
| Action without assertion: `await btn.click()` (end of test) | Add `await expect(...)` after | Test passes if nothing happened |
| Assertions inside POM | Assertions in test files | Couples POM to test intent |
| Test assertions inside POM (beyond `goto()` sanity checks) | Assertions in spec files; POM exposes actions/locators only | Couples POM to test intent |

---

Expand Down
7 changes: 3 additions & 4 deletions patterns/page-object-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,10 @@ export class LoginPage {
await this.passwordInput.fill(password);
await this.submitButton.click();
}

async expectError(text: string) {
await expect(this.errorMessage).toContainText(text);
}
}

// In the spec file (not the POM):
// await expect(loginPage.errorMessage).toContainText('Invalid credentials');
```

---
Expand Down
23 changes: 23 additions & 0 deletions scripts/cursor-sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Cursor SDK harness fix runner

Optional automation for the playwrighter adversarial-review fixes (3.1.1). The fixes are already applied in-tree; use this to re-verify or re-apply on a fork.

## Prerequisites

- Node.js ≥ 18
- `CURSOR_API_KEY` from [Cursor dashboard](https://cursor.com/settings)

## Run

```bash
cd scripts/cursor-sdk
npm install
export CURSOR_API_KEY=your_key
npm run apply-fixes
```

Uses `Agent.prompt()` with **local** runtime (`cwd` = playwrighter repo root) per the Cursor SDK docs.

## Without SDK

Apply the same checklist manually from `CHANGELOG.md` [3.1.1] or use the IDE agent with `.cursor/rules/playwrighter.mdc` loaded.
56 changes: 56 additions & 0 deletions scripts/cursor-sdk/apply-harness-fixes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env node
/**
* Re-apply playwrighter harness fixes using the Cursor SDK (local agent).
*
* Usage (from playwrighter repo root):
* export CURSOR_API_KEY=...
* cd scripts/cursor-sdk && npm install && npm run apply-fixes
*
* Idempotent with respect to 3.1.1: safe to re-run; agent should no-op if already fixed.
*/
import { Agent, CursorAgentError } from '@cursor/sdk';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');

const PROMPT = `You are fixing the playwrighter repo harness after an adversarial review. Work only under ${repoRoot}.

Verify and fix if still broken:

P0: .cursor/rules/playwrighter.mdc uses repo-root paths (skill/, patterns/, tools/), vendored playwrighter/ prefix note, assertions.md in mandatory reads, no "devices registry" hallucination.

P1: templates/README.md and GETTING_STARTED.md document copying tools/; templates/scripts/check-tools.js exists; templates/package.json validate/score use check-tools; LoginPage has no expectError (assertions in spec); anti-patterns.md POM row matches page-object-model sanity carve-out.

P2: tools/score-tests.js warns on @playwright/test imports in .spec/.test files; .github/workflows/dogfood-northwind-qa.yml runs validate-suite.sh before score-tests; playwrighter.mdc enforcement table documents validate vs scorer.

P3: playwrighter.mdc globs include fixtures, setup, pages; README integration has repo-root vs subfolder symlink instructions; README requires @playwright/test >= 1.50; INDEX version 3.1.1 dated 2026-05-24.

Run: node tools/score-tests.js on any local .spec.ts if present. Do not commit unless asked.

When done, print a one-line summary of files changed or "already compliant".`;

const apiKey = process.env.CURSOR_API_KEY;
if (!apiKey) {
console.error('CURSOR_API_KEY is not set. Export it or run fixes in the IDE agent instead.');
process.exit(1);
}

try {
const result = await Agent.prompt(PROMPT, {
apiKey,
model: { id: 'composer-2.5' },
local: { cwd: repoRoot },
});
if (result.status === 'error') {
console.error('Agent run failed:', result.id);
process.exit(2);
}
console.log(result.result ?? '(no text result)');
} catch (err) {
if (err instanceof CursorAgentError) {
console.error('SDK startup failed:', err.message, 'retryable=', err.isRetryable);
process.exit(1);
}
throw err;
}
Loading
Loading