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.
- package/.claude/commands/opsx/e2e.md +8 -0
- package/.claude/skills/openspec-e2e/SKILL.md +138 -0
- package/.github/workflows/release.yml +41 -0
- package/README.md +133 -0
- package/README.zh-CN.md +118 -0
- package/bin/openspec-pw +4 -0
- package/bin/openspec-pw.js +2 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +110 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +174 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/run.d.ts +5 -0
- package/dist/commands/run.js +135 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/update.d.ts +5 -0
- package/dist/commands/update.js +91 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/docs/plans/2026-03-26-openspec-playwright-design.md +180 -0
- package/package.json +39 -0
- package/schemas/playwright-e2e/schema.yaml +56 -0
- package/schemas/playwright-e2e/templates/e2e-test.ts +55 -0
- package/schemas/playwright-e2e/templates/playwright.config.ts +52 -0
- package/schemas/playwright-e2e/templates/report.md +27 -0
- package/schemas/playwright-e2e/templates/test-plan.md +24 -0
- package/src/commands/doctor.ts +114 -0
- package/src/commands/init.ts +209 -0
- package/src/commands/run.ts +172 -0
- package/src/commands/update.ts +130 -0
- package/src/index.ts +47 -0
- package/templates/auth.setup.ts +77 -0
- package/templates/credentials.yaml +33 -0
- package/templates/seed.spec.ts +63 -0
- 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
|
+
}
|