openspec-playwright 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude/commands/opsx/e2e.md +8 -0
  2. package/.claude/skills/openspec-e2e/SKILL.md +138 -0
  3. package/.github/workflows/release.yml +41 -0
  4. package/README.md +133 -0
  5. package/README.zh-CN.md +118 -0
  6. package/bin/openspec-pw +4 -0
  7. package/bin/openspec-pw.js +2 -0
  8. package/dist/commands/doctor.d.ts +1 -0
  9. package/dist/commands/doctor.js +110 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.d.ts +6 -0
  12. package/dist/commands/init.js +174 -0
  13. package/dist/commands/init.js.map +1 -0
  14. package/dist/commands/run.d.ts +5 -0
  15. package/dist/commands/run.js +135 -0
  16. package/dist/commands/run.js.map +1 -0
  17. package/dist/commands/update.d.ts +5 -0
  18. package/dist/commands/update.js +91 -0
  19. package/dist/commands/update.js.map +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +40 -0
  22. package/dist/index.js.map +1 -0
  23. package/docs/plans/2026-03-26-openspec-playwright-design.md +180 -0
  24. package/package.json +39 -0
  25. package/schemas/playwright-e2e/schema.yaml +56 -0
  26. package/schemas/playwright-e2e/templates/e2e-test.ts +55 -0
  27. package/schemas/playwright-e2e/templates/playwright.config.ts +52 -0
  28. package/schemas/playwright-e2e/templates/report.md +27 -0
  29. package/schemas/playwright-e2e/templates/test-plan.md +24 -0
  30. package/src/commands/doctor.ts +114 -0
  31. package/src/commands/init.ts +209 -0
  32. package/src/commands/run.ts +172 -0
  33. package/src/commands/update.ts +130 -0
  34. package/src/index.ts +47 -0
  35. package/templates/auth.setup.ts +77 -0
  36. package/templates/credentials.yaml +33 -0
  37. package/templates/seed.spec.ts +63 -0
  38. package/tsconfig.json +18 -0
@@ -0,0 +1,180 @@
1
+ # OpenSpec + Playwright E2E Integration Design
2
+
3
+ **Date:** 2026-03-26
4
+ **Updated:** 2026-03-27
5
+ **Status:** Implemented (MVP)
6
+
7
+ ## Overview
8
+
9
+ Integrate OpenSpec's spec-driven development workflow with Playwright Test Agents' three-agent harness (Planner / Generator / Healer) for automated E2E verification.
10
+
11
+ **Design decision:** Add a new independent command `/openspec-e2e` rather than hooking into `/opsx:verify`. This keeps concerns separated and allows each verification to be run independently.
12
+
13
+ ## Architecture
14
+
15
+ ```
16
+ openspec-pw (CLI - setup only)
17
+ openspec-pw init → installs Playwright + configures MCP + installs skill
18
+ openspec-pw doctor → checks prerequisites
19
+
20
+ /openspec-e2e (Claude Code skill - runs in Claude)
21
+
22
+ ├── 1. Read OpenSpec specs from openspec/changes/<name>/specs/
23
+ ├── 2. Planner Agent → specs/playwright/test-plan.md
24
+ ├── 3. Generator Agent → tests/playwright/<name>.spec.ts
25
+ └── 4. Healer Agent → run tests + auto-heal
26
+
27
+ └── Report: openspec/reports/playwright-e2e-<name>-<ts>.md
28
+ ```
29
+
30
+ ### Two Verification Layers
31
+
32
+ | Layer | Command | Runs in | What it checks |
33
+ |-------|---------|---------|----------------|
34
+ | Static | `/opsx:verify` | Claude Code (OpenSpec skill) | Implementation matches artifacts |
35
+ | E2E | `/openspec-e2e` | Claude Code (this skill) | App works when running |
36
+
37
+ ## Key Design Decisions
38
+
39
+ - **Separate command, not hook**: `/openspec-e2e` is independent from `/opsx:verify`. Users run them separately or together.
40
+ - **CLI as setup only**: The CLI does not run agents. It only installs/configures. Agents run in Claude Code.
41
+ - **Playwright MCP**: Playwright agents use the MCP protocol, configured in `.claude/settings.local.json`.
42
+ - **Seed test**: A `tests/playwright/seed.spec.ts` template guides the Generator agent.
43
+ - **No re-exploration**: Planner uses OpenSpec specs directly as the source of truth, no app exploration needed.
44
+
45
+ ## CLI Commands
46
+
47
+ ```bash
48
+ npm install -g wxhou/openspec-playwright
49
+
50
+ openspec-pw init # Setup: Playwright + MCP + skill + seed
51
+ openspec-pw doctor # Check prerequisites
52
+ ```
53
+
54
+ ## What `openspec-pw init` Does
55
+
56
+ 1. `npx playwright init-agents --loop=claude` — installs Playwright agents
57
+ 2. Configure Playwright MCP in `.claude/settings.local.json`
58
+ 3. Install skill: `.claude/skills/openspec-e2e/SKILL.md`
59
+ 4. Install command: `.claude/commands/opsx-e2e.md`
60
+ 5. Generate `tests/playwright/seed.spec.ts` template
61
+
62
+ ## SKILL.md Format
63
+
64
+ Follows the OpenSpec standard format:
65
+ - YAML frontmatter: `name`, `description`, `license`, `compatibility`, `metadata`
66
+ - Bold step names: `**Step 1: Name**`
67
+ - Output fenced code blocks: `**Output During Implementation**`, `**Output On Completion**`
68
+ - Guardrails section
69
+ - Fluid Workflow Integration section
70
+
71
+ ## Directory Structure
72
+
73
+ ```
74
+ project/
75
+ ├── .claude/
76
+ │ ├── skills/
77
+ │ │ └── openspec-e2e/
78
+ │ │ └── SKILL.md # The /openspec-e2e skill
79
+ │ ├── commands/
80
+ │ │ └── opsx-e2e.md # The /openspec-e2e command
81
+ │ └── settings.local.json # Playwright MCP config
82
+ ├── .github/ # Playwright agent definitions
83
+ │ └── ...
84
+ ├── openspec/
85
+ │ ├── changes/<name>/
86
+ │ │ ├── specs/
87
+ │ │ │ ├── *.md # OpenSpec propose output
88
+ │ │ │ └── playwright/
89
+ │ │ │ └── test-plan.md # Planner output
90
+ │ │ ├── design.md
91
+ │ │ └── tasks.md
92
+ │ └── reports/
93
+ │ └── playwright-e2e-<name>-<ts>.md # Healer report
94
+ └── tests/playwright/
95
+ ├── seed.spec.ts # Seed test template
96
+ └── <name>.spec.ts # Generated tests
97
+ ```
98
+
99
+ ## SKILL.md Sections (OpenSpec Standard)
100
+
101
+ ```yaml
102
+ ---
103
+ name: openspec-e2e
104
+ description: Run Playwright E2E verification for an OpenSpec change...
105
+ license: MIT
106
+ compatibility: Requires openspec CLI and Playwright Test Agents...
107
+ metadata:
108
+ author: openspec-playwright
109
+ version: "0.1.0"
110
+ generatedBy: "openspec-playwright"
111
+ ---
112
+
113
+ Run Playwright E2E verification for an OpenSpec change...
114
+
115
+ ## Step 1: Identify the Change
116
+ ...
117
+
118
+ ## Step 2: Verify Prerequisites
119
+ ...
120
+
121
+ **Output During Implementation**
122
+ ```markdown
123
+ ## E2E Verification: <name>
124
+ Status: 🔄 In Progress
125
+ ```
126
+ ```
127
+
128
+ **Output On Completion**
129
+ ```markdown
130
+ ## E2E Verify Report: <name>
131
+ | Check | Status |
132
+ |-------|--------|
133
+ ...
134
+ ```
135
+ ```
136
+
137
+ **Guardrails**
138
+ - Always read specs from `openspec/changes/<name>/specs/` as source of truth
139
+ - Do not generate tests that contradict the specs
140
+ - Cap auto-heal attempts at 3
141
+ ...
142
+
143
+ **Fluid Workflow Integration**
144
+ - Before: /opsx:propose → /opsx:apply
145
+ - This skill: /openspec-e2e
146
+ - After: /opsx:archive
147
+ ```
148
+
149
+ ## Error Handling
150
+
151
+ | Scenario | Action |
152
+ |----------|--------|
153
+ | No specs found | Prompt to run `/opsx:propose` first |
154
+ | Prerequisites missing | Prompt to run `openspec-pw init` |
155
+ | Dev server not running | Prompt to start before proceeding |
156
+ | Planner fails | Log error, mark FAILED |
157
+ | Generator fails | Log error, mark FAILED |
158
+ | Healer guardrails trigger | Report failures, mark PARTIAL |
159
+
160
+ ## Project Structure
161
+
162
+ ```
163
+ openspec-playwright/
164
+ ├── src/
165
+ │ ├── index.ts # CLI entry
166
+ │ └── commands/
167
+ │ ├── init.ts # openspec-pw init
168
+ │ └── doctor.ts # openspec-pw doctor
169
+ ├── .claude/
170
+ │ ├── skills/
171
+ │ │ └── openspec-e2e/
172
+ │ │ └── SKILL.md # The Claude Code skill
173
+ │ └── commands/
174
+ │ └── opsx-e2e.md # The command file
175
+ ├── templates/
176
+ │ └── seed.spec.ts # Playwright seed test template
177
+ ├── README.md
178
+ ├── package.json
179
+ └── tsconfig.json
180
+ ```
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "openspec-playwright",
3
+ "version": "0.1.16",
4
+ "description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "openspec-pw": "./bin/openspec-pw.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepublishOnly": "npm run build",
12
+ "release": "npm version patch && npm run build && git push && git push --tags"
13
+ },
14
+ "dependencies": {
15
+ "chalk": "^5.3.0",
16
+ "commander": "^12.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "typescript": "^5.6.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "keywords": [
26
+ "openspec",
27
+ "playwright",
28
+ "e2e",
29
+ "testing",
30
+ "claude-code",
31
+ "spec-driven",
32
+ "claude-code-skill"
33
+ ],
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/wxhou/openspec-playwright"
38
+ }
39
+ }
@@ -0,0 +1,56 @@
1
+ name: playwright-e2e
2
+ version: 1
3
+ description: Playwright E2E verification for an OpenSpec change
4
+
5
+ artifacts:
6
+ - id: test-plan
7
+ generates: specs/playwright/test-plan.md
8
+ description: Test plan derived from change specs
9
+ template: test-plan.md
10
+ instruction: |
11
+ Generate a test plan from the change specs. Each functional requirement becomes
12
+ one or more test cases.
13
+
14
+ For each test case, specify:
15
+ - **Test name** (kebab-case): what it verifies
16
+ - **Route/Page**: which URL or UI component it targets
17
+ - **Prerequisites**: auth role needed (user/admin/guest/none)
18
+ - **Happy path**: main user flow to verify
19
+ - **Error paths**: key error conditions to verify
20
+
21
+ Mark tests with role tags: `@role(user)`, `@role(admin)`, `@role(guest)`, `@role(none)`.
22
+ Mark auth requirement: `@auth(required)` or `@auth(none)`.
23
+
24
+ - id: e2e-test
25
+ generates: tests/playwright/<change>.spec.ts
26
+ description: Playwright E2E test suite
27
+ template: e2e-test.ts
28
+ instruction: |
29
+ Generate Playwright tests from the test-plan.
30
+
31
+ Rules:
32
+ - Follow the page object pattern from seed.spec.ts
33
+ - Prefer `data-testid` selectors, fall back to semantic selectors
34
+ - Each test maps to one test case from the test-plan
35
+ - Use `@project(user)` / `@project(admin)` for role-specific tests
36
+ - Cover happy path AND key error paths
37
+ - Name tests descriptively: `test('shows error on invalid input')`
38
+
39
+ - id: playwright-config
40
+ generates: playwright.config.ts
41
+ description: Playwright config with webServer and auth projects
42
+ template: playwright.config.ts
43
+ instruction: |
44
+ Configure Playwright for E2E testing.
45
+
46
+ - Set webServer command and url from BASE_URL
47
+ - Set baseURL for all tests
48
+ - Add auth setup project if credentials are configured
49
+ - Preserve any existing config fields (browsers, retries, etc.)
50
+ - Do NOT overwrite existing projects unless they conflict
51
+
52
+ apply:
53
+ requires: [test-plan, e2e-test]
54
+ instruction: |
55
+ Run tests via: openspec-pw run <change-name>
56
+ Analyze results and generate report at openspec/reports/playwright-e2e-<change>-<timestamp>.md
@@ -0,0 +1,55 @@
1
+ import { test, expect, Page } from '@playwright/test';
2
+
3
+ // ──────────────────────────────────────────────
4
+ // Test plan: <change-name>
5
+ // Generated from: openspec/changes/<change-name>/specs/playwright/test-plan.md
6
+ // ──────────────────────────────────────────────
7
+
8
+ const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
9
+
10
+ /**
11
+ * Page Object Pattern - add selectors for your app's pages
12
+ */
13
+ class AppPage {
14
+ constructor(private page: Page) {}
15
+
16
+ async goto(path: string = '/') {
17
+ await this.page.goto(`${BASE_URL}${path}`);
18
+ }
19
+
20
+ async getByTestId(id: string) {
21
+ return this.page.locator(`[data-testid="${id}"]`);
22
+ }
23
+
24
+ async waitForToast(message?: string) {
25
+ if (message) {
26
+ await this.page.getByText(message, { state: 'visible' }).waitFor();
27
+ }
28
+ }
29
+ }
30
+
31
+ function createPage(page: Page): AppPage {
32
+ return new AppPage(page);
33
+ }
34
+
35
+ // ──────────────────────────────────────────────
36
+ // Tests - generated from test-plan.md
37
+ // Customize selectors and assertions to match your app
38
+ // ──────────────────────────────────────────────
39
+
40
+ test.describe('<change-name>: E2E verification', () => {
41
+
42
+ test.beforeEach(async ({ page }) => {
43
+ const app = createPage(page);
44
+ await app.goto('/');
45
+ });
46
+
47
+ // TODO: Add test cases from specs/playwright/test-plan.md
48
+ // Example:
49
+ // test('shows expected content on page load', async ({ page }) => {
50
+ // const app = createPage(page);
51
+ // await app.goto('/');
52
+ // await expect(app.getByTestId('main-heading')).toBeVisible();
53
+ // });
54
+
55
+ });
@@ -0,0 +1,52 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ // ─── BASE_URL: prefer env, then seed.spec.ts, then default ───
6
+ const seedSpec = join(__dirname, '../tests/playwright/seed.spec.ts');
7
+ let baseUrl = process.env.BASE_URL || 'http://localhost:3000';
8
+ if (existsSync(seedSpec)) {
9
+ const content = readFileSync(seedSpec, 'utf-8');
10
+ const m = content.match(/BASE_URL\s*=\s*process\.env\.BASE_URL\s*\|\|\s*['"]([^'"]+)['"]/);
11
+ if (m) baseUrl = m[1];
12
+ }
13
+
14
+ // ─── Dev command: detect from package.json scripts ───
15
+ const pkgPath = join(__dirname, '../package.json');
16
+ let devCmd = 'npm run dev';
17
+ if (existsSync(pkgPath)) {
18
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
19
+ const scripts = pkg.scripts ?? {};
20
+ // Prefer in order: dev, start, serve, preview
21
+ devCmd = scripts.dev ?? scripts.start ?? scripts.serve ?? scripts.preview ?? devCmd;
22
+ }
23
+
24
+ export default defineConfig({
25
+ testDir: '../tests/playwright',
26
+ // Keep test artifacts inside tests/playwright/ instead of project root
27
+ outputDir: '../tests/playwright/test-results',
28
+ fullyParallel: true,
29
+ forbidOnly: !!process.env.CI,
30
+ retries: process.env.CI ? 2 : 0,
31
+ workers: process.env.CI ? 1 : undefined,
32
+ reporter: 'list',
33
+
34
+ use: {
35
+ baseURL: baseUrl,
36
+ trace: 'on-first-retry',
37
+ },
38
+
39
+ // Dev server lifecycle - Playwright starts/stops automatically
40
+ webServer: {
41
+ command: devCmd,
42
+ url: baseUrl,
43
+ timeout: 120000,
44
+ reuseExistingServer: true,
45
+ },
46
+
47
+ // Setup project for authentication (configured by openspec-pw run)
48
+ projects: [
49
+ { name: 'setup', testMatch: /.*\.setup\.ts/ },
50
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
51
+ ],
52
+ });
@@ -0,0 +1,27 @@
1
+ # E2E Verify Report: <change-name>
2
+
3
+ **Change**: `<change-name>`
4
+ **Generated**: `<timestamp>`
5
+ **Auth**: `<required|none>`
6
+
7
+ ## Summary
8
+
9
+ | Check | Status |
10
+ |-------|--------|
11
+ | Tests Run | <N> |
12
+ | Passed | <X> |
13
+ | Failed | <Y> |
14
+ | Auto-heals | <Z> |
15
+ | Final Status | <PASS|FAIL> |
16
+
17
+ ## Test Results
18
+
19
+ <!-- List each test with pass/fail status -->
20
+
21
+ ## Auto-Heal Log
22
+
23
+ <!-- If heals were performed, document each attempt -->
24
+
25
+ ## Recommendations
26
+
27
+ <!-- For failed tests, specific file:line references and fixes -->
@@ -0,0 +1,24 @@
1
+ # Test Plan: <change-name>
2
+
3
+ Generated from: `openspec/changes/<change-name>/specs/`
4
+
5
+ ## Auth Requirements
6
+
7
+ <!-- Mark auth requirements based on specs analysis: -->
8
+ - Auth required: **yes / no**
9
+ - Roles needed: none / user / admin / user+admin
10
+
11
+ ## Test Cases
12
+
13
+ ### <test-name>
14
+
15
+ - **Route**: `/<page>`
16
+ - **Role**: `@role(<role>)`
17
+ - **Auth**: `@auth(required|none)`
18
+
19
+ **Happy path:**
20
+ - Step 1: ...
21
+ - Step 2: ...
22
+
23
+ **Error paths:**
24
+ - ...
@@ -0,0 +1,114 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import chalk from 'chalk';
5
+
6
+ export async function doctor() {
7
+ console.log(chalk.blue('\n🔍 OpenSpec + Playwright E2E Prerequisites Check\n'));
8
+
9
+ const projectRoot = process.cwd();
10
+ let allOk = true;
11
+
12
+ // Node.js
13
+ console.log(chalk.blue('─── Node.js ───'));
14
+ try {
15
+ const node = execSync('node --version', { encoding: 'utf-8' }).trim();
16
+ console.log(chalk.green(` ✓ Node.js ${node}`));
17
+ } catch {
18
+ console.log(chalk.red(' ✗ Node.js not found'));
19
+ allOk = false;
20
+ }
21
+
22
+ // npm
23
+ console.log(chalk.blue('\n─── npm ───'));
24
+ try {
25
+ const npm = execSync('npm --version', { encoding: 'utf-8' }).trim();
26
+ console.log(chalk.green(` ✓ npm ${npm}`));
27
+ } catch {
28
+ console.log(chalk.red(' ✗ npm not found'));
29
+ allOk = false;
30
+ }
31
+
32
+ // OpenSpec
33
+ console.log(chalk.blue('\n─── OpenSpec ───'));
34
+ const hasOpenSpec = existsSync(join(projectRoot, 'openspec'));
35
+ if (hasOpenSpec) {
36
+ console.log(chalk.green(' ✓ OpenSpec initialized'));
37
+ } else {
38
+ console.log(chalk.red(' ✗ OpenSpec not initialized'));
39
+ console.log(chalk.gray(' Run: openspec init'));
40
+ allOk = false;
41
+ }
42
+
43
+ // Playwright browsers
44
+ console.log(chalk.blue('\n─── Playwright Browsers ───'));
45
+ try {
46
+ const pw = execSync('npx playwright --version', { encoding: 'utf-8' }).trim();
47
+ console.log(chalk.green(` ✓ Playwright ${pw}`));
48
+ } catch {
49
+ console.log(chalk.red(' ✗ Playwright browsers not installed'));
50
+ console.log(chalk.gray(' Run: npx playwright install --with-deps'));
51
+ allOk = false;
52
+ }
53
+
54
+ // Playwright MCP (global)
55
+ console.log(chalk.blue('\n─── Playwright MCP ───'));
56
+ const homeDir = process.env.HOME ?? '';
57
+ const claudeJsonPath = join(homeDir, '.claude.json');
58
+ let mcpInstalled = false;
59
+ if (existsSync(claudeJsonPath)) {
60
+ try {
61
+ const claudeJson = JSON.parse(readFileSync(claudeJsonPath, 'utf-8'));
62
+ const globalMcp = claudeJson?.mcpServers ?? {};
63
+ const localMcp = claudeJson?.projects?.[projectRoot]?.mcpServers ?? {};
64
+ if (globalMcp['playwright'] || localMcp['playwright']) {
65
+ mcpInstalled = true;
66
+ }
67
+ } catch {
68
+ // ignore
69
+ }
70
+ }
71
+ if (mcpInstalled) {
72
+ console.log(chalk.green(' ✓ Playwright MCP installed globally'));
73
+ } else {
74
+ console.log(chalk.red(' ✗ Playwright MCP not configured'));
75
+ console.log(chalk.gray(' Run: openspec-pw init'));
76
+ allOk = false;
77
+ }
78
+
79
+ // Skill
80
+ console.log(chalk.blue('\n─── Claude Code Skill ───'));
81
+ const hasSkill = existsSync(
82
+ join(projectRoot, '.claude', 'skills', 'openspec-e2e', 'SKILL.md')
83
+ );
84
+ if (hasSkill) {
85
+ console.log(chalk.green(' ✓ /openspec-e2e skill installed'));
86
+ } else {
87
+ console.log(chalk.red(' ✗ Skill not installed'));
88
+ console.log(chalk.gray(' Run: openspec-pw init'));
89
+ allOk = false;
90
+ }
91
+
92
+ // Seed test
93
+ console.log(chalk.blue('\n─── Seed Test ───'));
94
+ const hasSeed = existsSync(
95
+ join(projectRoot, 'tests', 'playwright', 'seed.spec.ts')
96
+ );
97
+ if (hasSeed) {
98
+ console.log(chalk.green(' ✓ seed.spec.ts found'));
99
+ } else {
100
+ console.log(chalk.yellow(' ⚠ seed.spec.ts not found (optional)'));
101
+ console.log(chalk.gray(' Run: openspec-pw init'));
102
+ }
103
+
104
+ // Summary
105
+ console.log(chalk.blue('\n─── Summary ───'));
106
+ if (allOk) {
107
+ console.log(chalk.green(' ✅ All prerequisites met!\n'));
108
+ console.log(chalk.gray(' Run: /opsx:e2e <change-name> in Claude Code\n'));
109
+ } else {
110
+ console.log(chalk.red(' ❌ Some prerequisites are missing\n'));
111
+ console.log(chalk.gray(' Run: openspec-pw init to fix\n'));
112
+ process.exit(1);
113
+ }
114
+ }