playwright-e2e-mcp 0.2.2 → 0.3.1
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/README.md +255 -35
- package/dist/cli/ci.d.ts +29 -0
- package/dist/cli/ci.js +145 -0
- package/dist/cli/ci.js.map +1 -0
- package/dist/cli/init.d.ts +40 -0
- package/dist/cli/init.js +218 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +15 -0
- package/dist/server.js +54 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/diagnose-flaky.d.ts +42 -4
- package/dist/tools/diagnose-flaky.js +342 -98
- package/dist/tools/diagnose-flaky.js.map +1 -1
- package/dist/types/index.d.ts +29 -0
- package/dist/utils/attempts-reporter.d.ts +19 -0
- package/dist/utils/attempts-reporter.js +96 -0
- package/dist/utils/attempts-reporter.js.map +1 -0
- package/dist/utils/playwright-runner.js +16 -1
- package/dist/utils/playwright-runner.js.map +1 -1
- package/dist/utils/report-parser.d.ts +1 -0
- package/dist/utils/report-parser.js +1 -1
- package/dist/utils/report-parser.js.map +1 -1
- package/package.json +18 -6
- package/skill/README.md +34 -0
- package/skill/playwright-e2e-mcp/SKILL.md +196 -0
- package/skill/playwright-e2e-mcp/references/example-session.md +53 -0
- package/skill/playwright-e2e-mcp/references/failure-kinds.md +32 -0
- package/skill/playwright-e2e-mcp/references/setup.md +120 -0
- package/skill/playwright-e2e-mcp/references/tools.md +161 -0
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# playwright-e2e-mcp
|
|
2
2
|
|
|
3
|
-
[](https://github.com/xsignon-secure/E2E/actions/workflows/ci.yml) [](https://github.com/xsignon-secure/E2E/releases) [](https://registry.modelcontextprotocol.io/)
|
|
4
4
|
[](https://m8ven.ai/mcp/trajectiq-ai/e2e?s=readme)
|
|
5
5
|
|
|
6
|
+
[](https://cursor.com/en/install-mcp?name=playwright-e2e&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInBsYXl3cmlnaHQtZTJlLW1jcCJdfQ==)
|
|
7
|
+
[](https://insiders.vscode.dev/redirect?url=vscode%3Amcp%2Finstall%3F%257B%2522name%2522%253A%2522playwright-e2e%2522%252C%2522command%2522%253A%2522npx%2522%252C%2522args%2522%253A%255B%2522-y%2522%252C%2522playwright-e2e-mcp%2522%255D%257D)
|
|
8
|
+
[](https://github.com/xsignon-secure/E2E/releases/latest)
|
|
9
|
+
|
|
6
10
|
An [MCP](https://modelcontextprotocol.io) server that **diagnoses and fixes failing Playwright
|
|
7
11
|
tests**. It is the layer that sits next to a browser-driving MCP: Playwright MCP gives an agent
|
|
8
12
|
hands in the browser; this server gives it a test runner, a failure analyst and a verified fixer.
|
|
@@ -26,9 +30,13 @@ What it does that a browser-driving MCP or a raw `npx playwright test` does not:
|
|
|
26
30
|
(`getByRole('button', { name: 'Proceed to checkout' })`), re-runs the test and reverts the
|
|
27
31
|
edit if it is still red. When nothing on the page plausibly matches, it says the element is
|
|
28
32
|
missing and does not patch the test to hide a real bug.
|
|
29
|
-
- **Flakiness
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
- **Flakiness with evidence.** `diagnose-flaky` repeats failing tests in one process and
|
|
34
|
+
gives a verdict per test; with `isolate: true` it re-runs them alone to tell a shared-state
|
|
35
|
+
bug that only bites in parallel from a race inside the test. Every run is recorded locally;
|
|
36
|
+
`analyze-history` separates tests that flip (flaky) from ones that started failing
|
|
37
|
+
(regressed) or always fail (broken), and groups failures that share one cause.
|
|
38
|
+
- **In CI too.** A [GitHub Action and `ci` command](#ci-github-action-and-ci-command) post the
|
|
39
|
+
same diagnosis, flaky verdict and fix diffs to the job summary and the PR.
|
|
32
40
|
- **Pages behind a login.** Every page tool accepts a Playwright `storageState`, extra
|
|
33
41
|
headers, and click/fill steps to reach a modal or a later step.
|
|
34
42
|
- **Small on the wire.** In the [benchmark](bench/RESULTS.md), diagnosing and fixing one
|
|
@@ -48,27 +56,47 @@ work side by side in the same client.
|
|
|
48
56
|
`https://playwright-e2e-mcp.vercel.app/api/mcp` returns for a client that sends the
|
|
49
57
|
bearer token (without one, only `list-tests` and `get-failure` are listed):
|
|
50
58
|
|
|
51
|
-

|
|
52
60
|
|
|
53
61
|
**A real test run** — `run-test` served over stdio by `npx -y playwright-e2e-mcp`
|
|
54
62
|
against the bundled `examples/sample-test.spec.ts` (actual output, unedited):
|
|
55
63
|
|
|
56
|
-

|
|
57
65
|
|
|
58
66
|
Images are rendered with `node scripts/gen-demo-images.mjs`: the run-test card is real
|
|
59
67
|
captured output; the endpoint card is an illustration of the authenticated listing.
|
|
60
68
|
|
|
69
|
+
> If this saves you a debugging session, a ⭐ on the repo helps other Playwright users find it.
|
|
70
|
+
|
|
61
71
|
## Install
|
|
62
72
|
|
|
63
73
|
Works with Claude Desktop, Claude Code, Cursor, Windsurf, Codex, Gemini CLI, Freebuff
|
|
64
74
|
and every other MCP client — pick whichever route fits:
|
|
65
75
|
|
|
76
|
+
**Let your agent do it.** Paste this into Claude Code, Cursor, Codex, Gemini CLI or any
|
|
77
|
+
other coding agent:
|
|
78
|
+
|
|
79
|
+
> Install the playwright-e2e-mcp server and skill by following
|
|
80
|
+
> https://raw.githubusercontent.com/xsignon-secure/E2E/main/llms-install.md
|
|
81
|
+
|
|
82
|
+
or run the installer yourself from the project root. It merges the server into each
|
|
83
|
+
client's project config it finds (Claude Code, Cursor, VS Code, Gemini CLI, Codex) and
|
|
84
|
+
installs the agent skill, leaving your other servers alone:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx -y playwright-e2e-mcp init # --client all | cursor,vscode … · --dry-run
|
|
88
|
+
```
|
|
89
|
+
|
|
66
90
|
| Route | How |
|
|
67
91
|
| --- | --- |
|
|
92
|
+
| **One command, any project** | `npx -y playwright-e2e-mcp init` |
|
|
93
|
+
| **Claude Code plugin (server + skill)** | `/plugin marketplace add xsignon-secure/E2E` then `/plugin install playwright-e2e-mcp@xsignon-secure` |
|
|
94
|
+
| **Gemini CLI extension (server + skill)** | `gemini extensions install https://github.com/xsignon-secure/E2E` |
|
|
95
|
+
| **Skill only, any agent** | `npx skills add xsignon-secure/E2E` |
|
|
68
96
|
| **npm (canonical, fastest)** | `npx -y playwright-e2e-mcp` |
|
|
69
|
-
| **MCP Registry** (registry-aware clients discover it automatically) | `io.github.
|
|
70
|
-
| **Any client, no npm account needed** | `npx -y github:
|
|
71
|
-
| **Claude Desktop, zero Node setup** | double-click the [`.mcpb` extension](https://github.com/
|
|
97
|
+
| **MCP Registry** (registry-aware clients discover it automatically) | `io.github.xsignon-secure/E2E` — [listing](https://registry.modelcontextprotocol.io/) |
|
|
98
|
+
| **Any client, no npm account needed** | `npx -y github:xsignon-secure/E2E#v0.3.1` (pin a release tag) |
|
|
99
|
+
| **Claude Desktop, zero Node setup** | double-click the [`.mcpb` extension](https://github.com/xsignon-secure/E2E/releases) |
|
|
72
100
|
| **Remote-only clients (ChatGPT connectors)** | `https://playwright-e2e-mcp.vercel.app/api/mcp` |
|
|
73
101
|
|
|
74
102
|
One line for the common clients:
|
|
@@ -78,6 +106,9 @@ claude mcp add playwright-e2e -- npx -y playwright-e2e-mcp
|
|
|
78
106
|
code --add-mcp '{"name":"playwright-e2e","command":"npx","args":["-y","playwright-e2e-mcp"]}'
|
|
79
107
|
```
|
|
80
108
|
|
|
109
|
+
Only need the fix loop? `--tools=core` (or `PW_MCP_TOOLS=core`) exposes six tools and
|
|
110
|
+
halves the tool-definition context; see [Configuration](#configuration).
|
|
111
|
+
|
|
81
112
|
Details and per-client config: [Installation](#installation) ·
|
|
82
113
|
[MCP client configuration](#mcp-client-configuration).
|
|
83
114
|
|
|
@@ -283,14 +314,21 @@ and write a red-highlighted diff image for review.
|
|
|
283
314
|
| Argument | Type | Description |
|
|
284
315
|
| --- | --- | --- |
|
|
285
316
|
| `testFiles` | string[] | Tests to diagnose (`file:line` supported). Defaults to the tests that failed in the most recent run |
|
|
286
|
-
| `runs` | number |
|
|
317
|
+
| `runs` | number | Repetitions, `2–10` (default `3`) |
|
|
318
|
+
| `isolate` | boolean | Also tell parallelism bugs from flakiness: repeat fully parallel (`workers` default `4`), then repeat the failing tests with one worker |
|
|
319
|
+
| `separateProcesses` | boolean | Start a fresh Playwright process per repetition instead of one `--repeat-each` process (slower; for flakes that depend on global setup or a cold start) |
|
|
287
320
|
| `browser` / `headed` / `workers` / `config` | — | As with `run-test` |
|
|
288
|
-
| `timeoutMs` | number | Hard wall-clock limit **per
|
|
321
|
+
| `timeoutMs` | number | Hard wall-clock limit **per repetition** (default `120000`); one `--repeat-each` process gets this × `runs` |
|
|
289
322
|
| `projectRoot` | string | Project directory |
|
|
290
323
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
324
|
+
All repetitions run in **one Playwright process** with `--repeat-each` and
|
|
325
|
+
`--retries=0`, so each attempt is honest evidence and the browser and workers
|
|
326
|
+
start once instead of once per run (about 25% faster on the integration fixture,
|
|
327
|
+
more for suites with expensive setup). A small extra reporter records every
|
|
328
|
+
attempt, because Playwright's JSON report keeps only one result per test.
|
|
329
|
+
|
|
330
|
+
The verdict is **per test**: a table of passes, failures, duration spread and
|
|
331
|
+
first error for each test, plus the verdict block for the worst one:
|
|
294
332
|
|
|
295
333
|
- **❌ CONSISTENTLY FAILING** — failed every run (same error → reproducible bug,
|
|
296
334
|
different errors → still broken, just noisy). Fix it; it is not flaky.
|
|
@@ -299,7 +337,17 @@ failure), the count of **distinct normalized error signatures**, and one of:
|
|
|
299
337
|
instability).
|
|
300
338
|
- **✅ NOT REPRODUCING** — passed every re-run; the original failure was one-off.
|
|
301
339
|
|
|
302
|
-
|
|
340
|
+
With `isolate: true` a second table compares each failing test **in parallel**
|
|
341
|
+
and **alone**:
|
|
342
|
+
|
|
343
|
+
| Reading | Meaning |
|
|
344
|
+
| --- | --- |
|
|
345
|
+
| 🔀 fails only in parallel | Passes every run alone: shared state between tests (same account, DB rows, files, ports). Give each worker its own data or run the file serially |
|
|
346
|
+
| ⏱️ flaky even alone | The race is inside the test or the app: missing `await`, asserting before the UI settles |
|
|
347
|
+
| ❌ fails alone too | Not a parallelism problem; a real bug |
|
|
348
|
+
|
|
349
|
+
Each repetition is recorded in the run history, and the last failing run is
|
|
350
|
+
stored, so `analyze-history` and `get-failure` work right afterwards.
|
|
303
351
|
|
|
304
352
|
### `suggest-fix`
|
|
305
353
|
|
|
@@ -367,11 +415,11 @@ Requirements:
|
|
|
367
415
|
|
|
368
416
|
**No npm account needed** — install straight from GitHub (the `prepare` script
|
|
369
417
|
builds `dist/` automatically on install). Pin a release tag: an unpinned
|
|
370
|
-
`github:
|
|
418
|
+
`github:xsignon-secure/E2E` runs whatever is on the default branch at that moment.
|
|
371
419
|
|
|
372
420
|
```bash
|
|
373
|
-
npx -y github:
|
|
374
|
-
npm install -D github:
|
|
421
|
+
npx -y github:xsignon-secure/E2E#v0.3.1
|
|
422
|
+
npm install -D github:xsignon-secure/E2E#v0.3.1 @playwright/test # or as a project dependency
|
|
375
423
|
npx playwright install chromium
|
|
376
424
|
```
|
|
377
425
|
|
|
@@ -379,11 +427,11 @@ Or grab the packaged tarball from the repo's **GitHub Releases** page and instal
|
|
|
379
427
|
it locally:
|
|
380
428
|
|
|
381
429
|
```bash
|
|
382
|
-
npm install -D https://github.com/
|
|
430
|
+
npm install -D https://github.com/xsignon-secure/E2E/releases/download/v0.3.1/playwright-e2e-mcp-0.3.1.tgz
|
|
383
431
|
```
|
|
384
432
|
|
|
385
433
|
Listed in the **official [MCP Registry](https://registry.modelcontextprotocol.io/)** as
|
|
386
|
-
`io.github.
|
|
434
|
+
`io.github.xsignon-secure/E2E` — registry-aware clients discover it there, and every
|
|
387
435
|
`v*` release tag republishes the entry from CI via [`server.json`](server.json).
|
|
388
436
|
|
|
389
437
|
### MCP client configuration
|
|
@@ -395,7 +443,7 @@ Listed in the **official [MCP Registry](https://registry.modelcontextprotocol.io
|
|
|
395
443
|
"mcpServers": {
|
|
396
444
|
"playwright-e2e": {
|
|
397
445
|
"command": "npx",
|
|
398
|
-
"args": ["-y", "github:
|
|
446
|
+
"args": ["-y", "github:xsignon-secure/E2E#v0.3.1"],
|
|
399
447
|
"env": { "PW_MCP_PROJECT_ROOT": "/absolute/path/to/your/project" }
|
|
400
448
|
}
|
|
401
449
|
}
|
|
@@ -409,8 +457,8 @@ when the client launches it somewhere else (e.g. your home directory).
|
|
|
409
457
|
**Codex / VS Code / Copilot CLIs:**
|
|
410
458
|
|
|
411
459
|
```bash
|
|
412
|
-
codex mcp add playwright-e2e -- npx -y github:
|
|
413
|
-
code --add-mcp '{"name":"playwright-e2e","command":"npx","args":["-y","github:
|
|
460
|
+
codex mcp add playwright-e2e -- npx -y github:xsignon-secure/E2E#v0.3.1
|
|
461
|
+
code --add-mcp '{"name":"playwright-e2e","command":"npx","args":["-y","github:xsignon-secure/E2E#v0.3.1"]}'
|
|
414
462
|
```
|
|
415
463
|
|
|
416
464
|
Codex's defaults fight this server: the first launch clones the repo and runs
|
|
@@ -421,13 +469,13 @@ Codex's defaults fight this server: the first launch clones the repo and runs
|
|
|
421
469
|
```toml
|
|
422
470
|
[mcp_servers.playwright-e2e]
|
|
423
471
|
command = "npx"
|
|
424
|
-
args = ["-y", "github:
|
|
472
|
+
args = ["-y", "github:xsignon-secure/E2E#v0.3.1"]
|
|
425
473
|
startup_timeout_sec = 60
|
|
426
474
|
tool_timeout_sec = 600
|
|
427
475
|
```
|
|
428
476
|
|
|
429
477
|
**Claude Desktop (one-click):** download and double-click the `.mcpb` Desktop
|
|
430
|
-
Extension attached to the [latest release](https://github.com/
|
|
478
|
+
Extension attached to the [latest release](https://github.com/xsignon-secure/E2E/releases) —
|
|
431
479
|
the bundle ships its own dependencies, so no Node setup is required. On install it
|
|
432
480
|
asks you to pick your **project root** (required, no default: choose the project
|
|
433
481
|
folder, not your home directory) and wires it
|
|
@@ -436,7 +484,7 @@ into `PW_MCP_PROJECT_ROOT`, so the tools point at a real project from the first
|
|
|
436
484
|
**Claude Code:**
|
|
437
485
|
|
|
438
486
|
```bash
|
|
439
|
-
claude mcp add playwright-e2e -- npx -y github:
|
|
487
|
+
claude mcp add playwright-e2e -- npx -y github:xsignon-secure/E2E#v0.3.1
|
|
440
488
|
```
|
|
441
489
|
|
|
442
490
|
**Gemini CLI / Qwen Code:** paste the `mcpServers` block above into
|
|
@@ -545,11 +593,60 @@ Redeploy after a change: merge to `main`. Vercel's Git integration deploys
|
|
|
545
593
|
every push, so there is no token to manage and no CLI step — watch the
|
|
546
594
|
`Vercel` commit status for the deployment result.
|
|
547
595
|
|
|
596
|
+
## CI: GitHub Action and `ci` command
|
|
597
|
+
|
|
598
|
+
The same diagnosis runs without an agent. `playwright-e2e-mcp ci` runs the
|
|
599
|
+
suite (auto-retry once, so flaky tests are reported as flaky, not failed),
|
|
600
|
+
optionally drafts `suggest-fix` diffs and a flaky verdict for the failures, and
|
|
601
|
+
writes one Markdown report to stdout, to `--output`, and to the GitHub job
|
|
602
|
+
summary when `GITHUB_STEP_SUMMARY` is set. Exit code `0` passed, `1` tests
|
|
603
|
+
failed, `2` the run itself failed.
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
npx -y playwright-e2e-mcp ci --flaky-runs 3 --suggest 2 --output report.md
|
|
607
|
+
npx -y playwright-e2e-mcp ci --help
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
As a GitHub Action, it adds the report to the job summary and keeps one PR
|
|
611
|
+
comment up to date when the run fails:
|
|
612
|
+
|
|
613
|
+
```yaml
|
|
614
|
+
permissions:
|
|
615
|
+
contents: read
|
|
616
|
+
pull-requests: write # only for the PR comment
|
|
617
|
+
steps:
|
|
618
|
+
- uses: actions/checkout@v6
|
|
619
|
+
- uses: actions/setup-node@v6
|
|
620
|
+
with: { node-version: 24 }
|
|
621
|
+
- run: npm ci && npx playwright install --with-deps chromium
|
|
622
|
+
- uses: xsignon-secure/E2E@v0.3.1
|
|
623
|
+
with:
|
|
624
|
+
flaky-runs: 3 # 0 skips the flaky verdict
|
|
625
|
+
suggest: 2 # suggest-fix diffs for the first 2 failures
|
|
626
|
+
# args: --browser chromium tests/checkout
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Inputs: `project-root`, `args`, `flaky-runs`, `suggest`, `comment`,
|
|
630
|
+
`fail-on-failure`, `version`, `github-token`. Outputs: `exit-code`, `report`.
|
|
631
|
+
|
|
632
|
+
## Docker
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
docker build -t playwright-e2e-mcp .
|
|
636
|
+
docker run -i --rm -v "$PWD:/work" playwright-e2e-mcp # stdio MCP server
|
|
637
|
+
docker run --rm -v "$PWD:/work" playwright-e2e-mcp ci # CI report
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
The image is based on `mcr.microsoft.com/playwright:v1.63.0-noble`, so its
|
|
641
|
+
browsers match Playwright 1.63. Mount your project at `/work`; its own
|
|
642
|
+
`node_modules` provide `@playwright/test`.
|
|
643
|
+
|
|
548
644
|
## Configuration
|
|
549
645
|
|
|
550
646
|
| Environment variable | Default | Purpose |
|
|
551
647
|
| --- | --- | --- |
|
|
552
648
|
| `PW_MCP_PROJECT_ROOT` | server cwd | Default project root for every tool |
|
|
649
|
+
| `PW_MCP_TOOLS` | all | Which tools to expose, as tool names and/or profiles, comma separated (same as the `--tools=` flag). Profiles: `core` (run, status, failure, fix, flaky, list), `triage` (run, status, failure, flaky, history, list), `inspect` (page, selector, visual), `author` (list, page, selector, generate, run). All eleven tool definitions cost about 5.2k tokens of context per request; `core` costs about 2.4k |
|
|
553
650
|
| `PW_MCP_STORAGE_STATE` | — | Default `storageState` file (inside the project) for the page tools |
|
|
554
651
|
| `PW_MCP_HISTORY` | on | `0` stops recording runs to `.playwright-e2e-mcp/history.jsonl` |
|
|
555
652
|
| `PW_MCP_ALLOWED_ROOTS` | — | Extra directories a caller may pass as `projectRoot` (`:`-separated, `;` on Windows). Anything outside these and the default root is rejected |
|
|
@@ -686,6 +783,9 @@ npm run e2e # build + e2e/run.mjs: live MCP ↔ Playwright integration suite
|
|
|
686
783
|
npm run mcpb # build + bundle dist/ into playwright-e2e-mcp-<version>.mcpb (+ .sha256)
|
|
687
784
|
npm run mcpb:smoke # ...then install that bundle in a temp dir, launch it and assert the handshake
|
|
688
785
|
npm run mcpb:sign # ...then sign it with MCPB_CERT/MCPB_KEY → <version>.signed.mcpb
|
|
786
|
+
npm run skill:zip # just the archive: dist-skill/playwright-e2e-mcp.zip
|
|
787
|
+
npm run skill:package # the uploadable folder: ../agensi-skill-<version>/ (skill copy, README, zip)
|
|
788
|
+
npm run skill:check # build, then fail if a packaged copy has drifted from skill/
|
|
689
789
|
```
|
|
690
790
|
|
|
691
791
|
Tests cover the report parser (sample Playwright JSON, trace attachments), path utils
|
|
@@ -695,11 +795,15 @@ helpers, the **trace reader** (synthetic trace.zip: error, failed action, DOM sn
|
|
|
695
795
|
`*.network` failed-request parsing, console error/warning events), the **image diff**
|
|
696
796
|
(PNG round-trip, regions, color shift, dimension changes), the **change analyzer**
|
|
697
797
|
(selector extraction, git + mtime paths), the **flaky verdict logic**
|
|
698
|
-
(failure signatures, CONSISTENTLY FAILING / FLAKY / NOT REPRODUCING / NO TESTS RAN
|
|
798
|
+
(failure signatures, CONSISTENTLY FAILING / FLAKY / NOT REPRODUCING / NO TESTS RAN,
|
|
799
|
+
per-test grouping of repeat attempts and the parallel-vs-alone reading), the `ci`
|
|
800
|
+
argument parser and tool profiles,
|
|
699
801
|
the **HTTP bridge** (token, restricted mode, Host allowlist), the **security
|
|
700
802
|
regressions** (sandbox escapes, argument smuggling, symlink writes, code injection,
|
|
701
803
|
SSRF ranges, env scrubbing, decode limits), the `.mcpb` manifest, the `.mcpb` zip
|
|
702
|
-
extractor,
|
|
804
|
+
extractor, the **skill package** (byte-identical rebuilds, a CRLF checkout hashing the
|
|
805
|
+
same, refusal of junk/binary/symlinks, and drift detection between `skill/` and a
|
|
806
|
+
packaged copy), and the MCP config files.
|
|
703
807
|
|
|
704
808
|
### Integration suite (`npm run e2e`)
|
|
705
809
|
|
|
@@ -719,7 +823,10 @@ that only appear end-to-end:
|
|
|
719
823
|
- `get-failure` trace diagnostics: DOM at failure, the 404 network request,
|
|
720
824
|
the `console.error` message, diagnosis and next steps,
|
|
721
825
|
- auto-retry turning a first-run failure into `PASSED (1 flaky)`,
|
|
722
|
-
- `diagnose-flaky` verdict **FLAKY** (2 of 3 runs) with retries disabled,
|
|
826
|
+
- `diagnose-flaky` verdict **FLAKY** (2 of 3 runs) with retries disabled, in one
|
|
827
|
+
`--repeat-each` process, and `isolate` reading a shared-slot test as "fails only in parallel",
|
|
828
|
+
- `--tools=` profiles trimming `tools/list`, and the `ci` command's exit code, report,
|
|
829
|
+
`--output` file and GitHub job summary,
|
|
723
830
|
- `generate-e2e-test` writing its scaffold, plus error paths
|
|
724
831
|
(missing test path, unknown tool, unreachable server).
|
|
725
832
|
|
|
@@ -753,24 +860,137 @@ but not third-party identity. A certificate issued by a public CA, or an
|
|
|
753
860
|
internal one installed on your machines, is what makes hosts report it as
|
|
754
861
|
signed.
|
|
755
862
|
|
|
863
|
+
### Assembling a release
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
npm run mcpb && npm pack # the bundle, then the npm tarball
|
|
867
|
+
npm run release:assemble -- --tag v0.2.3 # sign, checksum and write the notes
|
|
868
|
+
npm run release:publish -- --tag v0.2.3 --assets dist-release/assets \
|
|
869
|
+
--title-file dist-release/RELEASE_TITLE.txt \
|
|
870
|
+
--body-file dist-release/RELEASE_BODY.md --download dist-release/downloaded
|
|
871
|
+
npm run release:verify -- v0.2.3 --dir dist-release/downloaded
|
|
872
|
+
npm run release:publish -- --tag v0.2.3 --publish
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
Pushing a `v*` tag runs exactly that sequence in
|
|
876
|
+
`.github/workflows/release.yml`. The release used to be assembled by hand, and
|
|
877
|
+
the notes were written from what those commands printed: every hash in them was
|
|
878
|
+
a copy, and nothing compared the bundle hash printed there with the bundle that
|
|
879
|
+
shipped.
|
|
880
|
+
|
|
881
|
+
`scripts/assemble-release.mjs` stages the whole release into
|
|
882
|
+
`dist-release/assets/`: the bundle and its sidecar, the **signed** bundle and the
|
|
883
|
+
signing certificate when a key is configured, the npm tarball, the skill package
|
|
884
|
+
built by the same writer as `npm run skill:zip`, and `SHA256SUMS.txt` covering
|
|
885
|
+
every file it staged. It empties that directory first, so a file left over from
|
|
886
|
+
an earlier run cannot be hashed and published as if this run had produced it.
|
|
887
|
+
|
|
888
|
+
The notes are half written, half generated. `release-notes/<tag>.md` holds the
|
|
889
|
+
prose — its H1 becomes the release title and has to name the tag — and the script
|
|
890
|
+
appends the asset table, the reproduction recipe carrying the real bundle hash,
|
|
891
|
+
the signer and fingerprint, and the commit list. The prose is held to the same
|
|
892
|
+
rule: a 64-character hex string in it has to be a hash this release publishes,
|
|
893
|
+
and writing an `### Assets` section of your own fails the assembly rather than
|
|
894
|
+
shipping a second, staler table. See [release-notes/README.md](release-notes/README.md).
|
|
895
|
+
|
|
896
|
+
`scripts/publish-release.mjs` fills the release **as a draft**, uploads the
|
|
897
|
+
assets, downloads every one of them back and compares it byte for byte, and only
|
|
898
|
+
`--publish` makes it public — after `release:verify` has re-derived the release
|
|
899
|
+
from the tag. Re-running is safe: a draft this workflow assembled is refreshed in
|
|
900
|
+
place, while a release it did not assemble, or one already published, is refused
|
|
901
|
+
by name instead of overwritten.
|
|
902
|
+
|
|
903
|
+
Signing is optional and its absence is stated rather than hidden: without the
|
|
904
|
+
`MCPB_KEY` and `MCPB_CERT` repository secrets the release is assembled and
|
|
905
|
+
published **unsigned**, with a notice in the run and a line saying so in the
|
|
906
|
+
notes. Add both — the private key stays out of the repository and out of the logs:
|
|
907
|
+
|
|
908
|
+
```bash
|
|
909
|
+
gh secret set MCPB_KEY --repo xsignon-secure/E2E < ~/signing/key.pem
|
|
910
|
+
gh secret set MCPB_CERT --repo xsignon-secure/E2E < ~/signing/cert.pem
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Creating the release and uploading its assets needs `contents: write`, which the
|
|
914
|
+
job requests for itself; if it is refused, the run names the setting to change.
|
|
915
|
+
|
|
756
916
|
### Verifying a published release
|
|
757
917
|
|
|
758
918
|
```bash
|
|
759
|
-
git checkout v0.
|
|
760
|
-
npm run release:verify -- v0.
|
|
919
|
+
git checkout v0.3.1 && npm ci && npm run mcpb
|
|
920
|
+
npm run release:verify -- v0.3.1
|
|
761
921
|
```
|
|
762
922
|
|
|
763
923
|
The release notes promise that the tag rebuilds the published bundle byte for
|
|
764
924
|
byte. `scripts/verify-release.mjs` checks that promise from a checkout of the
|
|
765
925
|
tag: the published bundle against your fresh build, every checksum the release
|
|
766
926
|
publishes (and that nothing shippable is missing from `SHA256SUMS.txt`), a
|
|
767
|
-
**signed** bundle against the bytes its signature wraps,
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
927
|
+
**signed** bundle against the bytes its signature wraps, the **skill package**
|
|
928
|
+
against a rebuild from the same tag, and that the published archive still
|
|
929
|
+
extracts with that version's manifest. The skill package is required of a tag
|
|
930
|
+
whose tree carries the publishing workflow; older tags predate it, so there it
|
|
931
|
+
is checked when present and skipped when not, like the signing certificate.
|
|
932
|
+
`.github/workflows/verify-release.yml` runs it on demand for any past tag, and by
|
|
933
|
+
itself for a release published by hand. A release assembled by
|
|
934
|
+
`.github/workflows/release.yml` runs the same check on its own assets *before*
|
|
935
|
+
it is published — GitHub does not raise `release: published` for a release created
|
|
936
|
+
with the repository's own token, so the check is a step there rather than a
|
|
937
|
+
trigger. The `.tgz` is checked for download integrity only: npm
|
|
771
938
|
records the packer's line endings, so a tarball packed locally on another OS is
|
|
772
939
|
legitimately different.
|
|
773
940
|
|
|
941
|
+
### Packaging the agent skill
|
|
942
|
+
|
|
943
|
+
```bash
|
|
944
|
+
npm run skill:package # ../agensi-skill-<version>/, ready to upload
|
|
945
|
+
npm run skill:zip # only the archive: dist-skill/playwright-e2e-mcp.zip
|
|
946
|
+
npm run skill:check # build, then compare every copy
|
|
947
|
+
npm run skill:check -- ../agensi-skill-0.3.0
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
`skill/` is an [Agent Skill](https://agentskills.io/specification) — the
|
|
951
|
+
instructions an agent follows to drive this server — and the marketplace gets
|
|
952
|
+
the same Markdown as a zip with `playwright-e2e-mcp/` at its root.
|
|
953
|
+
`scripts/package-skill.mjs` produces that zip, so the uploaded copy is built
|
|
954
|
+
rather than assembled by hand, and it refuses what the marketplace rejects: a
|
|
955
|
+
missing `SKILL.md`, a non-text file, a symlink, `Thumbs.db`.
|
|
956
|
+
|
|
957
|
+
`skill:package` writes the whole folder a release would otherwise assemble by
|
|
958
|
+
hand — the skill copy, `skill/README.md` and the archive — to
|
|
959
|
+
`../agensi-skill-<version>` (or `--out <dir>`), then verifies it with the same
|
|
960
|
+
comparison `check` uses, so a successful exit means the folder really is the
|
|
961
|
+
skill tree. It is idempotent: a folder that already matches is left untouched.
|
|
962
|
+
A folder that has drifted is refused instead of overwritten, because it may
|
|
963
|
+
already be published — bump `version` for a new package, or pass `--force` to
|
|
964
|
+
replace that one.
|
|
965
|
+
|
|
966
|
+
The archive comes from the same deterministic writer as the `.mcpb`, so the
|
|
967
|
+
same tree always hashes the same, and text payloads are LF-normalised first:
|
|
968
|
+
a CRLF checkout builds the bytes an LF one does. CI runs `skill:check`, generates
|
|
969
|
+
a package folder, and uploads the built zip as an artifact
|
|
970
|
+
(`.github/workflows/ci.yml`).
|
|
971
|
+
|
|
972
|
+
**Every release publishes the package too.** Assembling a release builds it from
|
|
973
|
+
the release tag and attaches it as `<name>-<version>-skill.zip`, and the
|
|
974
|
+
`.github/workflows/release.yml` run downloads that asset back off the draft and
|
|
975
|
+
byte-compares it with the build before anything is published. The marketplace
|
|
976
|
+
upload is that asset, not a folder someone assembled — a deterministic archive
|
|
977
|
+
means "the same build as the tagged release" is an exact statement, and
|
|
978
|
+
`release:verify` checks it again against a rebuild from the tag.
|
|
979
|
+
`.github/workflows/publish-skill-package.yml` stays for the repair path: run it
|
|
980
|
+
by hand to attach or rebuild the package for a release that is missing it.
|
|
981
|
+
|
|
982
|
+
`skill:check` is the other half of that, and the part that matters when a
|
|
983
|
+
package has already been uploaded. It builds twice (the bytes must agree),
|
|
984
|
+
reads the archive back and compares its contents with `skill/` file by file,
|
|
985
|
+
then compares a packaged copy — `../agensi-skill-<version>` by default, or a
|
|
986
|
+
path you pass: the package folder, the bare `playwright-e2e-mcp/` folder, or
|
|
987
|
+
the zip on its own. Comparisons are by content, never by archive bytes, because
|
|
988
|
+
a zip written by a platform `zip` tool carries real timestamps that say nothing
|
|
989
|
+
about whether the Markdown inside it is current. Any file that differs, is
|
|
990
|
+
missing or is extra is named on stderr and the command exits 1 — the repository
|
|
991
|
+
copy and the uploaded copy are the same document, so an edit to either one
|
|
992
|
+
should not be invisible.
|
|
993
|
+
|
|
774
994
|
### Try the example
|
|
775
995
|
|
|
776
996
|
With Playwright installed in your project:
|
package/dist/cli/ci.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `playwright-e2e-mcp ci`: the same diagnosis the MCP tools give an agent,
|
|
3
|
+
* as a plain command for CI. It runs the suite (auto-retry once, so flaky
|
|
4
|
+
* tests are reported as flaky rather than failed), optionally repeats the
|
|
5
|
+
* failures through diagnose-flaky and drafts suggest-fix diffs, and writes
|
|
6
|
+
* one Markdown report: to stdout, to --output, and to the GitHub job
|
|
7
|
+
* summary when GITHUB_STEP_SUMMARY is set.
|
|
8
|
+
*
|
|
9
|
+
* Exit codes: 0 all passed (flaky allowed), 1 tests failed, 2 the run
|
|
10
|
+
* itself failed (no Playwright, config error, timeout) or bad arguments.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CI_USAGE: string;
|
|
13
|
+
export interface CiOptions {
|
|
14
|
+
projectRoot?: string;
|
|
15
|
+
config?: string;
|
|
16
|
+
browser?: 'chromium' | 'firefox' | 'webkit';
|
|
17
|
+
grep?: string;
|
|
18
|
+
workers?: number;
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
retry: boolean;
|
|
21
|
+
flakyRuns?: number;
|
|
22
|
+
suggest: number;
|
|
23
|
+
output?: string;
|
|
24
|
+
testFiles: string[];
|
|
25
|
+
}
|
|
26
|
+
/** Parse `ci` arguments (everything after the subcommand). Throws on bad input. */
|
|
27
|
+
export declare function parseCiArgs(argv: string[]): CiOptions;
|
|
28
|
+
/** Run the CI flow; returns the exit code. */
|
|
29
|
+
export declare function runCi(argv: string[]): Promise<number>;
|
package/dist/cli/ci.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `playwright-e2e-mcp ci`: the same diagnosis the MCP tools give an agent,
|
|
3
|
+
* as a plain command for CI. It runs the suite (auto-retry once, so flaky
|
|
4
|
+
* tests are reported as flaky rather than failed), optionally repeats the
|
|
5
|
+
* failures through diagnose-flaky and drafts suggest-fix diffs, and writes
|
|
6
|
+
* one Markdown report: to stdout, to --output, and to the GitHub job
|
|
7
|
+
* summary when GITHUB_STEP_SUMMARY is set.
|
|
8
|
+
*
|
|
9
|
+
* Exit codes: 0 all passed (flaky allowed), 1 tests failed, 2 the run
|
|
10
|
+
* itself failed (no Playwright, config error, timeout) or bad arguments.
|
|
11
|
+
*/
|
|
12
|
+
import { appendFile, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { parseArgs } from 'node:util';
|
|
14
|
+
import { createLogger } from '../utils/logger.js';
|
|
15
|
+
import { createToolStore } from '../tools/shared.js';
|
|
16
|
+
import { runTestTool } from '../tools/run-test.js';
|
|
17
|
+
import { diagnoseFlakyTool } from '../tools/diagnose-flaky.js';
|
|
18
|
+
import { suggestFixTool } from '../tools/suggest-fix.js';
|
|
19
|
+
export const CI_USAGE = [
|
|
20
|
+
'Usage: playwright-e2e-mcp ci [options] [test files…]',
|
|
21
|
+
'',
|
|
22
|
+
'Runs the Playwright suite and writes a Markdown diagnosis (stdout, --output,',
|
|
23
|
+
'and $GITHUB_STEP_SUMMARY when set). Exit 0 = passed, 1 = tests failed, 2 = run error.',
|
|
24
|
+
'',
|
|
25
|
+
' --project-root <dir> project directory (default: PW_MCP_PROJECT_ROOT or cwd)',
|
|
26
|
+
' --config <path|n> playwright.config path or 1-based index',
|
|
27
|
+
' --browser <name> chromium | firefox | webkit',
|
|
28
|
+
' --grep <regex> only tests whose title matches',
|
|
29
|
+
' --workers <n> Playwright workers',
|
|
30
|
+
' --timeout <ms> wall-clock limit for the run (default 600000)',
|
|
31
|
+
' --no-retry do not auto-retry failures once',
|
|
32
|
+
' --flaky-runs <n> repeat failing tests n times (2-10) for a flaky verdict',
|
|
33
|
+
' --suggest <n> draft suggest-fix diffs for the first n failures (max 5)',
|
|
34
|
+
' --output <file> also write the report to this file (for a PR comment)',
|
|
35
|
+
'',
|
|
36
|
+
].join('\n');
|
|
37
|
+
function intOption(value, name, min, max) {
|
|
38
|
+
if (value === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
const n = Number(value);
|
|
41
|
+
if (!Number.isInteger(n) || n < min || n > max)
|
|
42
|
+
throw new Error(`--${name} must be an integer from ${min} to ${max}`);
|
|
43
|
+
return n;
|
|
44
|
+
}
|
|
45
|
+
/** Parse `ci` arguments (everything after the subcommand). Throws on bad input. */
|
|
46
|
+
export function parseCiArgs(argv) {
|
|
47
|
+
const { values, positionals } = parseArgs({
|
|
48
|
+
args: argv,
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
strict: true,
|
|
51
|
+
options: {
|
|
52
|
+
'project-root': { type: 'string' },
|
|
53
|
+
config: { type: 'string' },
|
|
54
|
+
browser: { type: 'string' },
|
|
55
|
+
grep: { type: 'string' },
|
|
56
|
+
workers: { type: 'string' },
|
|
57
|
+
timeout: { type: 'string' },
|
|
58
|
+
'no-retry': { type: 'boolean' },
|
|
59
|
+
'flaky-runs': { type: 'string' },
|
|
60
|
+
suggest: { type: 'string' },
|
|
61
|
+
output: { type: 'string' },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const browser = values.browser;
|
|
65
|
+
if (browser !== undefined && browser !== 'chromium' && browser !== 'firefox' && browser !== 'webkit') {
|
|
66
|
+
throw new Error('--browser must be chromium, firefox or webkit');
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
projectRoot: values['project-root'],
|
|
70
|
+
config: values.config,
|
|
71
|
+
browser,
|
|
72
|
+
grep: values.grep,
|
|
73
|
+
workers: intOption(values.workers, 'workers', 1, 64),
|
|
74
|
+
timeoutMs: intOption(values.timeout, 'timeout', 1_000, 3_600_000) ?? 600_000,
|
|
75
|
+
retry: values['no-retry'] !== true,
|
|
76
|
+
flakyRuns: intOption(values['flaky-runs'], 'flaky-runs', 2, 10),
|
|
77
|
+
suggest: intOption(values.suggest, 'suggest', 0, 5) ?? 0,
|
|
78
|
+
output: values.output,
|
|
79
|
+
testFiles: positionals,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function text(response) {
|
|
83
|
+
return response.content.map((block) => block.text).join('\n');
|
|
84
|
+
}
|
|
85
|
+
/** Run the CI flow; returns the exit code. */
|
|
86
|
+
export async function runCi(argv) {
|
|
87
|
+
let options;
|
|
88
|
+
try {
|
|
89
|
+
options = parseCiArgs(argv);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
process.stderr.write(`${err.message}\n\n${CI_USAGE}`);
|
|
93
|
+
return 2;
|
|
94
|
+
}
|
|
95
|
+
const projectRoot = options.projectRoot ?? process.env.PW_MCP_PROJECT_ROOT ?? process.cwd();
|
|
96
|
+
const ctx = {
|
|
97
|
+
// Info logs would bury the report in CI output; LOG_LEVEL still wins.
|
|
98
|
+
logger: createLogger({
|
|
99
|
+
level: process.env.LOG_LEVEL || process.env.MCP_LOG_LEVEL ? undefined : 'warn',
|
|
100
|
+
bindings: { name: 'playwright-e2e-mcp', mode: 'ci' },
|
|
101
|
+
}),
|
|
102
|
+
store: createToolStore(),
|
|
103
|
+
projectRoot,
|
|
104
|
+
};
|
|
105
|
+
const sections = [];
|
|
106
|
+
const run = await runTestTool.handler({
|
|
107
|
+
testFiles: options.testFiles.length > 0 ? options.testFiles : undefined,
|
|
108
|
+
grep: options.grep,
|
|
109
|
+
browser: options.browser,
|
|
110
|
+
workers: options.workers,
|
|
111
|
+
timeoutMs: options.timeoutMs,
|
|
112
|
+
retryOnFailure: options.retry,
|
|
113
|
+
config: options.config,
|
|
114
|
+
}, ctx);
|
|
115
|
+
sections.push(text(run));
|
|
116
|
+
const result = ctx.store.lastRun?.result;
|
|
117
|
+
const failures = result?.failures ?? [];
|
|
118
|
+
let code = result === undefined || run.isError ? 2 : result.ok ? 0 : failures.length > 0 ? 1 : 2;
|
|
119
|
+
if (code === 1 && options.suggest > 0) {
|
|
120
|
+
// Before diagnose-flaky, which replaces the last run suggest-fix indexes into.
|
|
121
|
+
const count = Math.min(options.suggest, ctx.store.lastRun?.result.failures.length ?? 0);
|
|
122
|
+
for (let index = 1; index <= count; index += 1) {
|
|
123
|
+
const fix = await suggestFixTool.handler({ index, config: options.config }, ctx);
|
|
124
|
+
sections.push(text(fix).replace(/^## /m, '### '));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (code === 1 && options.flakyRuns) {
|
|
128
|
+
const flaky = await diagnoseFlakyTool.handler({ runs: options.flakyRuns, config: options.config, browser: options.browser }, ctx);
|
|
129
|
+
sections.push(text(flaky).replace(/^## /m, '### '));
|
|
130
|
+
}
|
|
131
|
+
const report = `${sections.join('\n\n---\n\n')}\n\n<sub>Generated by [playwright-e2e-mcp](https://github.com/xsignon-secure/E2E) \`ci\`.</sub>\n`;
|
|
132
|
+
process.stdout.write(report);
|
|
133
|
+
try {
|
|
134
|
+
if (options.output)
|
|
135
|
+
await writeFile(options.output, report, 'utf8');
|
|
136
|
+
if (process.env.GITHUB_STEP_SUMMARY)
|
|
137
|
+
await appendFile(process.env.GITHUB_STEP_SUMMARY, report, 'utf8');
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
process.stderr.write(`could not write the report: ${err.message}\n`);
|
|
141
|
+
code = Math.max(code, 2);
|
|
142
|
+
}
|
|
143
|
+
return code;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=ci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../../src/cli/ci.ts"],"names":[],"mappings":"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"}
|