@sightmap/playwright 0.9.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.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +437 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fullstory, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @sightmap/playwright
|
|
2
|
+
|
|
3
|
+
Sightmap-aware wrapper around `@playwright/cli`. Adds sightmap-aware
|
|
4
|
+
commands (`snapshot`, `match`, `act`, `network`) that consume your
|
|
5
|
+
`.sightmap/` corpus and enrich the agent-facing output with component
|
|
6
|
+
names, view memory, and request annotations. Other commands pass through
|
|
7
|
+
to `@playwright/cli` unchanged.
|
|
8
|
+
|
|
9
|
+
This is the **no-MCP** path: a coding agent invokes `sightmap-playwright`
|
|
10
|
+
the same way it would invoke `playwright`, with no MCP protocol overhead.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pnpm add -D @sightmap/playwright @playwright/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@playwright/cli` is a peer dependency. `sightmap-playwright` shells out
|
|
19
|
+
to it for the browser primitives.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
sightmap-playwright [global flags] <command> [args...]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Global flags:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
-s, --session NAME Named playwright-cli session (default: "default")
|
|
31
|
+
--sightmap-dir DIR Path to .sightmap/ directory (default: ".sightmap")
|
|
32
|
+
--json Emit JSON instead of human-readable text
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Sightmap-specific commands
|
|
36
|
+
|
|
37
|
+
### `snapshot` — enriched a11y snapshot
|
|
38
|
+
|
|
39
|
+
Captures an ARIA snapshot from the current page and annotates it with
|
|
40
|
+
the matched sightmap view, view memory, and the sightmap components
|
|
41
|
+
present on the page (with live `matchCount`).
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
$ sightmap-playwright snapshot
|
|
45
|
+
View: SearchResults (/search)
|
|
46
|
+
memory:
|
|
47
|
+
- paginated; load-more button at bottom
|
|
48
|
+
Components:
|
|
49
|
+
- SearchBar (global) — 1 match
|
|
50
|
+
- ResultCard (view-scoped) — 12 matches
|
|
51
|
+
memory: clicking opens detail in a new tab
|
|
52
|
+
|
|
53
|
+
--- ARIA snapshot ---
|
|
54
|
+
- main:
|
|
55
|
+
- heading "Results for 'sightmap'" [level=1]
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Pass `--json` to get the structured `EnrichedSnapshot` plus raw ARIA text.
|
|
60
|
+
|
|
61
|
+
### `match URL` — show the matching sightmap view
|
|
62
|
+
|
|
63
|
+
Pure-query: resolves a URL (or pathname) against the loaded sightmap and
|
|
64
|
+
prints the matched view, applicable components, and known requests.
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
$ sightmap-playwright match /list/abc123
|
|
68
|
+
View: ListDetailScreen (/list/*)
|
|
69
|
+
memory: id is opaque; do not parse
|
|
70
|
+
Components:
|
|
71
|
+
- BottomTabBar (global)
|
|
72
|
+
- ListCard (view-scoped)
|
|
73
|
+
Requests:
|
|
74
|
+
- GET /api/list/:id → fetchList
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
No browser is launched; this is the same matcher used by `@sightmap/sightmap`
|
|
78
|
+
exposed through the playwright-cli wrapper for convenience.
|
|
79
|
+
|
|
80
|
+
### `act NAME` — resolve a component name to a selector
|
|
81
|
+
|
|
82
|
+
Resolves a sightmap component name to its primary selector (plus any
|
|
83
|
+
fallbacks). Prints the selector to stdout so it can be piped into another
|
|
84
|
+
playwright-cli command.
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
$ sightmap-playwright act SubmitButton
|
|
88
|
+
[data-testid="submit"]
|
|
89
|
+
# fallbacks: button[type="submit"]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Exits non-zero if the component name is unknown. Pass `--json` for the
|
|
93
|
+
full resolution result.
|
|
94
|
+
|
|
95
|
+
### `network` — annotated network requests
|
|
96
|
+
|
|
97
|
+
Pulls recent network requests from the current session and tags any
|
|
98
|
+
that match a `requests:` entry in the sightmap, attaching request memory.
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
$ sightmap-playwright network
|
|
102
|
+
[GET] https://example.test/api/list/abc → 200 [fetchList]
|
|
103
|
+
memory: returns 404 for archived lists; treat as "not found"
|
|
104
|
+
[POST] https://example.test/api/track → 204
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Passthrough
|
|
108
|
+
|
|
109
|
+
Anything that isn't a sightmap-specific command is forwarded to
|
|
110
|
+
`@playwright/cli` verbatim, preserving the session flag:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
sightmap-playwright open https://example.test
|
|
114
|
+
sightmap-playwright close
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This means you can use `sightmap-playwright` as a drop-in for
|
|
118
|
+
`playwright` and only opt into the sightmap-aware behavior on the four
|
|
119
|
+
commands above.
|
|
120
|
+
|
|
121
|
+
## Sessions
|
|
122
|
+
|
|
123
|
+
`@playwright/cli` uses **named sessions** to keep a browser context alive
|
|
124
|
+
across invocations. `sightmap-playwright` honors the same flag:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
sightmap-playwright -s checkout open https://shop.test
|
|
128
|
+
sightmap-playwright -s checkout snapshot
|
|
129
|
+
sightmap-playwright -s checkout network
|
|
130
|
+
sightmap-playwright -s checkout close
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If you don't pass `-s`/`--session`, the session is `"default"`. Run
|
|
134
|
+
parallel flows by giving them distinct session names.
|
|
135
|
+
|
|
136
|
+
## `.sightmap/` resolution
|
|
137
|
+
|
|
138
|
+
Sightmap files are loaded **per invocation** from `--sightmap-dir`
|
|
139
|
+
(default `.sightmap`), resolved against the current working directory.
|
|
140
|
+
Override it if you're invoking from outside the project root:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
sightmap-playwright --sightmap-dir /path/to/repo/.sightmap snapshot
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
There is no daemon and no cache — each command re-reads the corpus, so
|
|
147
|
+
edits to `.sightmap/` are picked up immediately.
|
|
148
|
+
|
|
149
|
+
## See also
|
|
150
|
+
|
|
151
|
+
- [`@sightmap/sightmap`](../sightmap) — the underlying matcher, lint, and
|
|
152
|
+
validate library. Same semantics, no browser.
|
|
153
|
+
- [`@sightmap/mcp`](../mcp) — the MCP-server alternative if your host
|
|
154
|
+
agent speaks MCP.
|
|
155
|
+
- [sightmap.org](https://sightmap.org) — spec and full docs.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/act.ts
|
|
4
|
+
import {
|
|
5
|
+
loadDirectory,
|
|
6
|
+
resolveSightmapAct
|
|
7
|
+
} from "@sightmap/sightmap";
|
|
8
|
+
async function runActCommand(opts) {
|
|
9
|
+
const sightmap = await loadDirectory(opts.sightmapDir);
|
|
10
|
+
const r = resolveSightmapAct(sightmap, { componentName: opts.componentName });
|
|
11
|
+
if (r.kind === "error") {
|
|
12
|
+
process.stderr.write(r.message + "\n");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
process.stdout.write(JSON.stringify(r, null, 2) + "\n");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(`${r.selector}
|
|
21
|
+
`);
|
|
22
|
+
if (r.allSelectors.length > 1) {
|
|
23
|
+
process.stdout.write(`# fallbacks: ${r.allSelectors.slice(1).join(", ")}
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/commands/match.ts
|
|
29
|
+
import { loadDirectory as loadDirectory2, match } from "@sightmap/sightmap";
|
|
30
|
+
async function runMatchCommand(opts) {
|
|
31
|
+
const sightmap = await loadDirectory2(opts.sightmapDir);
|
|
32
|
+
const result = match(sightmap, { url: opts.url });
|
|
33
|
+
if (opts.json) {
|
|
34
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const lines = [];
|
|
38
|
+
if (result.view) {
|
|
39
|
+
lines.push(`View: ${result.view.name} (${result.view.route})`);
|
|
40
|
+
for (const m of result.view.memory ?? []) lines.push(` memory: ${m}`);
|
|
41
|
+
} else {
|
|
42
|
+
lines.push(`View: (no match)`);
|
|
43
|
+
}
|
|
44
|
+
if (result.components.length > 0) {
|
|
45
|
+
lines.push(`Components:`);
|
|
46
|
+
for (const c of result.components) {
|
|
47
|
+
lines.push(` - ${c.name} (${c.scope})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (result.requests.length > 0) {
|
|
51
|
+
lines.push(`Requests:`);
|
|
52
|
+
for (const r of result.requests) {
|
|
53
|
+
lines.push(` - ${r.method ?? "ANY"} ${r.route} \u2192 ${r.name}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/commands/network.ts
|
|
60
|
+
import {
|
|
61
|
+
loadDirectory as loadDirectory3,
|
|
62
|
+
annotateNetworkRequests
|
|
63
|
+
} from "@sightmap/sightmap";
|
|
64
|
+
|
|
65
|
+
// src/exec.ts
|
|
66
|
+
import { spawn } from "child_process";
|
|
67
|
+
async function execPlaywrightCli(args, options = {}) {
|
|
68
|
+
const binary = options.binary ?? "playwright-cli";
|
|
69
|
+
const timeoutMs = options.timeoutMs ?? 6e4;
|
|
70
|
+
return await new Promise((resolve, reject) => {
|
|
71
|
+
const child = spawn(binary, args, {
|
|
72
|
+
cwd: options.cwd ?? process.cwd(),
|
|
73
|
+
env: options.env ?? process.env,
|
|
74
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
75
|
+
});
|
|
76
|
+
const stdoutChunks = [];
|
|
77
|
+
const stderrChunks = [];
|
|
78
|
+
child.stdout.on("data", (c) => stdoutChunks.push(c));
|
|
79
|
+
child.stderr.on("data", (c) => stderrChunks.push(c));
|
|
80
|
+
const killTimer = setTimeout(() => {
|
|
81
|
+
child.kill("SIGTERM");
|
|
82
|
+
}, timeoutMs);
|
|
83
|
+
child.on("error", (err) => {
|
|
84
|
+
clearTimeout(killTimer);
|
|
85
|
+
reject(err);
|
|
86
|
+
});
|
|
87
|
+
child.on("close", (code) => {
|
|
88
|
+
clearTimeout(killTimer);
|
|
89
|
+
resolve({
|
|
90
|
+
exitCode: code ?? -1,
|
|
91
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
92
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8")
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async function streamPlaywrightCli(args, options = {}) {
|
|
98
|
+
const binary = options.binary ?? "playwright-cli";
|
|
99
|
+
return await new Promise((resolve, reject) => {
|
|
100
|
+
const child = spawn(binary, args, {
|
|
101
|
+
cwd: options.cwd ?? process.cwd(),
|
|
102
|
+
env: options.env ?? process.env,
|
|
103
|
+
stdio: "inherit"
|
|
104
|
+
});
|
|
105
|
+
child.on("error", reject);
|
|
106
|
+
child.on("close", (code) => {
|
|
107
|
+
resolve({ exitCode: code ?? -1 });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/parse-network.ts
|
|
113
|
+
function parsePlaywrightCliNetwork(text) {
|
|
114
|
+
const requests = [];
|
|
115
|
+
if (!/^###\s+Result/m.test(text)) return [];
|
|
116
|
+
const lines = text.split(/\r?\n/);
|
|
117
|
+
const re = /^\s*\d+\.\s*\[([A-Z]+)\]\s+(\S+)\s+=>\s+\[(\d+)\]\s*(.*?)\s*$/;
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
const m = re.exec(line);
|
|
120
|
+
if (m === null) continue;
|
|
121
|
+
requests.push({
|
|
122
|
+
method: m[1] ?? "",
|
|
123
|
+
url: m[2] ?? "",
|
|
124
|
+
status: Number.parseInt(m[3] ?? "0", 10),
|
|
125
|
+
statusText: m[4] ?? ""
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return requests;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/commands/network.ts
|
|
132
|
+
async function runNetworkCommand(opts) {
|
|
133
|
+
const sightmap = await loadDirectory3(opts.sightmapDir);
|
|
134
|
+
const result = await execPlaywrightCli([
|
|
135
|
+
"-s",
|
|
136
|
+
opts.sessionName,
|
|
137
|
+
"network",
|
|
138
|
+
"requests"
|
|
139
|
+
]);
|
|
140
|
+
if (result.exitCode !== 0) {
|
|
141
|
+
process.stderr.write(result.stderr);
|
|
142
|
+
process.exit(result.exitCode);
|
|
143
|
+
}
|
|
144
|
+
const parsed = parsePlaywrightCliNetwork(result.stdout);
|
|
145
|
+
const annotated = annotateNetworkRequests(sightmap, parsed);
|
|
146
|
+
if (opts.json) {
|
|
147
|
+
process.stdout.write(JSON.stringify(annotated, null, 2) + "\n");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (const r of annotated) {
|
|
151
|
+
const tag = r.sightmapName ? ` [${r.sightmapName}]` : "";
|
|
152
|
+
process.stdout.write(`[${r.method}] ${r.url} \u2192 ${r.status}${tag}
|
|
153
|
+
`);
|
|
154
|
+
if (r.sightmapMemory) {
|
|
155
|
+
for (const m of r.sightmapMemory) process.stdout.write(` memory: ${m}
|
|
156
|
+
`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/commands/passthrough.ts
|
|
162
|
+
async function runPassthroughCommand(opts) {
|
|
163
|
+
const { exitCode } = await streamPlaywrightCli([
|
|
164
|
+
"-s",
|
|
165
|
+
opts.sessionName,
|
|
166
|
+
...opts.rest
|
|
167
|
+
]);
|
|
168
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/commands/snapshot.ts
|
|
172
|
+
import {
|
|
173
|
+
enrichSnapshot,
|
|
174
|
+
loadDirectory as loadDirectory4,
|
|
175
|
+
match as sightmapMatch
|
|
176
|
+
} from "@sightmap/sightmap";
|
|
177
|
+
|
|
178
|
+
// src/parse-snapshot.ts
|
|
179
|
+
function parsePlaywrightCliSnapshot(text) {
|
|
180
|
+
const lines = text.split(/\r?\n/);
|
|
181
|
+
let url = "";
|
|
182
|
+
let title;
|
|
183
|
+
const nodes = [];
|
|
184
|
+
let inSnapshotSection = false;
|
|
185
|
+
let snapshotBaseIndent = null;
|
|
186
|
+
const pageUrlRe = /^- Page URL:\s*(\S+)/;
|
|
187
|
+
const pageTitleRe = /^- Page Title:\s*(.+?)\s*$/;
|
|
188
|
+
const nodeRe = /^(\s*)-\s+([a-zA-Z][\w-]*)\s*(?:"([^"]*)")?\s*((?:\[[^\]]+\]\s*)*)\s*:?/;
|
|
189
|
+
const refAttrRe = /\[ref=(e\d+)\]/;
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
if (/^###\s+Snapshot/.test(line)) {
|
|
192
|
+
inSnapshotSection = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (/^###\s+Events/.test(line)) {
|
|
196
|
+
inSnapshotSection = false;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (/^###\s+/.test(line)) {
|
|
200
|
+
inSnapshotSection = false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (/^```/.test(line)) continue;
|
|
204
|
+
if (!inSnapshotSection) {
|
|
205
|
+
const u = pageUrlRe.exec(line);
|
|
206
|
+
if (u) {
|
|
207
|
+
url = u[1] ?? "";
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const t = pageTitleRe.exec(line);
|
|
211
|
+
if (t) {
|
|
212
|
+
title = t[1];
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const m = nodeRe.exec(line);
|
|
218
|
+
if (m === null) continue;
|
|
219
|
+
const indent = m[1] ?? "";
|
|
220
|
+
const role = m[2];
|
|
221
|
+
const name = m[3];
|
|
222
|
+
const attrs = m[4] ?? "";
|
|
223
|
+
const refMatch = refAttrRe.exec(attrs);
|
|
224
|
+
const ref = refMatch?.[1];
|
|
225
|
+
if (snapshotBaseIndent === null) snapshotBaseIndent = indent.length;
|
|
226
|
+
const depth = Math.max(
|
|
227
|
+
0,
|
|
228
|
+
Math.floor((indent.length - snapshotBaseIndent) / 2)
|
|
229
|
+
);
|
|
230
|
+
nodes.push({
|
|
231
|
+
depth,
|
|
232
|
+
...ref !== void 0 ? { ref } : {},
|
|
233
|
+
...role !== void 0 ? { role } : {},
|
|
234
|
+
...name !== void 0 ? { name } : {}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
url,
|
|
239
|
+
...title !== void 0 ? { title } : {},
|
|
240
|
+
ariaText: text,
|
|
241
|
+
nodes
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/in-page-runner.ts
|
|
246
|
+
import {
|
|
247
|
+
buildInPageEvalFunction
|
|
248
|
+
} from "@sightmap/sightmap";
|
|
249
|
+
async function runInPageMatcher(components, sessionName, options = {}) {
|
|
250
|
+
if (components.length === 0) return [];
|
|
251
|
+
const js = buildInPageEvalFunction(components);
|
|
252
|
+
const result = await execPlaywrightCli(
|
|
253
|
+
["-s", sessionName, "eval", js],
|
|
254
|
+
options
|
|
255
|
+
);
|
|
256
|
+
if (result.exitCode !== 0) {
|
|
257
|
+
throw new Error(`playwright-cli eval failed: ${result.stderr}`);
|
|
258
|
+
}
|
|
259
|
+
return parseInPageEvalStdout(result.stdout);
|
|
260
|
+
}
|
|
261
|
+
function parseInPageEvalStdout(stdout) {
|
|
262
|
+
const text = stdout.trim();
|
|
263
|
+
if (text.length === 0) return [];
|
|
264
|
+
const afterResult = text.split(/^###\s*Result/im)[1] ?? text;
|
|
265
|
+
const start = afterResult.indexOf("{");
|
|
266
|
+
if (start < 0) return [];
|
|
267
|
+
for (let end = afterResult.length; end > start; end--) {
|
|
268
|
+
try {
|
|
269
|
+
const parsed = JSON.parse(afterResult.slice(start, end));
|
|
270
|
+
if (Array.isArray(parsed.matches)) return parsed.matches;
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/commands/snapshot.ts
|
|
278
|
+
async function runSnapshotCommand(opts) {
|
|
279
|
+
const sightmap = await loadDirectory4(opts.sightmapDir);
|
|
280
|
+
const snapResult = await execPlaywrightCli([
|
|
281
|
+
"-s",
|
|
282
|
+
opts.sessionName,
|
|
283
|
+
"snapshot"
|
|
284
|
+
]);
|
|
285
|
+
if (snapResult.exitCode !== 0) {
|
|
286
|
+
process.stderr.write(snapResult.stderr);
|
|
287
|
+
process.exit(snapResult.exitCode);
|
|
288
|
+
}
|
|
289
|
+
const parsed = parsePlaywrightCliSnapshot(snapResult.stdout);
|
|
290
|
+
const matchResult = sightmapMatch(sightmap, { url: parsed.url });
|
|
291
|
+
const allComponents = matchResult.components.map((c) => ({
|
|
292
|
+
name: c.name,
|
|
293
|
+
selector: c.selector
|
|
294
|
+
}));
|
|
295
|
+
const inPage = await runInPageMatcher(allComponents, opts.sessionName);
|
|
296
|
+
const enriched = enrichSnapshot({
|
|
297
|
+
sightmap,
|
|
298
|
+
currentUrl: parsed.url,
|
|
299
|
+
inPageMatches: inPage
|
|
300
|
+
});
|
|
301
|
+
if (opts.json) {
|
|
302
|
+
process.stdout.write(
|
|
303
|
+
JSON.stringify({ ...enriched, ariaSnapshot: parsed.ariaText }, null, 2) + "\n"
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
process.stdout.write(formatHuman(enriched, parsed.ariaText));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function formatHuman(enriched, ariaText) {
|
|
310
|
+
const lines = [];
|
|
311
|
+
if (enriched.view) {
|
|
312
|
+
lines.push(`View: ${enriched.view.name} (${enriched.view.route})`);
|
|
313
|
+
if (enriched.view.memory.length > 0) {
|
|
314
|
+
lines.push(` memory:`);
|
|
315
|
+
for (const m of enriched.view.memory) lines.push(` - ${m}`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
lines.push(`View: (no match)`);
|
|
319
|
+
}
|
|
320
|
+
if (enriched.components.length > 0) {
|
|
321
|
+
lines.push(`Components:`);
|
|
322
|
+
for (const c of enriched.components) {
|
|
323
|
+
lines.push(
|
|
324
|
+
` - ${c.name} (${c.scope}) \u2014 ${c.matchCount} match${c.matchCount === 1 ? "" : "es"}`
|
|
325
|
+
);
|
|
326
|
+
for (const m of c.memory) lines.push(` memory: ${m}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
lines.push("");
|
|
330
|
+
lines.push("--- ARIA snapshot ---");
|
|
331
|
+
lines.push(ariaText);
|
|
332
|
+
return lines.join("\n");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/cli.ts
|
|
336
|
+
function requireValue(flag, next) {
|
|
337
|
+
if (next === void 0 || next.startsWith("-")) {
|
|
338
|
+
console.error(
|
|
339
|
+
`${flag}: expected a value, got "${next ?? "(end of args)"}"`
|
|
340
|
+
);
|
|
341
|
+
process.exit(2);
|
|
342
|
+
}
|
|
343
|
+
return next;
|
|
344
|
+
}
|
|
345
|
+
function parseArgs(argv) {
|
|
346
|
+
let session = "default";
|
|
347
|
+
let sightmapDir = ".sightmap";
|
|
348
|
+
let json = false;
|
|
349
|
+
const positional = [];
|
|
350
|
+
for (let i = 0; i < argv.length; i++) {
|
|
351
|
+
const a = argv[i];
|
|
352
|
+
if (a === "-s" || a === "--session") {
|
|
353
|
+
session = requireValue(a, argv[++i]);
|
|
354
|
+
} else if (a === "--sightmap-dir") {
|
|
355
|
+
sightmapDir = requireValue(a, argv[++i]);
|
|
356
|
+
} else if (a === "--json") {
|
|
357
|
+
json = true;
|
|
358
|
+
} else {
|
|
359
|
+
positional.push(a);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
command: positional[0] ?? "",
|
|
364
|
+
session,
|
|
365
|
+
sightmapDir,
|
|
366
|
+
json,
|
|
367
|
+
rest: positional.slice(1)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function printHelp() {
|
|
371
|
+
console.log(
|
|
372
|
+
`sightmap-playwright \u2014 sightmap-aware wrapper around @playwright/cli
|
|
373
|
+
|
|
374
|
+
Usage:
|
|
375
|
+
sightmap-playwright [global flags] <command> [args...]
|
|
376
|
+
|
|
377
|
+
Global flags:
|
|
378
|
+
-s, --session NAME Named playwright-cli session (default: "default")
|
|
379
|
+
--sightmap-dir DIR Path to .sightmap/ directory (default: ".sightmap")
|
|
380
|
+
--json Emit JSON instead of human-readable text
|
|
381
|
+
|
|
382
|
+
Sightmap-specific commands:
|
|
383
|
+
snapshot Enriched a11y snapshot with sightmap component names
|
|
384
|
+
match URL Show the matching sightmap view for URL
|
|
385
|
+
act NAME Resolve a sightmap component name to a selector
|
|
386
|
+
network Network requests annotated with sightmap memory
|
|
387
|
+
|
|
388
|
+
All other commands are forwarded to @playwright/cli verbatim.
|
|
389
|
+
`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
async function main() {
|
|
393
|
+
const argv = process.argv.slice(2);
|
|
394
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
395
|
+
printHelp();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const { command, session, sightmapDir, json, rest } = parseArgs(argv);
|
|
399
|
+
switch (command) {
|
|
400
|
+
case "snapshot":
|
|
401
|
+
await runSnapshotCommand({ sessionName: session, sightmapDir, json });
|
|
402
|
+
return;
|
|
403
|
+
case "match": {
|
|
404
|
+
const url = rest[0];
|
|
405
|
+
if (url === void 0) {
|
|
406
|
+
console.error("sightmap-playwright match: URL required");
|
|
407
|
+
process.exit(2);
|
|
408
|
+
}
|
|
409
|
+
await runMatchCommand({ url, sightmapDir, json });
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
case "act": {
|
|
413
|
+
const name = rest[0];
|
|
414
|
+
if (name === void 0) {
|
|
415
|
+
console.error("sightmap-playwright act: component name required");
|
|
416
|
+
process.exit(2);
|
|
417
|
+
}
|
|
418
|
+
await runActCommand({ componentName: name, sightmapDir, json });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
case "network":
|
|
422
|
+
await runNetworkCommand({ sessionName: session, sightmapDir, json });
|
|
423
|
+
return;
|
|
424
|
+
default: {
|
|
425
|
+
const passArgs = json ? ["--json", command, ...rest] : [command, ...rest];
|
|
426
|
+
await runPassthroughCommand({
|
|
427
|
+
sessionName: session,
|
|
428
|
+
rest: passArgs.filter((s) => s.length > 0)
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
main().catch((err) => {
|
|
435
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sightmap/playwright",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Sightmap-aware wrapper around @playwright/cli. Adds sightmap_* commands and enriches snapshot output with component names from .sightmap/.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sightmap/sightmap-js.git",
|
|
9
|
+
"directory": "packages/playwright"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://sightmap.org",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sightmap",
|
|
14
|
+
"playwright",
|
|
15
|
+
"cli",
|
|
16
|
+
"agent",
|
|
17
|
+
"browser"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"sightmap-playwright": "./dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@playwright/cli": "^0.1.13",
|
|
33
|
+
"@types/node": "^20.19.39",
|
|
34
|
+
"tsup": "^8.5.1",
|
|
35
|
+
"typescript": "~5.7.3",
|
|
36
|
+
"vitest": "^3.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@sightmap/sightmap": "^0.9.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@playwright/cli": "^0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@playwright/cli": {
|
|
46
|
+
"optional": false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"typecheck": "tsc --noEmit"
|
|
54
|
+
}
|
|
55
|
+
}
|