@sightmap/playwright 0.9.0 → 0.10.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 (3) hide show
  1. package/README.md +24 -7
  2. package/dist/cli.js +92 -2
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -36,9 +36,21 @@ Global flags:
36
36
 
37
37
  ### `snapshot` — enriched a11y snapshot
38
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`).
39
+ Captures an ARIA snapshot from the current page and **annotates the a11y
40
+ tree itself**: every node that matches a sightmap component gets an inline
41
+ `[sightmap=Name]` token, placed right after the engine's `[ref=eN]` token.
42
+ A header still lists the matched view, view memory, and components (with
43
+ live `matchCount`) as an index.
44
+
45
+ The annotation is **purely additive**:
46
+
47
+ - The engine's native `[ref=eN]` tokens are preserved verbatim — the
48
+ agent's click/navigation and sightmap-bootstrap paths are untouched.
49
+ - Unmatched nodes are byte-identical to the un-enriched tool, so a page
50
+ with zero sightmap coverage looks exactly as before.
51
+ - Multiple component matches on one node are comma-joined
52
+ (`[sightmap=PrimaryCTA,Checkout.SubmitButton]`); view-scoped components
53
+ are slash-qualified (`[sightmap=Checkout/PaymentForm]`).
42
54
 
43
55
  ```text
44
56
  $ sightmap-playwright snapshot
@@ -51,12 +63,17 @@ Components:
51
63
  memory: clicking opens detail in a new tab
52
64
 
53
65
  --- ARIA snapshot ---
54
- - main:
55
- - heading "Results for 'sightmap'" [level=1]
56
- ...
66
+ - main [ref=e1]:
67
+ - heading "Results for 'sightmap'" [level=1] [ref=e2]
68
+ - searchbox "Search" [ref=e3] [sightmap=SearchBar]
69
+ - listitem [ref=e7] [sightmap=ResultCard]:
70
+ - link "First result" [ref=e8]
57
71
  ```
58
72
 
59
- Pass `--json` to get the structured `EnrichedSnapshot` plus raw ARIA text.
73
+ Annotation requires a Chromium-backed session; non-Chromium engines (no
74
+ CDP) degrade gracefully to the un-annotated tree. Pass `--json` to get the
75
+ structured `EnrichedSnapshot` (now including `enrichedAriaText`) plus the
76
+ raw ARIA text under `ariaSnapshot` for back-compat.
60
77
 
61
78
  ### `match URL` — show the matching sightmap view
62
79
 
package/dist/cli.js CHANGED
@@ -274,6 +274,71 @@ function parseInPageEvalStdout(stdout) {
274
274
  return [];
275
275
  }
276
276
 
277
+ // src/ref-join.ts
278
+ function buildRefJoinSnippet(components, refs) {
279
+ const componentsJson = JSON.stringify(components);
280
+ const refsJson = JSON.stringify(refs);
281
+ return `async (page) => {
282
+ const components = ${componentsJson};
283
+ const refs = ${refsJson};
284
+ await page.evaluate((components) => {
285
+ for (const c of components) {
286
+ for (const sel of (c.selector || [])) {
287
+ let els;
288
+ try { els = document.querySelectorAll(sel); } catch { continue; }
289
+ for (const el of Array.from(els)) {
290
+ let names = [];
291
+ try { names = JSON.parse(el.getAttribute("data-sm-mark") || "[]"); } catch { names = []; }
292
+ if (!names.includes(c.name)) names.push(c.name);
293
+ el.setAttribute("data-sm-mark", JSON.stringify(names));
294
+ }
295
+ }
296
+ }
297
+ }, components);
298
+ const refAnnotations = {};
299
+ await Promise.all(refs.map(async (ref) => {
300
+ try {
301
+ const mark = await page.locator("aria-ref=" + ref).evaluate((el) => el.getAttribute("data-sm-mark"));
302
+ if (mark) {
303
+ const names = JSON.parse(mark);
304
+ if (Array.isArray(names) && names.length > 0) refAnnotations[ref] = names;
305
+ }
306
+ } catch { /* ref no longer resolvable \u2014 skip */ }
307
+ }));
308
+ try {
309
+ await page.evaluate(() => {
310
+ for (const el of Array.from(document.querySelectorAll("[data-sm-mark]"))) {
311
+ el.removeAttribute("data-sm-mark");
312
+ }
313
+ });
314
+ } catch { /* best-effort cleanup */ }
315
+ return { refAnnotations };
316
+ }`;
317
+ }
318
+ function parseRefJoinOutput(stdout) {
319
+ const empty = /* @__PURE__ */ new Map();
320
+ const text = stdout.trim();
321
+ if (text.length === 0) return empty;
322
+ const afterResult = text.split(/^###\s*Result/im)[1] ?? text;
323
+ const start = afterResult.indexOf("{");
324
+ if (start < 0) return empty;
325
+ for (let end = afterResult.length; end > start; end--) {
326
+ try {
327
+ const parsed = JSON.parse(afterResult.slice(start, end));
328
+ const ann = parsed.refAnnotations;
329
+ if (ann && typeof ann === "object") {
330
+ const m = /* @__PURE__ */ new Map();
331
+ for (const [ref, names] of Object.entries(ann)) {
332
+ if (Array.isArray(names) && names.length > 0) m.set(ref, names);
333
+ }
334
+ return m;
335
+ }
336
+ } catch {
337
+ }
338
+ }
339
+ return empty;
340
+ }
341
+
277
342
  // src/commands/snapshot.ts
278
343
  async function runSnapshotCommand(opts) {
279
344
  const sightmap = await loadDirectory4(opts.sightmapDir);
@@ -293,18 +358,43 @@ async function runSnapshotCommand(opts) {
293
358
  selector: c.selector
294
359
  }));
295
360
  const inPage = await runInPageMatcher(allComponents, opts.sessionName);
361
+ const refs = parsed.nodes.map((n) => n.ref).filter((r) => r !== void 0);
362
+ const refAnnotations = await resolveRefAnnotations(
363
+ allComponents,
364
+ refs,
365
+ opts.sessionName
366
+ );
296
367
  const enriched = enrichSnapshot({
297
368
  sightmap,
298
369
  currentUrl: parsed.url,
299
- inPageMatches: inPage
370
+ inPageMatches: inPage,
371
+ ariaText: parsed.ariaText,
372
+ refAnnotations
300
373
  });
374
+ const ariaForHuman = enriched.enrichedAriaText ?? parsed.ariaText;
301
375
  if (opts.json) {
302
376
  process.stdout.write(
303
377
  JSON.stringify({ ...enriched, ariaSnapshot: parsed.ariaText }, null, 2) + "\n"
304
378
  );
305
379
  } else {
306
- process.stdout.write(formatHuman(enriched, parsed.ariaText));
380
+ process.stdout.write(formatHuman(enriched, ariaForHuman));
381
+ }
382
+ }
383
+ async function resolveRefAnnotations(components, refs, sessionName) {
384
+ if (components.length === 0 || refs.length === 0) {
385
+ return /* @__PURE__ */ new Map();
386
+ }
387
+ const snippet = buildRefJoinSnippet(components, refs);
388
+ const result = await execPlaywrightCli([
389
+ "-s",
390
+ sessionName,
391
+ "run-code",
392
+ snippet
393
+ ]);
394
+ if (result.exitCode !== 0) {
395
+ return /* @__PURE__ */ new Map();
307
396
  }
397
+ return parseRefJoinOutput(result.stdout);
308
398
  }
309
399
  function formatHuman(enriched, ariaText) {
310
400
  const lines = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sightmap/playwright",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Sightmap-aware wrapper around @playwright/cli. Adds sightmap_* commands and enriches snapshot output with component names from .sightmap/.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,7 +36,7 @@
36
36
  "vitest": "^3.0.0"
37
37
  },
38
38
  "dependencies": {
39
- "@sightmap/sightmap": "^0.9.0"
39
+ "@sightmap/sightmap": "^0.10.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@playwright/cli": "^0.1.0"