openspec-playwright 0.1.40 → 0.1.43

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.
@@ -3,11 +3,9 @@ name: openspec-e2e
3
3
  description: Run Playwright E2E verification for an OpenSpec change. Use when the user wants to validate that the implementation works end-to-end by running Playwright tests generated from the specs.
4
4
  license: MIT
5
5
  compatibility: Requires openspec CLI, Playwright (with browsers installed), and @playwright/mcp (globally installed via `claude mcp add playwright npx @playwright/mcp@latest`).
6
-
7
- **Architecture**: Uses CLI + SKILLs (not `init-agents`). This follows Playwright's recommended approach for coding agents — CLI is more token-efficient than loading MCP tool schemas into context. MCP is used only for Healer (UI inspection on failure).
8
6
  metadata:
9
7
  author: openspec-playwright
10
- version: "2.7"
8
+ version: "2.8"
11
9
  ---
12
10
 
13
11
  ## Input
@@ -25,13 +23,9 @@ metadata:
25
23
 
26
24
  ## Architecture
27
25
 
28
- This skill implements the Playwright Test Agents pipeline:
29
-
30
- - **🎭 Planner** (Step 4): Consumes OpenSpec specs (`specs/*.md`) and produces `test-plan.md` — combines OpenSpec's structured requirements with LLM编排.
31
- - **🎭 Generator** (Step 5): Transforms the Markdown test-plan into real Playwright `.spec.ts` files using LLM code generation.
32
- - **🎭 Healer** (Step 8): Executes the test suite and automatically repairs failing selectors via Playwright MCP tools.
26
+ Pipeline: **Planner** (Step 4) **Generator** (Step 5) → **Healer** (Step 8).
33
27
 
34
- Uses CLI + SKILLs (not `init-agents`). This follows Playwright's recommended approach for coding agents — CLI is more token-efficient than loading MCP tool schemas into context. MCP is used only for Healer (UI inspection on failure).
28
+ Uses CLI + SKILLs (not `init-agents`). CLI is ~4x more token-efficient than loading MCP tool schemas. MCP is used only for Healer (UI inspection on failure).
35
29
 
36
30
  **Schema owns templates. CLI handles execution. Skill handles cognitive work.**
37
31
 
@@ -56,7 +50,7 @@ If `openspec/changes/<name>/specs/` is empty, inform the user and stop. E2E requ
56
50
 
57
51
  Read all files from `openspec/changes/<name>/specs/*.md`. Extract functional requirements.
58
52
 
59
- Detect if auth is required. Mark as **auth required** only when BOTH conditions are met:
53
+ Detect if auth is required only when BOTH conditions are met:
60
54
 
61
55
  **Condition A — Explicit markers**: "login", "signin", "logout", "authenticate", "protected", "authenticated", "session", "unauthorized"
62
56
 
@@ -71,169 +65,101 @@ Detect if auth is required. Mark as **auth required** only when BOTH conditions
71
65
 
72
66
  ### 3. Validate environment
73
67
 
74
- Before generating tests, verify the environment is ready by running the seed test:
75
-
68
+ Run the seed test before generating tests:
76
69
  ```bash
77
70
  npx playwright test tests/playwright/seed.spec.ts --project=chromium
78
71
  ```
79
72
 
80
- **What this validates**:
81
- - App server is reachable (BASE_URL accessible)
82
- - Auth fixtures (`storageState`) are initialized if auth is required
83
- - Playwright browser and config are working
84
-
85
- **If seed test fails**: Stop and report the failure. User must fix the environment before proceeding.
73
+ This validates: app server reachable, auth fixtures initialized, Playwright working.
86
74
 
87
- **If seed test passes or no auth required**: Proceed to Step 4.
75
+ **If seed test fails**: Stop and report. Fix the environment before proceeding.
88
76
 
89
77
  ### 4. Generate test plan
90
78
 
91
- Create `openspec/changes/<name>/specs/playwright/test-plan.md` by:
92
- - Listing each functional requirement as a test case
93
- - Marking each with `@role(user|admin|guest|none)` and `@auth(required|none)`
94
- - Including happy path AND key error paths
95
- - Referencing the route/page each test targets
79
+ Create `openspec/changes/<name>/specs/playwright/test-plan.md`:
80
+ - List each functional requirement as a test case
81
+ - Mark with `@role(user|admin|guest|none)` and `@auth(required|none)`
82
+ - Include happy path AND error paths
83
+ - Reference the route/page each test targets
96
84
 
97
- **Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate unless user explicitly asks.
85
+ **Idempotency**: If test-plan.md already exists → read it, use it, do NOT regenerate.
98
86
 
99
- ### 5. Generate test file (LLM-driven)
87
+ ### 5. Generate test file
100
88
 
101
- Use your file writing capability to create `tests/playwright/<name>.spec.ts`.
89
+ Create `tests/playwright/<name>.spec.ts`:
102
90
 
103
91
  **Read inputs first**:
104
- - `openspec/changes/<name>/specs/playwright/test-plan.md` — test cases with `@role` and `@auth` tags
105
- - `tests/playwright/seed.spec.ts` — code pattern, BASE_URL, and page object structure
106
-
107
- **Generate Playwright code** for each test case from test-plan.md:
108
- - Follow the same structure as `seed.spec.ts`
109
- - Prefer `data-testid` selectors; fallback to `input[name="..."]` or semantic selectors
110
- - Include **happy path** AND **error/edge cases**
111
- - Use `test.describe(...)` for grouping related tests
92
+ - `openspec/changes/<name>/specs/playwright/test-plan.md`
93
+ - `tests/playwright/seed.spec.ts`
94
+
95
+ **Generate** Playwright code for each test case:
96
+ - Follow `seed.spec.ts` structure
97
+ - Prefer `data-testid`; fallback to `getByRole`, `getByLabel`, `getByText`
98
+ - Include happy path AND error/edge cases
99
+ - Use `test.describe(...)` for grouping
112
100
  - Each test: `test('描述性名称', async ({ page }) => { ... })`
113
- - Add `@project(user)` / `@project(admin)` on role-specific tests
114
101
 
115
- ### Anti-Pattern Warnings (Generator)
102
+ #### Anti-Pattern Warnings
116
103
 
117
- **🚫 NEVER do this False Pass pattern:**
104
+ **🚫 False Pass**never silently skip when element is missing:
118
105
  ```typescript
119
- // WRONG: If button doesn't exist, test silently passes and tests nothing
120
- const btn = page.getByRole('button', { name: '取消' }).first();
121
- if (await btn.isVisible().catch(() => false)) {
122
- await btn.click();
123
- await expect(page.getByText('成功')).toBeVisible();
124
- }
125
- // ✅ CORRECT: Use assertion — test fails if element is missing
106
+ // WRONG
107
+ if (await btn.isVisible().catch(() => false)) { ... }
108
+ // CORRECT
126
109
  await expect(page.getByRole('button', { name: '取消' })).toBeVisible();
127
- await page.getByRole('button', { name: '取消' }).click();
128
- await expect(page.getByText('成功')).toBeVisible();
129
110
  ```
130
111
 
131
- **Why it matters**: A test that passes but skipped its logic gives **false confidence**. It reports green but tests nothing. Worse if the test modifies data, a skipped run can corrupt state for the next test.
132
-
133
- **🚫 NEVER rely on Playwright projects for permission filtering:**
112
+ **🚫 Permission filtering** — use `@tag` with `--grep`, not projects:
134
113
  ```typescript
135
- // WRONG: All tests run under both admin AND user projects false "16 tests" impression
136
- projects: [{ name: 'admin' }, { name: 'user' }]
137
-
138
- // ✅ CORRECT: Use @tag for permission-based test filtering
139
- test('admin only - activate subscription', { tag: '@admin' }, async ({ page }) => { ... });
140
- test('user only - view subscription', { tag: '@user' }, async ({ page }) => { ... });
114
+ test('admin only', { tag: '@admin' }, async ({ page }) => { ... });
141
115
  // Run with: npx playwright test --grep "@admin"
142
116
  ```
143
117
 
144
- **🚫 NEVER skip auth guard tests:**
145
- The auth guard is a **critical security feature**. Skipping it leaves a gap in coverage.
118
+ **🚫 Auth guard** test with FRESH browser context (no inherited cookies):
146
119
  ```typescript
147
- // ✅ CORRECT: Test auth guard with a FRESH browser context (no cookies, no storage)
148
120
  test('redirects unauthenticated user to login', async ({ browser }) => {
149
- const freshContext = await browser.newContext(); // No session cookies
150
- const freshPage = await freshContext.newPage();
121
+ const freshPage = await browser.newContext().newPage();
151
122
  await freshPage.goto(`${BASE_URL}/dashboard`);
152
123
  await expect(freshPage).toHaveURL(/login|auth/);
153
- await freshContext.close();
154
- });
155
- ```
156
-
157
- **Always include error path tests** (not just happy paths):
158
- - API returns 500 → UI error message displayed?
159
- - API returns 404 → graceful "not found" handling?
160
- - Network timeout → retry or error UX?
161
- - Invalid input → validation message shown?
162
-
163
- **Example pattern** (from seed.spec.ts):
164
- ```typescript
165
- import { test, expect } from '@playwright/test';
166
-
167
- const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
168
-
169
- test.describe('Feature Name', () => {
170
- test('happy path - action succeeds', async ({ page }) => {
171
- await page.goto(`${BASE_URL}/path`);
172
- await page.getByTestId('element').click();
173
- await expect(page.getByTestId('result')).toBeVisible();
174
- });
175
-
176
- test('error case - invalid input shows message', async ({ page }) => {
177
- await page.goto(`${BASE_URL}/path`);
178
- await page.getByTestId('input').fill('invalid');
179
- await page.getByTestId('submit').click();
180
- await expect(page.getByTestId('error')).toContainText('Error text');
181
- });
182
124
  });
183
125
  ```
184
126
 
185
- **Write the file** using the Write tool. If the file already exists → diff against test-plan, add only missing test cases, preserve existing implementations.
127
+ **Always include error path tests** API 500, 404, network timeout, invalid input.
186
128
 
187
- **Selector guidance**: If no `data-testid` exists in the app, prefer `getByRole`, `getByLabel`, `getByText` over fragile selectors like CSS paths.
129
+ If the file exists diff against test-plan, add only missing test cases.
188
130
 
189
131
  ### 6. Configure auth (if required)
190
132
 
191
- If auth is required:
192
-
193
- **API login**: If specs mention `/api/auth/login` or similar → generate `tests/playwright/auth.setup.ts` using API login with `E2E_USERNAME`/`E2E_PASSWORD`.
133
+ - **API login**: Generate `auth.setup.ts` using `E2E_USERNAME`/`E2E_PASSWORD` + POST to login endpoint
134
+ - **UI login**: Generate `auth.setup.ts` using browser form fill. Update selectors to match your login page
135
+ - **Multi-user**: Separate `storageState` paths per role
194
136
 
195
- **UI login**: Otherwise → generate `tests/playwright/auth.setup.ts` using UI login. Update selectors to match the login page (look for `data-testid` attributes or fallback to `input[name="..."]`).
196
-
197
- **Multi-user**: If specs mention multiple roles (admin + user) → generate separate `auth.setup.ts` blocks for each role with different `storageState` paths.
198
-
199
- **Prompt user** with:
137
+ **Prompt user**:
200
138
  ```
201
- Auth required. To set up credentials:
202
-
203
- 1. Customize tests/playwright/credentials.yaml with your test user
139
+ Auth required. To set up:
140
+ 1. Customize tests/playwright/credentials.yaml
204
141
  2. Export: export E2E_USERNAME=xxx E2E_PASSWORD=yyy
205
142
  3. Run auth: npx playwright test --project=setup
206
- 4. Then re-run /opsx:e2e to execute tests
143
+ 4. Re-run /opsx:e2e to execute tests
207
144
  ```
208
145
 
209
146
  **Idempotency**: If `auth.setup.ts` already exists → verify format, update only if stale.
210
147
 
211
- ### 7. Configure playwright.config.ts (non-destructive)
148
+ ### 7. Configure playwright.config.ts
212
149
 
213
- If `playwright.config.ts` does not exist → generate it from the schema template at `openspec/schemas/playwright-e2e/templates/playwright.config.ts`. The template auto-detects:
214
- - **BASE_URL**: from `process.env.BASE_URL`, falling back to `tests/playwright/seed.spec.ts` → `BASE_URL` value, then `http://localhost:3000`
215
- - **Dev command**: from `package.json` → scripts in order: `dev` → `start` → `serve` → `preview` → `npm run dev`
150
+ If missing → generate from `openspec/schemas/playwright-e2e/templates/playwright.config.ts`. Auto-detects BASE_URL and dev command from `package.json`.
216
151
 
217
- If `playwright.config.ts` exists → READ it first. Extract existing `webServer`, `use.baseURL`, and `projects`. Preserve ALL existing fields. Add `webServer` block if missing. Do NOT replace existing config values.
152
+ If exists → READ first, preserve ALL existing fields, add only missing `webServer` block.
218
153
 
219
- ### 8. Execute tests via CLI
154
+ ### 8. Execute tests
220
155
 
221
156
  ```bash
222
157
  openspec-pw run <name> --project=<role>
223
158
  ```
224
159
 
225
- **For role-based tests using `@tag`** (recommended over `--project` filtering):
226
- ```bash
227
- npx playwright test tests/playwright/<name>.spec.ts --grep "@<role>"
228
- ```
229
- The `--project` approach runs ALL tests under each project's credentials — use `@tag` with `--grep` for precise filtering.
230
-
231
- The CLI handles:
232
- - Server lifecycle (start → wait for HTTP → test → stop)
233
- - Port mismatch detection
234
- - Report generation at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`
160
+ The CLI handles: server lifecycle, port mismatch, report generation.
235
161
 
236
- If tests fail → analyze failures, use **Playwright MCP tools** to inspect UI state, fix selectors in the test file, and re-run.
162
+ If tests fail → use Playwright MCP tools to inspect UI, fix selectors, re-run.
237
163
 
238
164
  **Healer MCP tools** (in order of use):
239
165
  <!-- MCP_VERSION: 0.0.68 -->
@@ -247,77 +173,41 @@ If tests fail → analyze failures, use **Playwright MCP tools** to inspect UI s
247
173
  | `browser_run_code` | Execute custom fix logic (optional) |
248
174
 
249
175
  **Healer workflow**:
250
- 1. Read the failing test → identify failure type by checking error message and console output
251
- 2. Classify the failure:
176
+ 1. Read the failing test → identify failure type
177
+ 2. Classify:
252
178
 
253
179
  | Failure type | Signal | Action |
254
180
  |-------------|--------|--------|
255
- | **Network/backend** | `fetch failed`, `net::ERR`, 5xx, timeout | `browser_console_messages` → add `test.skip()` + report "backend error" |
256
- | **Selector changed** | Element not found, page loaded | `browser_snapshot` → fix selector → re-run |
257
- | **Assertion mismatch** | Element exists, wrong content/value | `browser_snapshot` → compare → fix assertion → re-run |
258
- | **Timing issue** | `waitFor` timeout, race condition | Adjust wait strategy → re-run |
259
-
260
- 3. **Attempt heal** (up to 3 times):
261
- - Apply fix using `browser_snapshot` (prefer `getByRole`, `getByLabel`, `getByText`)
262
- - Re-run: `openspec-pw run <name> --project=<role>`
263
-
264
- 4. **After 3 failed attempts**, collect evidence:
181
+ | **Network/backend** | `fetch failed`, `net::ERR`, 5xx | `browser_console_messages` → `test.skip()` |
182
+ | **Selector changed** | Element not found | `browser_snapshot` → fix selector → re-run |
183
+ | **Assertion mismatch** | Wrong content/value | `browser_snapshot` → fix assertion → re-run |
184
+ | **Timing issue** | `waitFor` timeout | Adjust wait strategy → re-run |
265
185
 
266
- **Evidence checklist** (in order, stop at first match):
267
- | Check | Signal | Decision |
268
- |-------|--------|----------|
269
- | `browser_console_messages` | ERROR-level messages present | App bug → `test.skip()` + report "console error" |
270
- | `browser_snapshot` | Target element missing from DOM | App bug → `test.skip()` + report "element missing" |
271
- | `browser_snapshot` | Element exists, no errors | Test bug → report recommendation |
272
-
273
- - **App bug**: `test.skip('app bug — reason: <signal>')` + detailed report entry
274
- - **Test bug**: report with "likely selector change, verify manually at file:line"
275
- - Do NOT retry after evidence checklist — evidence is conclusive
186
+ 3. **Attempt heal** (≤3 times): snapshot fix → re-run
187
+ 4. **After 3 failures**: collect evidence checklist → `test.skip()` if app bug, report recommendation if test bug
276
188
 
277
189
  ### 9. False Pass Detection
278
190
 
279
- Run **after** the test suite completes (even if all tests pass). Scan for silent skips that give false confidence:
280
-
281
- **Indicator A — Conditional test logic:**
282
- Look for patterns in the test file:
283
- ```typescript
284
- if (await locator.isVisible().catch(() => false)) { ... }
285
- ```
286
- → If test passes, the locator might not exist → check with `browser_snapshot`
287
- → Report: "Test passed but may have skipped — conditional visibility check detected"
191
+ Run after test suite completes (even if all pass):
288
192
 
289
- **Indicator BTest ran too fast:**
290
- A test covering a complex flow that completes in < 200ms is suspicious.
291
- Inspect with `browser_snapshot` to confirm page state
292
- → Report: "Test duration suspiciously short — verify test logic was executed"
193
+ - **Conditional logic**: Look for `if (locator.isVisible().catch(() => false))` if test passes, locator may not exist
194
+ - **Too fast**: < 200ms for a complex flow is suspicious
195
+ - **No fresh auth context**: Protected routes without `browser.newContext()`
293
196
 
294
- **Indicator C Auth guard not tested:**
295
- If specs mention "protected route" or "redirect to login" but no test uses a fresh browser context:
296
- → Report: "Auth guard not verified — test uses authenticated context (cookies/storage inherited)"
297
- → Recommendation: Add a test with `browser.newContext()` (no storageState) to verify the guard
298
-
299
- If any false-pass indicator is found → add a **⚠️ Coverage Gap** section to the report.
197
+ Report any gaps in a **⚠️ Coverage Gap** section.
300
198
 
301
199
  ### 10. Report results
302
200
 
303
- Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
304
-
305
- **Present results to user**:
306
- - Summary table (tests run, passed, failed, duration, final status)
307
- - Any auto-heal notes
308
- - Recommendations for failed tests (with specific `file:line` references)
201
+ Read report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`. Present:
202
+ - Summary table (tests, passed, failed, duration, status)
203
+ - Auto-heal notes
204
+ - Recommendations with `file:line` references
309
205
 
310
- **Update tasks.md** (if all tests pass):
311
- - Read `openspec/changes/<name>/tasks.md`
312
- - Find tasks related to E2E testing (look for `[ ]` items mentioning "test", "e2e", "playwright", "verify")
313
- - Append a verification note: e.g. `✅ Verified via Playwright E2E (<timestamp>)`
314
- - Write the updated content back using the Edit tool
206
+ **Update tasks.md** if all tests pass: find E2E-related items, append `✅ Verified via Playwright E2E (<timestamp>)`.
315
207
 
316
- ## Output Format
208
+ ## Report Structure
317
209
 
318
- ### Report Structure (`openspec/reports/playwright-e2e-<name>-<timestamp>.md`)
319
-
320
- ```
210
+ ```markdown
321
211
  # Playwright E2E Report — <name>
322
212
 
323
213
  ## Summary
@@ -326,7 +216,6 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
326
216
  | N | N | N | Xm Xs | ✅/❌ |
327
217
 
328
218
  ## Results
329
-
330
219
  ### Passed
331
220
  | Test | Duration | Notes |
332
221
  |------|----------|-------|
@@ -345,49 +234,35 @@ Read the report at `openspec/reports/playwright-e2e-<name>-<timestamp>.md`.
345
234
  - [ ] Requirement 2 (unverified)
346
235
 
347
236
  ## ⚠️ Coverage Gaps
348
- > Tests passed but coverage gaps were detected. Review carefully.
237
+ > Tests passed but coverage gaps detected.
349
238
 
350
239
  | Test | Gap | Recommendation |
351
240
  |------|-----|----------------|
352
- | ... | Conditional visibility check — test may have skipped | file:line — use `expect().toBeVisible()` |
353
- | ... | Auth guard uses inherited session | Add fresh context test: `browser.newContext()` |
354
- | ... | Suspiciously fast execution (<200ms) | Verify test logic was actually executed |
355
-
356
- ### Updated tasks.md
357
- ```
358
- - [x] Implement feature X ✅ Verified via Playwright E2E (2026-03-28)
241
+ | ... | Conditional visibility check | file:line — use `expect().toBeVisible()` |
242
+ | ... | Auth guard uses inherited session | Add fresh context test |
243
+ | ... | Suspiciously fast execution (<200ms) | Verify test logic executed |
359
244
  ```
360
245
 
361
246
  ## Graceful Degradation
362
247
 
363
248
  | Scenario | Behavior |
364
249
  |----------|----------|
365
- | No specs found | Stop with info message — E2E requires specs |
366
- | Seed test fails | Stop with failure — fix environment before proceeding |
367
- | No auth required | Skip auth setup entirely |
368
- | test-plan.md exists | Read and use it — never regenerate |
369
- | auth.setup.ts exists | Verify format update only if stale |
370
- | playwright.config.ts exists | Read and preserve all fields add only missing |
371
- | Test fails (network/backend) | Skip with `test.skip()` backend/app error, not test fragility |
372
- | Test fails (selector) | Healer: snapshot → fix selector → re-run (≤3 attempts) |
373
- | Test fails (assertion) | Healer: snapshotfix assertion re-run (≤3 attempts) |
374
- | 3 heal attempts failed | Confirm root cause if app bug: `test.skip()` + report; if unclear: report with recommendation |
375
- | False pass detected | Report coverage gap → add to "⚠️ Coverage Gap" section in report |
376
-
377
- ## Verification Heuristics
378
-
379
- - **Coverage**: Every functional requirement → at least one test
380
- - **Selector robustness**: Prefer `data-testid`, fallback to semantic selectors
381
- - **False positives**: If test fails due to test bug (not app bug) → fix the test
382
- - **Actionability**: Every failed test needs a specific recommendation
383
- - **No false passes**: Every passing test must actually execute its test logic — verify absence of `if (isVisible())` conditional patterns
384
- - **Auth guard verified**: Protected routes must have a test using a fresh browser context (no inherited cookies)
250
+ | No specs | Stop — E2E requires specs |
251
+ | Seed test fails | Stop — fix environment |
252
+ | No auth required | Skip auth setup |
253
+ | test-plan.md exists | Read and use (never regenerate) |
254
+ | auth.setup.ts exists | Verify format (update only if stale) |
255
+ | playwright.config.ts exists | Preserve all fields (add only missing) |
256
+ | Test fails (backend) | `test.skip()` + report |
257
+ | Test fails (selector/assertion) | Healer: snapshot → fix → re-run (≤3) |
258
+ | 3 heals failed | Evidence checklistapp bug: `test.skip()`; unclear: report |
259
+ | False pass detected | Add "⚠️ Coverage Gap" to report |
385
260
 
386
261
  ## Guardrails
387
262
 
388
263
  - Read specs from `openspec/changes/<name>/specs/` as source of truth
389
264
  - Do NOT generate tests that contradict the specs
390
- - **DO generate real, runnable Playwright test code** — not placeholders or TODO comments. Every test case in test-plan.md must produce an actual `test(...)` block.
391
- - Do NOT overwrite files outside: `specs/playwright/`, `tests/playwright/`, `openspec/reports/`, `playwright.config.ts`, `tests/auth.setup.ts`
265
+ - **DO generate real, runnable Playwright test code** — not placeholders or TODOs
266
+ - Do NOT overwrite files outside: `specs/playwright/`, `tests/playwright/`, `openspec/reports/`, `playwright.config.ts`
392
267
  - Cap auto-heal at 3 attempts
393
268
  - If no change specified → always ask user to select
package/README.md CHANGED
@@ -20,17 +20,24 @@ openspec-pw init # Install Playwright E2E integration
20
20
 
21
21
  ## Supported AI Coding Assistants
22
22
 
23
- Auto-detects and installs commands for these editors:
24
-
25
- | Editor | Command | Format |
26
- |--------|---------|--------|
27
- | Claude Code | `/opsx:e2e` | Skill + command + MCP |
28
- | Cursor | `/opsx-e2e` | Command |
29
- | Windsurf | `/opsx-e2e` | Workflow |
30
- | Cline | `/opsx-e2e` | Workflow |
31
- | Continue | `/opsx-e2e` | Prompt |
32
-
33
- `openspec-pw init` detects which editors you have in your project and installs the appropriate files. Claude Code gets the full experience (skill + command + Playwright MCP). Other editors get command/workflow files with the complete E2E workflow.
23
+ Auto-detects and installs commands for all 24 editors OpenSpec supports:
24
+
25
+ | Editor | Path | Editor | Path |
26
+ |--------|------|--------|------|
27
+ | Claude Code | `.claude/` | Gemini CLI | `.gemini/` |
28
+ | Cursor | `.cursor/` | GitHub Copilot | `.github/` |
29
+ | Windsurf | `.windsurf/` | Kiro | `.kiro/` |
30
+ | Cline | `.clinerules/` | Kilo Code | `.kilocode/` |
31
+ | Continue | `.continue/` | iFlow | `.iflow/` |
32
+ | Amazon Q | `.amazonq/` | CoStrict | `.cospec/` |
33
+ | Antigravity | `.agent/` | OpenCode | `.opencode/` |
34
+ | Auggie | `.augment/` | Factory | `.factory/` |
35
+ | CodeBuddy | `.codebuddy/` | Pi | `.pi/` |
36
+ | Codex | `~/.codex/` (global) | Qoder | `.qoder/` |
37
+ | Qwen Code | `.qwen/` | RooCode | `.roo/` |
38
+ | Crush | `.crush/` | | |
39
+
40
+ `openspec-pw init` auto-detects editors in your project and installs the right command files. Claude Code gets the full experience (skill + command + Playwright MCP). Other editors get command/workflow files with the complete E2E workflow.
34
41
 
35
42
  ## Usage
36
43
 
@@ -76,12 +83,12 @@ openspec-pw doctor # Check prerequisites
76
83
 
77
84
  1. **Node.js >= 20**
78
85
  2. **OpenSpec** initialized: `npm install -g @fission-ai/openspec && openspec init`
79
- 3. **One of**: Claude Code, Cursor, Windsurf, Cline, or Continue (auto-detected)
86
+ 3. **One of 24 editors**: Claude Code, Cursor, Windsurf, Cline, Continue, Amazon Q, Gemini CLI, GitHub Copilot, Kiro, Kilo Code, iFlow, CoStrict, OpenCode, Auggie, Factory, CodeBuddy, Codex, Pi, Qoder, Qwen Code, RooCode, Crush, Antigravity (auto-detected)
80
87
  4. **Claude Code only**: Playwright MCP — `claude mcp add playwright npx @playwright/mcp@latest`
81
88
 
82
89
  ## What `openspec-pw init` Does
83
90
 
84
- 1. Detects installed AI coding assistants (Claude Code, Cursor, Windsurf, Cline, Continue)
91
+ 1. Detects installed AI coding assistants (all 24 supported editors)
85
92
  2. Installs E2E command/workflow files for each detected editor
86
93
  3. Installs `/openspec-e2e` skill for Claude Code
87
94
  4. Installs Playwright MCP globally for Claude Code (via `claude mcp add`)
package/README.zh-CN.md CHANGED
@@ -20,15 +20,22 @@ openspec-pw init # 安装 Playwright E2E 集成
20
20
 
21
21
  ## 支持的 AI 编码助手
22
22
 
23
- 自动检测并安装以下编辑器的命令文件:
24
-
25
- | 编辑器 | 命令 | 格式 |
26
- |--------|------|------|
27
- | Claude Code | `/opsx:e2e` | Skill + 命令 + MCP |
28
- | Cursor | `/opsx-e2e` | 命令 |
29
- | Windsurf | `/opsx-e2e` | 工作流 |
30
- | Cline | `/opsx-e2e` | 工作流 |
31
- | Continue | `/opsx-e2e` | Prompt |
23
+ 自动检测并安装 OpenSpec 支持的全部 24 个编辑器的命令文件:
24
+
25
+ | 编辑器 | 路径 | 编辑器 | 路径 |
26
+ |--------|------|--------|------|
27
+ | Claude Code | `.claude/` | Gemini CLI | `.gemini/` |
28
+ | Cursor | `.cursor/` | GitHub Copilot | `.github/` |
29
+ | Windsurf | `.windsurf/` | Kiro | `.kiro/` |
30
+ | Cline | `.clinerules/` | Kilo Code | `.kilocode/` |
31
+ | Continue | `.continue/` | iFlow | `.iflow/` |
32
+ | Amazon Q | `.amazonq/` | CoStrict | `.cospec/` |
33
+ | Antigravity | `.agent/` | OpenCode | `.opencode/` |
34
+ | Auggie | `.augment/` | Factory | `.factory/` |
35
+ | CodeBuddy | `.codebuddy/` | Pi | `.pi/` |
36
+ | Codex | `~/.codex/` (全局) | Qoder | `.qoder/` |
37
+ | Qwen Code | `.qwen/` | RooCode | `.roo/` |
38
+ | Crush | `.crush/` | | |
32
39
 
33
40
  `openspec-pw init` 会检测项目中安装了哪些编辑器并安装对应文件。Claude Code 获得完整体验(skill + 命令 + Playwright MCP)。其他编辑器获得包含完整 E2E 工作流的命令/工作流文件。
34
41
 
@@ -20,6 +20,7 @@ export interface EditorAdapter {
20
20
  }
21
21
  /** Claude Code: .claude/commands/opsx/<id>.md + SKILL.md */
22
22
  declare const claudeAdapter: EditorAdapter;
23
+ declare const ALL_ADAPTERS: EditorAdapter[];
23
24
  /** Detect which editors are installed by checking their config directories */
24
25
  export declare function detectEditors(projectRoot: string): EditorAdapter[];
25
26
  /** Detect Codex by checking if CODEX_HOME or ~/.codex exists */
@@ -30,4 +31,4 @@ export declare function buildCommandMeta(body: string): CommandMeta;
30
31
  export declare function installForAllEditors(body: string, adapters: EditorAdapter[], projectRoot: string): void;
31
32
  /** Install SKILL.md only for Claude Code */
32
33
  export declare function installSkill(projectRoot: string, skillContent: string): void;
33
- export { claudeAdapter };
34
+ export { claudeAdapter, ALL_ADAPTERS };
@@ -117,7 +117,7 @@ const amazonqAdapter = {
117
117
  getCommandPath(id) { return join('.amazonq', 'prompts', `opsx-${id}.md`); },
118
118
  formatCommand(meta) {
119
119
  return `---
120
- description: ${escapeYamlValue(meta.description)}
120
+ description: ${meta.description}
121
121
  ---
122
122
 
123
123
  ${meta.body}
@@ -132,7 +132,7 @@ const antigravityAdapter = {
132
132
  getCommandPath(id) { return join('.agent', 'workflows', `opsx-${id}.md`); },
133
133
  formatCommand(meta) {
134
134
  return `---
135
- description: ${escapeYamlValue(meta.description)}
135
+ description: ${meta.description}
136
136
  ---
137
137
 
138
138
  ${meta.body}
@@ -147,7 +147,7 @@ const auggieAdapter = {
147
147
  getCommandPath(id) { return join('.augment', 'commands', `opsx-${id}.md`); },
148
148
  formatCommand(meta) {
149
149
  return `---
150
- description: ${escapeYamlValue(meta.description)}
150
+ description: ${meta.description}
151
151
  argument-hint: command arguments
152
152
  ---
153
153
 
@@ -163,7 +163,7 @@ const codebuddyAdapter = {
163
163
  getCommandPath(id) { return join('.codebuddy', 'commands', 'opsx', `${id}.md`); },
164
164
  formatCommand(meta) {
165
165
  return `---
166
- name: ${escapeYamlValue(meta.name)}
166
+ name: ${meta.name}
167
167
  description: "${meta.description}"
168
168
  argument-hint: "[command arguments]"
169
169
  ---
@@ -183,7 +183,7 @@ const codexAdapter = {
183
183
  },
184
184
  formatCommand(meta) {
185
185
  return `---
186
- description: ${escapeYamlValue(meta.description)}
186
+ description: ${meta.description}
187
187
  argument-hint: command arguments
188
188
  ---
189
189
 
@@ -208,16 +208,16 @@ ${meta.body}
208
208
  },
209
209
  };
210
210
  // ─── crush ────────────────────────────────────────────────────────────────
211
- /** Crush: .crush/commands/opsx/<id>.md — tags plain join */
211
+ /** Crush: .crush/commands/opsx/<id>.md — raw values, no escaping */
212
212
  const crushAdapter = {
213
213
  toolId: 'crush',
214
214
  hasSkill: false,
215
215
  getCommandPath(id) { return join('.crush', 'commands', 'opsx', `${id}.md`); },
216
216
  formatCommand(meta) {
217
217
  return `---
218
- name: ${escapeYamlValue(meta.name)}
219
- description: ${escapeYamlValue(meta.description)}
220
- category: ${escapeYamlValue(meta.category)}
218
+ name: ${meta.name}
219
+ description: ${meta.description}
220
+ category: ${meta.category}
221
221
  tags: ${formatTagsPlain(meta.tags)}
222
222
  ---
223
223
 
@@ -233,7 +233,7 @@ const factoryAdapter = {
233
233
  getCommandPath(id) { return join('.factory', 'commands', `opsx-${id}.md`); },
234
234
  formatCommand(meta) {
235
235
  return `---
236
- description: ${escapeYamlValue(meta.description)}
236
+ description: ${meta.description}
237
237
  argument-hint: command arguments
238
238
  ---
239
239
 
@@ -264,7 +264,7 @@ const githubcopilotAdapter = {
264
264
  getCommandPath(id) { return join('.github', 'prompts', `opsx-${id}.prompt.md`); },
265
265
  formatCommand(meta) {
266
266
  return `---
267
- description: ${escapeYamlValue(meta.description)}
267
+ description: ${meta.description}
268
268
  ---
269
269
 
270
270
  ${meta.body}
@@ -281,8 +281,8 @@ const iflowAdapter = {
281
281
  return `---
282
282
  name: /opsx-${meta.id}
283
283
  id: opsx-${meta.id}
284
- category: ${escapeYamlValue(meta.category)}
285
- description: ${escapeYamlValue(meta.description)}
284
+ category: ${meta.category}
285
+ description: ${meta.description}
286
286
  ---
287
287
 
288
288
  ${meta.body}
@@ -308,7 +308,7 @@ const kiroAdapter = {
308
308
  getCommandPath(id) { return join('.kiro', 'prompts', `opsx-${id}.prompt.md`); },
309
309
  formatCommand(meta) {
310
310
  return `---
311
- description: ${escapeYamlValue(meta.description)}
311
+ description: ${meta.description}
312
312
  ---
313
313
 
314
314
  ${meta.body}
@@ -324,7 +324,7 @@ const opencodeAdapter = {
324
324
  formatCommand(meta) {
325
325
  const transformed = transformToHyphenCommands(meta.body);
326
326
  return `---
327
- description: ${escapeYamlValue(meta.description)}
327
+ description: ${meta.description}
328
328
  ---
329
329
 
330
330
  ${transformed}
@@ -347,16 +347,16 @@ ${meta.body}
347
347
  },
348
348
  };
349
349
  // ─── qoder ────────────────────────────────────────────────────────────────
350
- /** Qoder: .qoder/commands/opsx/<id>.md — tags plain join */
350
+ /** Qoder: .qoder/commands/opsx/<id>.md — raw values, no escaping */
351
351
  const qoderAdapter = {
352
352
  toolId: 'qoder',
353
353
  hasSkill: false,
354
354
  getCommandPath(id) { return join('.qoder', 'commands', 'opsx', `${id}.md`); },
355
355
  formatCommand(meta) {
356
356
  return `---
357
- name: ${escapeYamlValue(meta.name)}
358
- description: ${escapeYamlValue(meta.description)}
359
- category: ${escapeYamlValue(meta.category)}
357
+ name: ${meta.name}
358
+ description: ${meta.description}
359
+ category: ${meta.category}
360
360
  tags: ${formatTagsPlain(meta.tags)}
361
361
  ---
362
362
 
@@ -486,5 +486,5 @@ export function installSkill(projectRoot, skillContent) {
486
486
  writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
487
487
  console.log(chalk.green(` ✓ claude: .claude/skills/openspec-e2e/SKILL.md`));
488
488
  }
489
- export { claudeAdapter };
489
+ export { claudeAdapter, ALL_ADAPTERS };
490
490
  //# sourceMappingURL=editors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"editors.js","sourceRoot":"","sources":["../../src/commands/editors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,YAAY,GAAG,kCAAkC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACxF,OAAO,IAAI,OAAO,GAAG,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC7D,CAAC;AAED,4DAA4D;AAC5D,SAAS,eAAe,CAAC,IAAc;IACrC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAChC,CAAC;AAED,8CAA8C;AAC9C,SAAS,yBAAyB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAoBD,iFAAiF;AAEjF,4DAA4D;AAC5D,MAAM,aAAa,GAAkB;IACnC,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,IAAI;IACd,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,aAAa,CAAC,IAAI;QAChB,OAAO;QACH,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;eACnB,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;YACpC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;QAClC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;;EAGhC,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,+EAA+E;AAE/E,4CAA4C;AAC5C,MAAM,aAAa,GAAkB;IACnC,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,aAAa,CAAC,IAAI;QAChB,OAAO;cACG,IAAI,CAAC,EAAE;WACV,IAAI,CAAC,EAAE;YACN,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;eAC3B,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,gFAAgF;AAEhF,iDAAiD;AACjD,MAAM,eAAe,GAAkB;IACrC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,aAAa,CAAC,IAAI;QAChB,OAAO;QACH,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;eACnB,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;YACpC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;QAClC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;;EAGhC,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,+EAA+E;AAE/E,uEAAuE;AACvE,MAAM,YAAY,GAAkB;IAClC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAChF,aAAa,CAAC,IAAI;QAChB,OAAO,KAAK,IAAI,CAAC,IAAI;;EAEvB,IAAI,CAAC,WAAW;;EAEhB,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,gFAAgF;AAEhF,mDAAmD;AACnD,MAAM,eAAe,GAAkB;IACrC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAChF,aAAa,CAAC,IAAI;QAChB,OAAO;aACE,IAAI,CAAC,EAAE;eACL,IAAI,CAAC,WAAW;;;;EAI7B,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,8CAA8C;AAC9C,MAAM,cAAc,GAAkB;IACpC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,iDAAiD;AACjD,MAAM,kBAAkB,GAAkB;IACxC,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,8EAA8E;AAE9E,6CAA6C;AAC7C,MAAM,aAAa,GAAkB;IACnC,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5E,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;;EAI9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,8EAA8E;AAE9E,kDAAkD;AAClD,MAAM,gBAAgB,GAAkB;IACtC,MAAM,EAAE,WAAW;IACnB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACjF,aAAa,CAAC,IAAI;QAChB,OAAO;QACH,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;gBAClB,IAAI,CAAC,WAAW;;;;EAI9B,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,8DAA8D;AAC9D,MAAM,YAAY,GAAkB;IAClC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE;QACf,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;IACD,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;;EAI9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,uDAAuD;AACvD,MAAM,eAAe,GAAkB;IACrC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACvF,aAAa,CAAC,IAAI;QAChB,OAAO;gBACK,IAAI,CAAC,WAAW;;;;EAI9B,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,4DAA4D;AAC5D,MAAM,YAAY,GAAkB;IAClC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7E,aAAa,CAAC,IAAI;QAChB,OAAO;QACH,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;eACnB,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;YACpC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;QAClC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;;EAGhC,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,8EAA8E;AAE9E,oDAAoD;AACpD,MAAM,cAAc,GAAkB;IACpC,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5E,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;;EAI9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,8EAA8E;AAE9E,kDAAkD;AAClD,MAAM,aAAa,GAAkB;IACnC,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAChF,aAAa,CAAC,IAAI;QAChB,OAAO,kBAAkB,IAAI,CAAC,WAAW;;;EAG3C,IAAI,CAAC,IAAI;;CAEV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,8EAA8E;AAE9E,0DAA0D;AAC1D,MAAM,oBAAoB,GAAkB;IAC1C,MAAM,EAAE,gBAAgB;IACxB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IACjF,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,0CAA0C;AAC1C,MAAM,YAAY,GAAkB;IAClC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1E,aAAa,CAAC,IAAI;QAChB,OAAO;cACG,IAAI,CAAC,EAAE;WACV,IAAI,CAAC,EAAE;YACN,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;eAC3B,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,4EAA4E;AAE5E,8DAA8D;AAC9D,MAAM,eAAe,GAAkB;IACrC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9E,aAAa,CAAC,IAAI;QAChB,OAAO,GAAG,IAAI,CAAC,IAAI;CACtB,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,8CAA8C;AAC9C,MAAM,WAAW,GAAkB;IACjC,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/E,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,8EAA8E;AAC9E,MAAM,eAAe,GAAkB;IACrC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7E,aAAa,CAAC,IAAI;QAChB,MAAM,WAAW,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzD,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,WAAW;CACZ,CAAC;IACA,CAAC;CACF,CAAC;AAEF,4EAA4E;AAE5E,mCAAmC;AACnC,MAAM,SAAS,GAAkB;IAC/B,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACtE,aAAa,CAAC,IAAI;QAChB,OAAO;eACI,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;EAG9C,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,6EAA6E;AAE7E,4DAA4D;AAC5D,MAAM,YAAY,GAAkB;IAClC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7E,aAAa,CAAC,IAAI;QAChB,OAAO;QACH,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;eACnB,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;YACpC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC;QAClC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;;EAGhC,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,4EAA4E;AAE5E,+CAA+C;AAC/C,MAAM,WAAW,GAAkB;IACjC,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3E,aAAa,CAAC,IAAI;QAChB,OAAO,kBAAkB,IAAI,CAAC,WAAW;;;EAG3C,IAAI,CAAC,IAAI;;CAEV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,4EAA4E;AAE5E,4DAA4D;AAC5D,MAAM,cAAc,GAAkB;IACpC,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,KAAK;IACf,cAAc,CAAC,EAAE,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACxE,aAAa,CAAC,IAAI;QAChB,OAAO,KAAK,IAAI,CAAC,IAAI;;EAEvB,IAAI,CAAC,WAAW;;EAEhB,IAAI,CAAC,IAAI;CACV,CAAC;IACA,CAAC;CACF,CAAC;AAEF,4EAA4E;AAE5E,MAAM,YAAY,GAAoB;IACpC,aAAa;IACb,aAAa;IACb,eAAe;IACf,YAAY;IACZ,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,YAAY;IACZ,eAAe;IACf,YAAY;IACZ,cAAc;IACd,aAAa;IACb,oBAAoB;IACpB,YAAY;IACZ,eAAe;IACf,WAAW;IACX,eAAe;IACf,SAAS;IACT,YAAY;IACZ,WAAW;IACX,cAAc;CACf,CAAC;AAEF,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,MAAM,MAAM,GAAmC;QAC7C,CAAC,SAAS,EAAE,aAAa,CAAC;QAC1B,CAAC,SAAS,EAAE,aAAa,CAAC;QAC1B,CAAC,WAAW,EAAE,eAAe,CAAC;QAC9B,CAAC,aAAa,EAAE,YAAY,CAAC;QAC7B,CAAC,WAAW,EAAE,eAAe,CAAC;QAC9B,CAAC,UAAU,EAAE,cAAc,CAAC;QAC5B,CAAC,QAAQ,EAAE,kBAAkB,CAAC;QAC9B,CAAC,UAAU,EAAE,aAAa,CAAC;QAC3B,CAAC,YAAY,EAAE,gBAAgB,CAAC;QAChC,CAAC,SAAS,EAAE,eAAe,CAAC;QAC5B,CAAC,QAAQ,EAAE,YAAY,CAAC;QACxB,CAAC,UAAU,EAAE,cAAc,CAAC;QAC5B,CAAC,SAAS,EAAE,aAAa,CAAC;QAC1B,CAAC,SAAS,EAAE,oBAAoB,CAAC;QACjC,CAAC,QAAQ,EAAE,YAAY,CAAC;QACxB,CAAC,WAAW,EAAE,eAAe,CAAC;QAC9B,CAAC,OAAO,EAAE,WAAW,CAAC;QACtB,CAAC,WAAW,EAAE,eAAe,CAAC;QAC9B,CAAC,KAAK,EAAE,SAAS,CAAC;QAClB,CAAC,QAAQ,EAAE,YAAY,CAAC;QACxB,CAAC,OAAO,EAAE,WAAW,CAAC;QACtB,CAAC,MAAM,EAAE,cAAc,CAAC;KACzB,CAAC;IAEF,OAAO,MAAM;SACV,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;SACrD,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,4EAA4E;AAE5E,gEAAgE;AAChE,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9E,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;AACrD,CAAC;AAED,8EAA8E;AAE9E,wCAAwC;AACxC,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO;QACL,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,wDAAwD;QACrE,QAAQ,EAAE,UAAU;QACpB,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC;QAClD,IAAI;KACL,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,QAAyB,EACzB,WAAmB;IAEnB,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAEpC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC3C,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,YAAY,CAAC,WAAmB,EAAE,YAAoB;IACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IACxE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"editors.js","sourceRoot":"","sources":["../../src/commands/editors.ts"],"names":[],"mappings":"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"}
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openspec-playwright",
3
- "version": "0.1.40",
3
+ "version": "0.1.43",
4
4
  "description": "OpenSpec + Playwright E2E verification setup tool for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,8 @@
8
8
  },
9
9
  "scripts": {
10
10
  "build": "tsc",
11
+ "test": "vitest",
12
+ "test:run": "vitest run",
11
13
  "prepublishOnly": "npm run build",
12
14
  "release": "npm version patch && npm run build && git push && git push --tags && npm publish"
13
15
  },
@@ -18,7 +20,8 @@
18
20
  },
19
21
  "devDependencies": {
20
22
  "@types/node": "^22.0.0",
21
- "typescript": "^5.6.0"
23
+ "typescript": "^5.6.0",
24
+ "vitest": "^4.1.2"
22
25
  },
23
26
  "engines": {
24
27
  "node": ">=20"
package/release-notes.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ## What's Changed
2
2
 
3
- - feat: support all 24 OpenSpec editors
3
+ - chore: regenerate package-lock.json with vitest dev dependency
4
4
 
5
- **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.40
5
+ **Full Changelog**: https://github.com/wxhou/openspec-playwright/releases/tag/v0.1.43
@@ -151,7 +151,7 @@ const amazonqAdapter: EditorAdapter = {
151
151
  getCommandPath(id) { return join('.amazonq', 'prompts', `opsx-${id}.md`); },
152
152
  formatCommand(meta) {
153
153
  return `---
154
- description: ${escapeYamlValue(meta.description)}
154
+ description: ${meta.description}
155
155
  ---
156
156
 
157
157
  ${meta.body}
@@ -168,7 +168,7 @@ const antigravityAdapter: EditorAdapter = {
168
168
  getCommandPath(id) { return join('.agent', 'workflows', `opsx-${id}.md`); },
169
169
  formatCommand(meta) {
170
170
  return `---
171
- description: ${escapeYamlValue(meta.description)}
171
+ description: ${meta.description}
172
172
  ---
173
173
 
174
174
  ${meta.body}
@@ -185,7 +185,7 @@ const auggieAdapter: EditorAdapter = {
185
185
  getCommandPath(id) { return join('.augment', 'commands', `opsx-${id}.md`); },
186
186
  formatCommand(meta) {
187
187
  return `---
188
- description: ${escapeYamlValue(meta.description)}
188
+ description: ${meta.description}
189
189
  argument-hint: command arguments
190
190
  ---
191
191
 
@@ -203,7 +203,7 @@ const codebuddyAdapter: EditorAdapter = {
203
203
  getCommandPath(id) { return join('.codebuddy', 'commands', 'opsx', `${id}.md`); },
204
204
  formatCommand(meta) {
205
205
  return `---
206
- name: ${escapeYamlValue(meta.name)}
206
+ name: ${meta.name}
207
207
  description: "${meta.description}"
208
208
  argument-hint: "[command arguments]"
209
209
  ---
@@ -225,7 +225,7 @@ const codexAdapter: EditorAdapter = {
225
225
  },
226
226
  formatCommand(meta) {
227
227
  return `---
228
- description: ${escapeYamlValue(meta.description)}
228
+ description: ${meta.description}
229
229
  argument-hint: command arguments
230
230
  ---
231
231
 
@@ -254,16 +254,16 @@ ${meta.body}
254
254
 
255
255
  // ─── crush ────────────────────────────────────────────────────────────────
256
256
 
257
- /** Crush: .crush/commands/opsx/<id>.md — tags plain join */
257
+ /** Crush: .crush/commands/opsx/<id>.md — raw values, no escaping */
258
258
  const crushAdapter: EditorAdapter = {
259
259
  toolId: 'crush',
260
260
  hasSkill: false,
261
261
  getCommandPath(id) { return join('.crush', 'commands', 'opsx', `${id}.md`); },
262
262
  formatCommand(meta) {
263
263
  return `---
264
- name: ${escapeYamlValue(meta.name)}
265
- description: ${escapeYamlValue(meta.description)}
266
- category: ${escapeYamlValue(meta.category)}
264
+ name: ${meta.name}
265
+ description: ${meta.description}
266
+ category: ${meta.category}
267
267
  tags: ${formatTagsPlain(meta.tags)}
268
268
  ---
269
269
 
@@ -281,7 +281,7 @@ const factoryAdapter: EditorAdapter = {
281
281
  getCommandPath(id) { return join('.factory', 'commands', `opsx-${id}.md`); },
282
282
  formatCommand(meta) {
283
283
  return `---
284
- description: ${escapeYamlValue(meta.description)}
284
+ description: ${meta.description}
285
285
  argument-hint: command arguments
286
286
  ---
287
287
 
@@ -316,7 +316,7 @@ const githubcopilotAdapter: EditorAdapter = {
316
316
  getCommandPath(id) { return join('.github', 'prompts', `opsx-${id}.prompt.md`); },
317
317
  formatCommand(meta) {
318
318
  return `---
319
- description: ${escapeYamlValue(meta.description)}
319
+ description: ${meta.description}
320
320
  ---
321
321
 
322
322
  ${meta.body}
@@ -335,8 +335,8 @@ const iflowAdapter: EditorAdapter = {
335
335
  return `---
336
336
  name: /opsx-${meta.id}
337
337
  id: opsx-${meta.id}
338
- category: ${escapeYamlValue(meta.category)}
339
- description: ${escapeYamlValue(meta.description)}
338
+ category: ${meta.category}
339
+ description: ${meta.description}
340
340
  ---
341
341
 
342
342
  ${meta.body}
@@ -366,7 +366,7 @@ const kiroAdapter: EditorAdapter = {
366
366
  getCommandPath(id) { return join('.kiro', 'prompts', `opsx-${id}.prompt.md`); },
367
367
  formatCommand(meta) {
368
368
  return `---
369
- description: ${escapeYamlValue(meta.description)}
369
+ description: ${meta.description}
370
370
  ---
371
371
 
372
372
  ${meta.body}
@@ -384,7 +384,7 @@ const opencodeAdapter: EditorAdapter = {
384
384
  formatCommand(meta) {
385
385
  const transformed = transformToHyphenCommands(meta.body);
386
386
  return `---
387
- description: ${escapeYamlValue(meta.description)}
387
+ description: ${meta.description}
388
388
  ---
389
389
 
390
390
  ${transformed}
@@ -411,16 +411,16 @@ ${meta.body}
411
411
 
412
412
  // ─── qoder ────────────────────────────────────────────────────────────────
413
413
 
414
- /** Qoder: .qoder/commands/opsx/<id>.md — tags plain join */
414
+ /** Qoder: .qoder/commands/opsx/<id>.md — raw values, no escaping */
415
415
  const qoderAdapter: EditorAdapter = {
416
416
  toolId: 'qoder',
417
417
  hasSkill: false,
418
418
  getCommandPath(id) { return join('.qoder', 'commands', 'opsx', `${id}.md`); },
419
419
  formatCommand(meta) {
420
420
  return `---
421
- name: ${escapeYamlValue(meta.name)}
422
- description: ${escapeYamlValue(meta.description)}
423
- category: ${escapeYamlValue(meta.category)}
421
+ name: ${meta.name}
422
+ description: ${meta.description}
423
+ category: ${meta.category}
424
424
  tags: ${formatTagsPlain(meta.tags)}
425
425
  ---
426
426
 
@@ -570,4 +570,4 @@ export function installSkill(projectRoot: string, skillContent: string): void {
570
570
  console.log(chalk.green(` ✓ claude: .claude/skills/openspec-e2e/SKILL.md`));
571
571
  }
572
572
 
573
- export { claudeAdapter };
573
+ export { claudeAdapter, ALL_ADAPTERS };
@@ -13,6 +13,7 @@
13
13
  // For multi-user testing, add more setup blocks with different storageState paths.
14
14
 
15
15
  import { test as setup, expect } from '@playwright/test';
16
+ import { existsSync } from 'fs';
16
17
 
17
18
  // ─── API Login (preferred) ───────────────────────────────────────────────────
18
19
  // If your app has a login API, configure it in tests/playwright/credentials.yaml:
@@ -25,8 +26,10 @@ setup('authenticate via API', async ({ page }) => {
25
26
  const password = process.env.E2E_PASSWORD;
26
27
 
27
28
  if (!username || !password) {
28
- console.warn('⚠ E2E_USERNAME or E2E_PASSWORD not set — skipping API auth');
29
- return;
29
+ throw new Error(
30
+ 'E2E_USERNAME and E2E_PASSWORD environment variables are required.\n' +
31
+ 'Set them with: export E2E_USERNAME=xxx E2E_PASSWORD=yyy'
32
+ );
30
33
  }
31
34
 
32
35
  const res = await page.request.post(`${baseUrl}/api/auth/login`, {
@@ -38,6 +41,10 @@ setup('authenticate via API', async ({ page }) => {
38
41
  }
39
42
 
40
43
  await page.context().storageState({ path: './playwright/.auth/user.json' });
44
+
45
+ if (!existsSync('./playwright/.auth/user.json')) {
46
+ throw new Error('Auth setup failed: storageState file was not created');
47
+ }
41
48
  });
42
49
 
43
50
  // ─── UI Login (fallback) ─────────────────────────────────────────────────────
@@ -49,8 +56,10 @@ setup('authenticate via UI', async ({ page }) => {
49
56
  const password = process.env.E2E_PASSWORD;
50
57
 
51
58
  if (!username || !password) {
52
- console.warn('⚠ E2E_USERNAME or E2E_PASSWORD not set — skipping UI auth');
53
- return;
59
+ throw new Error(
60
+ 'E2E_USERNAME and E2E_PASSWORD environment variables are required.\n' +
61
+ 'Set them with: export E2E_USERNAME=xxx E2E_PASSWORD=yyy'
62
+ );
54
63
  }
55
64
 
56
65
  // TODO: Update these selectors to match your login page
@@ -74,4 +83,8 @@ setup('authenticate via UI', async ({ page }) => {
74
83
  await expect(page).not.toHaveURL(/.*login.*|.*signin.*/);
75
84
 
76
85
  await page.context().storageState({ path: './playwright/.auth/user.json' });
86
+
87
+ if (!existsSync('./playwright/.auth/user.json')) {
88
+ throw new Error('Auth setup failed: storageState file was not created');
89
+ }
77
90
  });
@@ -21,13 +21,13 @@ api: /api/auth/login # Leave empty for UI login
21
21
 
22
22
  users:
23
23
  - name: user
24
- username: test@example.com
25
- password: testpassword123
24
+ username: CHANGE_ME@example.com
25
+ password: CHANGE_ME_PASSWORD
26
26
 
27
27
  # Multi-user example (uncomment and configure for role-based tests):
28
28
  # - name: admin
29
- # username: admin@example.com
30
- # password: adminpassword123
29
+ # username: CHANGE_ME@admin.com
30
+ # password: CHANGE_ME_ADMIN_PASSWORD
31
31
  # - name: premium
32
- # username: premium@example.com
33
- # password: premiumpassword123
32
+ # username: CHANGE_ME@premium.com
33
+ # password: CHANGE_ME_PREMIUM_PASSWORD
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ escapeYamlValue,
4
+ formatTagsArray,
5
+ buildCommandMeta,
6
+ detectEditors,
7
+ detectCodex,
8
+ ALL_ADAPTERS,
9
+ } from '../src/commands/editors.js';
10
+
11
+ // ─── escapeYamlValue ──────────────────────────────────────────────────────────
12
+
13
+ describe('escapeYamlValue', () => {
14
+ it('returns raw value for plain strings', () => {
15
+ expect(escapeYamlValue('hello world')).toBe('hello world');
16
+ expect(escapeYamlValue('simple')).toBe('simple');
17
+ });
18
+
19
+ it('quotes strings with colons', () => {
20
+ expect(escapeYamlValue('hello: world')).toBe('"hello: world"');
21
+ });
22
+
23
+ it('quotes strings starting with whitespace', () => {
24
+ expect(escapeYamlValue(' hello')).toBe('" hello"');
25
+ });
26
+
27
+ it('quotes strings ending with whitespace', () => {
28
+ expect(escapeYamlValue('hello ')).toBe('"hello "');
29
+ });
30
+
31
+ it('quotes and escapes strings containing quotes', () => {
32
+ expect(escapeYamlValue('say "hello"')).toBe('"say \\"hello\\""');
33
+ });
34
+
35
+ it('quotes and escapes strings containing newlines', () => {
36
+ expect(escapeYamlValue('line1\nline2')).toBe('"line1\\nline2"');
37
+ });
38
+
39
+ it('quotes strings with special YAML chars', () => {
40
+ expect(escapeYamlValue('key: value')).toBe('"key: value"');
41
+ expect(escapeYamlValue('# comment')).toBe('"# comment"');
42
+ expect(escapeYamlValue('a{b}c')).toBe('"a{b}c"');
43
+ });
44
+
45
+ it('quotes strings with array-like chars', () => {
46
+ expect(escapeYamlValue('[a, b]')).toBe('"[a, b]"');
47
+ });
48
+ });
49
+
50
+ // ─── formatTagsArray ─────────────────────────────────────────────────────────
51
+
52
+ describe('formatTagsArray', () => {
53
+ it('formats empty tags', () => {
54
+ expect(formatTagsArray([])).toBe('[]');
55
+ });
56
+
57
+ it('formats single tag', () => {
58
+ expect(formatTagsArray(['openspec'])).toBe('[openspec]');
59
+ });
60
+
61
+ it('formats multiple tags', () => {
62
+ expect(formatTagsArray(['openspec', 'playwright', 'e2e'])).toBe(
63
+ '[openspec, playwright, e2e]'
64
+ );
65
+ });
66
+
67
+ it('escapes tags with colons using YAML quoting', () => {
68
+ // escapeYamlValue quotes 'tag:colon' → "tag:colon", so array has quoted element
69
+ expect(formatTagsArray(['tag:colon'])).toBe('["tag:colon"]');
70
+ });
71
+ });
72
+
73
+ // ─── buildCommandMeta ─────────────────────────────────────────────────────────
74
+
75
+ describe('buildCommandMeta', () => {
76
+ it('creates meta with correct id', () => {
77
+ const meta = buildCommandMeta('test body');
78
+ expect(meta.id).toBe('e2e');
79
+ });
80
+
81
+ it('creates meta with correct fields', () => {
82
+ const meta = buildCommandMeta('test body');
83
+ expect(meta.name).toBe('OPSX: E2E');
84
+ expect(meta.description).toBe('Run Playwright E2E verification for an OpenSpec change');
85
+ expect(meta.category).toBe('OpenSpec');
86
+ expect(meta.tags).toEqual(['openspec', 'playwright', 'e2e', 'testing']);
87
+ expect(meta.body).toBe('test body');
88
+ });
89
+ });
90
+
91
+ // ─── detectEditors ────────────────────────────────────────────────────────────
92
+
93
+ describe('detectEditors', () => {
94
+ it('returns empty array for non-existent directory', () => {
95
+ const result = detectEditors('/tmp/nonexistent-project-xyz-123');
96
+ expect(result).toEqual([]);
97
+ });
98
+
99
+ it('detects Claude Code when .claude directory exists', () => {
100
+ const result = detectEditors('/Users/wxhou/Documents/openspec-playwright');
101
+ const names = result.map(a => a.toolId);
102
+ expect(names).toContain('claude');
103
+ });
104
+ });
105
+
106
+ // ─── detectCodex ─────────────────────────────────────────────────────────────
107
+
108
+ describe('detectCodex', () => {
109
+ it('returns null when CODEX_HOME is set to non-existent path', () => {
110
+ const original = process.env.CODEX_HOME;
111
+ process.env.CODEX_HOME = '/tmp/this-codex-dir-does-not-exist-xyz';
112
+ const result = detectCodex();
113
+ expect(result).toBeNull();
114
+ if (original !== undefined) {
115
+ process.env.CODEX_HOME = original;
116
+ } else {
117
+ delete process.env.CODEX_HOME;
118
+ }
119
+ });
120
+ });
121
+
122
+ // ─── Adapter format correctness ───────────────────────────────────────────────
123
+
124
+ describe('Adapter format correctness', () => {
125
+ it('every adapter has a toolId', () => {
126
+ for (const adapter of ALL_ADAPTERS) {
127
+ expect(adapter.toolId).toBeTruthy();
128
+ }
129
+ });
130
+
131
+ it('every adapter has formatCommand that returns non-empty string', () => {
132
+ const meta = buildCommandMeta('test body content');
133
+ for (const adapter of ALL_ADAPTERS) {
134
+ const output = adapter.formatCommand(meta);
135
+ expect(output.length).toBeGreaterThan(0);
136
+ }
137
+ });
138
+
139
+ it('every adapter getCommandPath returns a path with the id', () => {
140
+ const meta = buildCommandMeta('test');
141
+ for (const adapter of ALL_ADAPTERS) {
142
+ const path = adapter.getCommandPath(meta.id);
143
+ expect(path).toContain('opsx');
144
+ }
145
+ });
146
+
147
+ it('Claude adapter output has YAML frontmatter', () => {
148
+ const meta = buildCommandMeta('body');
149
+ const output = ALL_ADAPTERS[0].formatCommand(meta);
150
+ expect(output).toContain('---');
151
+ expect(output).toContain('name:');
152
+ expect(output).toContain('description:');
153
+ });
154
+
155
+ it('TOML adapters (gemini, qwen) do not use YAML frontmatter', () => {
156
+ const meta = buildCommandMeta('body');
157
+ const adapters = ALL_ADAPTERS.filter(
158
+ a => a.toolId === 'gemini' || a.toolId === 'qwen'
159
+ );
160
+ for (const adapter of adapters) {
161
+ const output = adapter.formatCommand(meta);
162
+ expect(output).not.toContain('---');
163
+ expect(output).toContain('description =');
164
+ expect(output).toContain('prompt =');
165
+ }
166
+ });
167
+
168
+ it('crush and qoder use raw values without escaping in name/description', () => {
169
+ const meta = buildCommandMeta('body');
170
+ const adapters = ALL_ADAPTERS.filter(
171
+ a => a.toolId === 'crush' || a.toolId === 'qoder'
172
+ );
173
+ for (const adapter of adapters) {
174
+ const output = adapter.formatCommand(meta);
175
+ // Should NOT have escaped quotes in the raw-value fields
176
+ expect(output).not.toContain('\\\\"');
177
+ expect(output).toContain('name: OPSX: E2E');
178
+ }
179
+ });
180
+ });
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ globals: true,
7
+ environment: 'node',
8
+ },
9
+ });
Binary file