ptywright 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +459 -116
  2. package/dist/agent.mjs +2 -0
  3. package/dist/bin/ptywright.mjs +6 -0
  4. package/dist/cli-DIUx2w6X.mjs +3587 -0
  5. package/dist/cli.mjs +2 -0
  6. package/{src/index.ts → dist/index.mjs} +7 -9
  7. package/dist/mcp.mjs +2 -0
  8. package/dist/pty-cassette.mjs +24 -0
  9. package/dist/pty_like-Cpkh_O9B.mjs +404 -0
  10. package/dist/runner-DzZlFrt1.mjs +1897 -0
  11. package/dist/runner-zApMYWZx.mjs +3257 -0
  12. package/dist/script.mjs +2 -0
  13. package/dist/server-VHuEWWj_.mjs +3068 -0
  14. package/dist/session.mjs +2 -0
  15. package/dist/terminal_session-DopC7Xg6.mjs +893 -0
  16. package/package.json +28 -21
  17. package/schemas/ptywright-agent-cassette.schema.json +57 -0
  18. package/schemas/ptywright-agent-check.schema.json +122 -0
  19. package/schemas/ptywright-agent-manifest.schema.json +107 -0
  20. package/schemas/ptywright-agent-promote.schema.json +146 -0
  21. package/schemas/ptywright-agent-replay-summary.schema.json +140 -0
  22. package/schemas/ptywright-agent-run.schema.json +126 -0
  23. package/schemas/ptywright-agent.schema.json +182 -0
  24. package/schemas/ptywright-pty-cassette.schema.json +86 -0
  25. package/schemas/ptywright-script-manifest.schema.json +75 -0
  26. package/schemas/ptywright-script-run-summary.schema.json +114 -0
  27. package/schemas/ptywright-script.schema.json +55 -3
  28. package/skills/ptywright-testing/SKILL.md +53 -33
  29. package/bin/ptywright +0 -4
  30. package/src/cli.ts +0 -414
  31. package/src/generator/doc_parser.ts +0 -341
  32. package/src/generator/generate.ts +0 -161
  33. package/src/generator/index.ts +0 -10
  34. package/src/generator/script_generator.ts +0 -209
  35. package/src/generator/step_extractor.ts +0 -397
  36. package/src/mcp/http_server.ts +0 -174
  37. package/src/mcp/script_recording.ts +0 -238
  38. package/src/mcp/server.ts +0 -1348
  39. package/src/pty/bun_pty_adapter.ts +0 -34
  40. package/src/pty/bun_terminal_adapter.ts +0 -149
  41. package/src/pty/pty_adapter.ts +0 -31
  42. package/src/script/dsl.ts +0 -188
  43. package/src/script/module.ts +0 -43
  44. package/src/script/path.ts +0 -151
  45. package/src/script/run.ts +0 -108
  46. package/src/script/run_all.ts +0 -229
  47. package/src/script/runner.ts +0 -983
  48. package/src/script/schema.ts +0 -237
  49. package/src/script/steps/assert_snapshot_equals.ts +0 -21
  50. package/src/script/steps/index.ts +0 -2
  51. package/src/script/suite_report.ts +0 -626
  52. package/src/session/session_manager.ts +0 -145
  53. package/src/session/terminal_session.ts +0 -473
  54. package/src/terminal/ansi.ts +0 -142
  55. package/src/terminal/keys.ts +0 -180
  56. package/src/terminal/mask.ts +0 -70
  57. package/src/terminal/mouse.ts +0 -75
  58. package/src/terminal/snapshot.ts +0 -196
  59. package/src/terminal/style.ts +0 -121
  60. package/src/terminal/view.ts +0 -49
  61. package/src/trace/asciicast.ts +0 -20
  62. package/src/trace/asciinema_player_assets.ts +0 -44
  63. package/src/trace/cast_to_txt.ts +0 -116
  64. package/src/trace/recorder.ts +0 -110
  65. package/src/trace/report.ts +0 -2092
  66. package/src/types.ts +0 -86
  67. package/src/util/hash.ts +0 -8
  68. package/src/util/sleep.ts +0 -5
@@ -3,20 +3,23 @@ name: ptywright-testing
3
3
  description: Terminal/TUI automation and regression testing using ptywright (PTY + xterm) via CLI or MCP tools. Use when you need to (1) drive a CLI/TUI app (send keys/mouse, wait, snapshot), (2) run scripted regressions (run/run-all) and review the HTML report (index.html + run.summary.json), or (3) record an interactive MCP-driven session into a replayable script with golden checkpoints.
4
4
  ---
5
5
 
6
- # Ptywright Testing / 使用指南
6
+ # Ptywright Testing
7
7
 
8
- Use ptywright to run deterministic CLI/TUI regression tests with readable terminal screenshots and a Playwright-like HTML report.
8
+ Use ptywright to run deterministic CLI/TUI regression tests with readable "terminal screenshots" and a Playwright-like HTML report.
9
9
 
10
- ## How to run (发布版 vs 仓库内开发)
10
+ ## Installation & Usage
11
11
 
12
- This CLI is Bun-based (`#!/usr/bin/env bun`). For a released package, prefer `bunx`.
12
+ ```bash
13
+ # 推荐:bunx 一次性运行
14
+ bunx ptywright@latest <command>
13
15
 
14
- - **Published package (recommended):**
15
- - one-off: `bunx ptywright@latest <command>`
16
- - pinned: `bunx ptywright@0.1.0 <command>`
17
- - global: `bun add -g ptywright` then `ptywright <command>`
18
- - **Inside this repo (dev):**
19
- - `bun run bin/ptywright <command>`
16
+ # 或全局安装
17
+ bun add -g ptywright
18
+ ptywright <command>
19
+
20
+ # 本仓库内开发
21
+ bun run bin/ptywright <command>
22
+ ```
20
23
 
21
24
  ## Choose the interface
22
25
 
@@ -25,42 +28,57 @@ This CLI is Bun-based (`#!/usr/bin/env bun`). For a released package, prefer `bu
25
28
 
26
29
  ## Start the MCP server
27
30
 
28
- - stdio (default):
29
- - published: `bunx ptywright@latest mcp`
30
- - repo: `bun run bin/ptywright mcp`
31
- - restrict tools to reduce context:
32
- - published: `bunx ptywright@latest mcp --caps core`
33
- - repo: `bun run bin/ptywright mcp --caps core` (or `PTYWRIGHT_CAPS=core`)
34
- - Streamable HTTP:
35
- - published: `bunx ptywright@latest mcp-http --port 3000`
36
- - repo: `bun run bin/ptywright mcp-http --port 3000`
31
+ ```bash
32
+ # stdio (default)
33
+ bunx ptywright@latest mcp
34
+
35
+ # 精简 tools(降低上下文压力)
36
+ bunx ptywright@latest mcp --caps core
37
+
38
+ # HTTP 模式
39
+ bunx ptywright@latest mcp-http --port 3000
40
+ ```
41
+
42
+ Capabilities (`--caps`): `all|core|debug|script|recording` (comma separated)
37
43
 
38
- Capabilities (`--caps` / `PTYWRIGHT_CAPS`) match MCP tools:
44
+ ## Configure MCP Client
39
45
 
40
- - `all|core|debug|script|recording` (comma/space separated)
46
+ **Claude Desktop / Cursor** (`~/.config/claude/claude_desktop_config.json`):
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "ptywright": {
52
+ "command": "bunx",
53
+ "args": ["ptywright@latest", "mcp"]
54
+ }
55
+ }
56
+ }
57
+ ```
41
58
 
42
59
  ## Run scripts (deterministic regression)
43
60
 
44
61
  ### Run the whole suite (preferred)
45
62
 
46
- - CLI:
47
- - published: `bunx ptywright@latest run-all --dir scripts`
48
- - repo: `bun run bin/ptywright run-all --dir scripts`
49
- - Output to focus on:
50
- - `reportPath` (open in a browser)
51
- - `summaryPath` (`run.summary.json` for agents/CI)
63
+ ```bash
64
+ bunx ptywright@latest run-all --dir scripts
65
+ ```
52
66
 
53
- MCP equivalent:
67
+ Output to focus on:
68
+ - `reportPath` (open in a browser)
69
+ - `summaryPath` (`run.summary.json` for agents/CI)
54
70
 
71
+ MCP equivalent:
55
72
  - `run_all_scripts` (defaults: `dir="scripts"`, suite report in `.tmp/run-all/`)
56
73
  - Keep MCP output small: `run_all_scripts(includeEntries="failures", maxEntries=20)`
57
74
 
58
75
  ### Run one script
59
76
 
60
- - CLI:
61
- - published: `bunx ptywright@latest run <file.json|file.ts> [--artifacts-dir <dir>]`
62
- - repo: `bun run bin/ptywright run <file.json|file.ts> [--artifacts-dir <dir>]`
63
- - MCP: `run_script(scriptPath=...)`
77
+ ```bash
78
+ bunx ptywright@latest run <file.json|file.ts> [--artifacts-dir <dir>]
79
+ ```
80
+
81
+ MCP: `run_script(scriptPath=...)`
64
82
 
65
83
  ## Debug a failure
66
84
 
@@ -85,7 +103,9 @@ Tip: for flaky waits, prefer `scope="buffer"` when the content may have scrolled
85
103
 
86
104
  To verify ptywright MCP tool coverage without relying on external apps/network, run:
87
105
 
88
- - `bun test tests/mcp_all_tools_smoke.test.ts`
106
+ ```bash
107
+ bun test tests/mcp_all_tools_smoke.test.ts
108
+ ```
89
109
 
90
110
  This exercises `core + debug + script + recording` tools end-to-end.
91
111
 
package/bin/ptywright DELETED
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { main } from "../src/cli";
3
-
4
- await main();
package/src/cli.ts DELETED
@@ -1,414 +0,0 @@
1
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
-
3
- import type { PtywrightCapability } from "./mcp/server";
4
- import { createPtywrightServer } from "./mcp/server";
5
- import { startPtywrightHttpServer } from "./mcp/http_server";
6
- import { runAllScripts } from "./script/run_all";
7
- import { runScriptPath } from "./script/path";
8
-
9
- function usage(): string {
10
- return [
11
- "ptywright <command>",
12
- "",
13
- "Commands:",
14
- " mcp Start the MCP server over stdio (default)",
15
- " mcp-http Start the MCP server over Streamable HTTP",
16
- " run <file> Run one script (JSON/TS) and write artifacts",
17
- " run-all [dir] Run all scripts in a directory and write a suite report",
18
- " help Show help",
19
- "",
20
- "Run options:",
21
- " --artifacts-dir <dir> Override artifacts directory",
22
- " --steps <module.ts> Inject custom step handlers",
23
- " --update-goldens Update golden snapshots",
24
- "",
25
- "Run-all options:",
26
- " --dir <dir> Directory to scan (default: scripts)",
27
- " --artifacts-root <dir> Suite artifacts root (default: .tmp/run-all)",
28
- " --steps <module.ts> Inject custom step handlers",
29
- " --update-goldens Update golden snapshots",
30
- "",
31
- "MCP options:",
32
- " --caps <list> Capabilities: all|core|debug|script|recording",
33
- "",
34
- "MCP HTTP options (mcp-http):",
35
- " --host <host> Bind host (default: 127.0.0.1)",
36
- " --port <port> Bind port (default: 3000)",
37
- " --allowed-origins <list> Comma/space separated Origin allowlist",
38
- " --no-cors Disable CORS headers",
39
- ].join("\n");
40
- }
41
-
42
- function isHelp(arg: string | undefined): boolean {
43
- return arg === "-h" || arg === "--help" || arg === "help";
44
- }
45
-
46
- function logLines(lines: Array<string | null | undefined>, stderr: boolean): void {
47
- const filtered = lines.map((l) => l?.trim()).filter(Boolean) as string[];
48
- for (const line of filtered) {
49
- // eslint-disable-next-line no-console
50
- (stderr ? console.error : console.log)(line);
51
- }
52
- }
53
-
54
- function parseCaps(value: string): PtywrightCapability[] {
55
- const parts = value
56
- .split(/[\s,]+/g)
57
- .map((p) => p.trim().toLowerCase())
58
- .filter(Boolean);
59
-
60
- const out: PtywrightCapability[] = [];
61
- for (const p of parts) {
62
- if (p === "all") out.push("all");
63
- else if (p === "core") out.push("core");
64
- else if (p === "debug") out.push("debug");
65
- else if (p === "script" || p === "scripts" || p === "runner" || p === "run") out.push("script");
66
- else if (p === "recording" || p === "record" || p === "rec") out.push("recording");
67
- else throw new Error(`unknown capability: ${p}`);
68
- }
69
- return out;
70
- }
71
-
72
- function parseRunArgs(argv: string[]): {
73
- scriptPath: string;
74
- artifactsDir?: string;
75
- stepsPath?: string;
76
- updateGoldens: boolean;
77
- } {
78
- const out: {
79
- scriptPath?: string;
80
- artifactsDir?: string;
81
- stepsPath?: string;
82
- updateGoldens: boolean;
83
- } = { updateGoldens: false };
84
-
85
- for (let i = 0; i < argv.length; i += 1) {
86
- const arg = argv[i];
87
- const next = argv[i + 1];
88
-
89
- if (!out.scriptPath && arg && !arg.startsWith("-")) {
90
- out.scriptPath = arg;
91
- continue;
92
- }
93
-
94
- if (arg === "--artifacts-dir" && next) {
95
- out.artifactsDir = next;
96
- i += 1;
97
- continue;
98
- }
99
-
100
- if (arg === "--steps" && next) {
101
- out.stepsPath = next;
102
- i += 1;
103
- continue;
104
- }
105
-
106
- if (arg === "--update-goldens") {
107
- out.updateGoldens = true;
108
- continue;
109
- }
110
-
111
- throw new Error(`unknown arg: ${arg ?? ""}`);
112
- }
113
-
114
- if (!out.scriptPath) {
115
- throw new Error("missing <file>\n\n" + usage());
116
- }
117
-
118
- return out as {
119
- scriptPath: string;
120
- artifactsDir?: string;
121
- stepsPath?: string;
122
- updateGoldens: boolean;
123
- };
124
- }
125
-
126
- function parseRunAllArgs(argv: string[]): {
127
- dir?: string;
128
- artifactsRoot?: string;
129
- stepsPath?: string;
130
- updateGoldens: boolean;
131
- } {
132
- const out: {
133
- dir?: string;
134
- artifactsRoot?: string;
135
- stepsPath?: string;
136
- updateGoldens: boolean;
137
- } = { updateGoldens: false };
138
-
139
- for (let i = 0; i < argv.length; i += 1) {
140
- const arg = argv[i];
141
- const next = argv[i + 1];
142
-
143
- if (!out.dir && arg && !arg.startsWith("-")) {
144
- out.dir = arg;
145
- continue;
146
- }
147
-
148
- if (arg === "--dir" && next) {
149
- out.dir = next;
150
- i += 1;
151
- continue;
152
- }
153
-
154
- if (arg === "--artifacts-root" && next) {
155
- out.artifactsRoot = next;
156
- i += 1;
157
- continue;
158
- }
159
-
160
- if (arg === "--steps" && next) {
161
- out.stepsPath = next;
162
- i += 1;
163
- continue;
164
- }
165
-
166
- if (arg === "--update-goldens") {
167
- out.updateGoldens = true;
168
- continue;
169
- }
170
-
171
- throw new Error(`unknown arg: ${arg ?? ""}`);
172
- }
173
-
174
- return out as {
175
- dir?: string;
176
- artifactsRoot?: string;
177
- stepsPath?: string;
178
- updateGoldens: boolean;
179
- };
180
- }
181
-
182
- async function cmdMcp(argv: string[]): Promise<void> {
183
- let capabilities: PtywrightCapability[] | undefined;
184
-
185
- for (let i = 0; i < argv.length; i += 1) {
186
- const arg = argv[i];
187
- const next = argv[i + 1];
188
-
189
- if (isHelp(arg)) {
190
- // eslint-disable-next-line no-console
191
- console.log(usage());
192
- return;
193
- }
194
-
195
- if (arg === "--caps" && next) {
196
- capabilities = parseCaps(next);
197
- i += 1;
198
- continue;
199
- }
200
-
201
- throw new Error(`unknown arg: ${arg ?? ""}`);
202
- }
203
-
204
- const { server, sessions } = createPtywrightServer({ capabilities });
205
- const transport = new StdioServerTransport();
206
- await server.connect(transport);
207
-
208
- function shutdown(): void {
209
- sessions.closeAll();
210
- void server.close();
211
- }
212
-
213
- process.on("SIGINT", shutdown);
214
- process.on("SIGTERM", shutdown);
215
- }
216
-
217
- async function cmdMcpHttp(argv: string[]): Promise<void> {
218
- let capabilities: PtywrightCapability[] | undefined;
219
- let hostname: string | undefined;
220
- let port: number | undefined;
221
- let allowedOrigins: string[] | undefined;
222
- let cors = true;
223
-
224
- for (let i = 0; i < argv.length; i += 1) {
225
- const arg = argv[i];
226
- const next = argv[i + 1];
227
-
228
- if (isHelp(arg)) {
229
- // eslint-disable-next-line no-console
230
- console.log(usage());
231
- return;
232
- }
233
-
234
- if (arg === "--caps" && next) {
235
- capabilities = parseCaps(next);
236
- i += 1;
237
- continue;
238
- }
239
-
240
- if ((arg === "--host" || arg === "--hostname") && next) {
241
- hostname = next;
242
- i += 1;
243
- continue;
244
- }
245
-
246
- if (arg === "--port" && next) {
247
- const value = Number.parseInt(next, 10);
248
- if (!Number.isFinite(value) || value < 0) {
249
- throw new Error(`invalid --port: ${next}`);
250
- }
251
- port = value;
252
- i += 1;
253
- continue;
254
- }
255
-
256
- if (arg === "--allowed-origins" && next) {
257
- allowedOrigins = next
258
- .split(/[\s,]+/g)
259
- .map((v) => v.trim())
260
- .filter(Boolean);
261
- i += 1;
262
- continue;
263
- }
264
-
265
- if (arg === "--no-cors") {
266
- cors = false;
267
- continue;
268
- }
269
-
270
- throw new Error(`unknown arg: ${arg ?? ""}`);
271
- }
272
-
273
- const handle = await startPtywrightHttpServer({
274
- hostname,
275
- port,
276
- capabilities,
277
- allowedOrigins,
278
- cors,
279
- });
280
-
281
- // eslint-disable-next-line no-console
282
- console.log(`listening ${handle.url}`);
283
- // eslint-disable-next-line no-console
284
- console.log(`health http://${handle.hostname}:${handle.port}/health`);
285
-
286
- function shutdown(): void {
287
- void handle.close();
288
- }
289
-
290
- process.on("SIGINT", shutdown);
291
- process.on("SIGTERM", shutdown);
292
- }
293
-
294
- async function cmdRun(argv: string[]): Promise<number> {
295
- const args = parseRunArgs(argv);
296
- const result = await runScriptPath(args.scriptPath, {
297
- artifactsDir: args.artifactsDir,
298
- updateGoldens: args.updateGoldens,
299
- stepsPath: args.stepsPath,
300
- });
301
-
302
- if (!result.ok) {
303
- logLines(
304
- [
305
- result.error,
306
- result.artifactsDir ? `artifacts=${result.artifactsDir}` : null,
307
- result.reportPath ? `report=${result.reportPath}` : null,
308
- result.castPath ? `cast=${result.castPath}` : null,
309
- result.failureArtifacts?.lastViewPath
310
- ? `last=${result.failureArtifacts.lastViewPath}`
311
- : null,
312
- result.failureArtifacts?.errorPath ? `error=${result.failureArtifacts.errorPath}` : null,
313
- ],
314
- true,
315
- );
316
- return 1;
317
- }
318
-
319
- logLines(
320
- [
321
- `ok artifacts=${result.artifactsDir}`,
322
- result.reportPath ? `report=${result.reportPath}` : null,
323
- result.castPath ? `cast=${result.castPath}` : null,
324
- ],
325
- false,
326
- );
327
- return 0;
328
- }
329
-
330
- async function cmdRunAll(argv: string[]): Promise<number> {
331
- const args = parseRunAllArgs(argv);
332
- const result = await runAllScripts({
333
- dir: args.dir,
334
- artifactsRoot: args.artifactsRoot,
335
- stepsPath: args.stepsPath,
336
- updateGoldens: args.updateGoldens,
337
- });
338
-
339
- const failures = result.entries.filter((e) => !e.result.ok);
340
-
341
- if (failures.length === 0) {
342
- // eslint-disable-next-line no-console
343
- console.log(
344
- `ok count=${result.entries.length} dir=${result.dir}\nreport=${result.reportPath}\nsummary=${result.summaryPath}`,
345
- );
346
- return 0;
347
- }
348
-
349
- // eslint-disable-next-line no-console
350
- console.error(
351
- `failed count=${failures.length}/${result.entries.length} dir=${result.dir}\nreport=${result.reportPath}\nsummary=${result.summaryPath}`,
352
- );
353
- for (const f of failures) {
354
- if (f.result.ok) continue;
355
- // eslint-disable-next-line no-console
356
- console.error(`- ${f.filePath}: ${f.result.error}`);
357
- if (f.result.failureArtifacts) {
358
- // eslint-disable-next-line no-console
359
- console.error(` artifacts=${f.result.artifactsDir ?? ""}`);
360
- // eslint-disable-next-line no-console
361
- console.error(` last=${f.result.failureArtifacts.lastViewPath}`);
362
- // eslint-disable-next-line no-console
363
- console.error(` error=${f.result.failureArtifacts.errorPath}`);
364
- }
365
- }
366
- return 1;
367
- }
368
-
369
- export async function main(argv: string[] = process.argv.slice(2)): Promise<void> {
370
- const [command, ...rest] = argv;
371
-
372
- if (!command) {
373
- await cmdMcp([]);
374
- return;
375
- }
376
-
377
- if (isHelp(command)) {
378
- // eslint-disable-next-line no-console
379
- console.log(usage());
380
- return;
381
- }
382
-
383
- if (command === "mcp") {
384
- await cmdMcp(rest);
385
- return;
386
- }
387
-
388
- if (command === "mcp-http") {
389
- await cmdMcpHttp(rest);
390
- return;
391
- }
392
-
393
- if (command === "run") {
394
- process.exitCode = await cmdRun(rest);
395
- return;
396
- }
397
-
398
- if (command === "run-all") {
399
- process.exitCode = await cmdRunAll(rest);
400
- return;
401
- }
402
-
403
- throw new Error(`unknown command: ${command}\n\n` + usage());
404
- }
405
-
406
- if (import.meta.main) {
407
- try {
408
- await main();
409
- } catch (error) {
410
- // eslint-disable-next-line no-console
411
- console.error((error as Error).message);
412
- process.exitCode = 1;
413
- }
414
- }