@unotest/web 0.5.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.
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Discriminator for SemanticRegion. Fixed set of bucket kinds that downstream
3
+ * consumers (projection, assertion-suggester, etc.) can filter by.
4
+ * Zone keys in `regions` remain human-readable labels — use `kind` for logic.
5
+ */
6
+ type SemanticRegionKind = 'navigation' | 'search' | 'form' | 'main' | 'aside' | 'header' | 'footer' | 'dialog' | 'cmp' | 'overlay' | 'recommendations' | 'promo' | 'categoryChips' | 'table' | 'unknown';
7
+ interface CompactAction {
8
+ tag?: string;
9
+ type?: string;
10
+ text?: string;
11
+ qa?: string;
12
+ testId?: string;
13
+ selector?: string;
14
+ role?: string;
15
+ href?: string;
16
+ /** Heuristic stability of the selector: high = id/testid/qa, medium = aria-label/name, low = fallback. */
17
+ stability?: 'high' | 'medium' | 'low';
18
+ /**
19
+ * True when disambiguation ran but couldn't make the selector unique (still
20
+ * matches >1 live elements). Downstream consumers can use this as a signal
21
+ * that strict-mode will fail — record as-is in DSL, do NOT mask with .first().
22
+ */
23
+ ambiguous?: boolean;
24
+ /** Name of the strategy that successfully refined to unique. Undefined if initial was unique or disambiguation failed. */
25
+ disambiguatedBy?: 'ancestor-anchor' | 'has-text' | 'slot-filter';
26
+ /** Bounding rectangle in viewport CSS pixels at capture time. Outline
27
+ * rendering uses this to partition on-screen vs off-screen actions; it
28
+ * is dropped from the rendered outline (only `clipped` survives there). */
29
+ bounds?: {
30
+ x: number;
31
+ y: number;
32
+ w: number;
33
+ h: number;
34
+ };
35
+ /** True when bbox intersects a viewport edge (partially visible).
36
+ * Distinct from off-screen: clipped means the element IS in the
37
+ * viewport but cut by an edge. */
38
+ clipped?: boolean;
39
+ }
40
+ interface CompactPattern {
41
+ pattern: string;
42
+ selectorPattern?: string;
43
+ count: number;
44
+ samples?: string[];
45
+ }
46
+ interface TemplateField {
47
+ tag: string;
48
+ selector?: string;
49
+ text?: string;
50
+ ariaLabel?: string;
51
+ role?: string;
52
+ type?: string;
53
+ interactive: boolean;
54
+ optional: boolean;
55
+ sampleValues?: string[];
56
+ }
57
+ interface Collection {
58
+ count: number;
59
+ template: TemplateField[];
60
+ /** First N item texts (title/textContent of anchor element). Index = position in DOM. */
61
+ itemTexts?: string[];
62
+ }
63
+ /**
64
+ * Summary of a <table> / role=grid / role=table landmark.
65
+ * Populated when the algorithm encounters one inside a region.
66
+ */
67
+ interface TableSummary {
68
+ selector: string;
69
+ columnCount: number;
70
+ rowCount: number;
71
+ headers: string[];
72
+ }
73
+ interface SemanticRegion {
74
+ /** Discriminator — fixed enum, consumers filter regions by this. */
75
+ kind: SemanticRegionKind;
76
+ /** Human-readable label (heading or landmark tag). Display-only. */
77
+ label?: string;
78
+ total: number;
79
+ actions?: CompactAction[];
80
+ collections?: Collection[];
81
+ patterns?: CompactPattern[];
82
+ tables?: TableSummary[];
83
+ other?: number;
84
+ }
85
+ interface SemanticDomStats {
86
+ totalElements: number;
87
+ filteredElements: number;
88
+ chars: number;
89
+ tokens: number;
90
+ }
91
+ interface SemanticDomDiagnostics {
92
+ collections: number;
93
+ shadowHosts: number;
94
+ shadowElements: number;
95
+ fuzzyMatched: number;
96
+ totalDomElements: number;
97
+ visibleDomElements: number;
98
+ interestingElements: number;
99
+ customElements: number;
100
+ pageTitle: string;
101
+ bodyChildren: number;
102
+ bodyVisible: boolean;
103
+ walkVisits: number;
104
+ actionableHosts: number;
105
+ suppressedByHost: number;
106
+ /** Warnings emitted by the algorithm (e.g. "unclassified-heavy" when unknown region >10%). */
107
+ warnings?: string[];
108
+ }
109
+ interface SemanticDomResult {
110
+ text: string;
111
+ rawText?: string;
112
+ regions: Record<string, SemanticRegion>;
113
+ stats: SemanticDomStats;
114
+ diagnostics: SemanticDomDiagnostics;
115
+ /** Viewport size at capture time (window.innerWidth / innerHeight).
116
+ * Outline projection uses this together with CompactAction.bounds to
117
+ * decide on-screen vs off-screen for each action. */
118
+ viewport?: {
119
+ width: number;
120
+ height: number;
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Semantic DOM Snapshot algorithm v2.
126
+ * Self-contained function for page.evaluate() — all logic runs in browser context.
127
+ *
128
+ * v2 changes (TES-450 follow-up, semantic-dom-v2-cleanup-plan):
129
+ * - Regions now carry a `kind: SemanticRegionKind` discriminator (R1).
130
+ * - Extended LANDMARK_SELECTOR: section[aria-label], article, dialog, role=region,
131
+ * role=dialog, role=alertdialog, role=complementary (P5 Layer 1).
132
+ * - Synthetic region detectors: cmp (OneTrust/Cookiebot/Didomi/Usercentrics/TrustArc),
133
+ * overlay, recommendations, promo, categoryChips (P5 Layer 2, P4).
134
+ * - Anti-fragmentation guardrails: min-3 interactives per region (form/search exempt),
135
+ * diagnostics warning when unknown > 10% (P5 Layer 4).
136
+ * - Navigation regions emit each link as a separate action — no pattern collapse (P1).
137
+ * - Pattern key builder uses href/testid/qa prefixes, never bare tag (P2).
138
+ * - Stable selector fallback: text-locator `role=button[name="X"]`, never single-tag (P3).
139
+ * - CompactAction carries `stability: 'high'|'medium'|'low'` (P3, R2 requirement).
140
+ */
141
+
142
+ interface BrowserSemanticDomOptions {
143
+ disambiguate?: boolean;
144
+ }
145
+ declare function semanticDomAlgorithm(options?: BrowserSemanticDomOptions): SemanticDomResult;
146
+
147
+ /** Position of a region's bounding box relative to the current viewport.
148
+ * Omitted on `OutlineRegion` when the region sits entirely inside the
149
+ * viewport (i.e. `viewport === undefined` means "in view"). */
150
+ type RegionViewport = 'above' | 'below' | 'partial';
151
+ /** Accessibility state captured per interactive element. Only `true`/`mixed`
152
+ * values are populated — `false` is the default and omitted by both
153
+ * capture and render to keep the outline compact. Renderer emits flags
154
+ * in a fixed order (disabled → checked → pressed → expanded → selected →
155
+ * active → level) so diffs across snapshots stay stable. */
156
+ interface OutlineActionState {
157
+ disabled?: true;
158
+ checked?: true | 'mixed';
159
+ pressed?: true | 'mixed';
160
+ expanded?: true;
161
+ selected?: true;
162
+ active?: true;
163
+ level?: number;
164
+ }
165
+ interface OutlineAction {
166
+ /** ARIA role or tag if no role attached (`button` / `link` / `textbox` / `heading` / …).
167
+ * Omitted by the renderer when generic + name present (compaction
168
+ * rule D from the mobile-side outline grammar). */
169
+ role?: string;
170
+ /** Visible text or aria-label. The single scalar the agent matches
171
+ * against in selector synthesis. */
172
+ text?: string;
173
+ /** Stable selector hint — qa attribute or data-testid (#-prefixed in
174
+ * the rendered line). */
175
+ testId?: string;
176
+ /** A stable app-authored `data-*` identifier (data-id / data-guid / …) —
177
+ * the durable handle on grid rows / custom widgets that carry no role+name.
178
+ * Rendered as `[data-x=v]`; the resolver can also address by it. */
179
+ dataId?: {
180
+ name: string;
181
+ value: string;
182
+ };
183
+ /** `title` / `aria-label` tooltip text when it ADDS information beyond the
184
+ * accessible name — the action label of an icon ("Скопировать") or the full
185
+ * value behind a truncated cell. Rendered as `title="…"`. */
186
+ title?: string;
187
+ /** Set when the element isn't a native/ARIA control but is clickable by
188
+ * affordance (cursor:pointer / `hover:` class / onclick) — e.g. a datagrid
189
+ * row. Rendered as `[clickable]` so the agent knows it can act on it. */
190
+ clickable?: true;
191
+ /** Shortened href fallback for `<a>` elements without an accessible
192
+ * name. Carried on every link for tooling; renderer emits it ONLY
193
+ * when `text` is absent (token economy — agents address links by
194
+ * name when one exists). */
195
+ href?: string;
196
+ /** Accessibility state flags — see OutlineActionState. */
197
+ state?: OutlineActionState;
198
+ /** Partially clipped by a viewport edge. Off-screen actions never
199
+ * carry this flag (off-screen ⇒ not in viewport at all). */
200
+ clipped?: boolean;
201
+ /** Opaque per-snapshot identifier of the form `e\d+`. Assigned by
202
+ * capture (Commit 2 H); referenced by the wire-only LocatorStep
203
+ * `{kind:'ref', ref}` form. Persistent .test.js scenarios cannot
204
+ * use refs — they are stale-by-design across snapshots. */
205
+ ref?: string;
206
+ /** Bounding rect at capture time (viewport-relative integer px).
207
+ * Used by tooling (coverage measurement, golden-test fixture
208
+ * comparison); deliberately omitted by `renderOutline` to keep the
209
+ * rendered string token-economical. */
210
+ bounds?: {
211
+ x: number;
212
+ y: number;
213
+ w: number;
214
+ h: number;
215
+ };
216
+ }
217
+ interface OutlineRegion {
218
+ /** Landmark role: `banner` / `navigation` / `main` / `complementary` /
219
+ * `contentinfo` / `form` / `search` / `region` / `dialog` /
220
+ * `alertdialog` / `article`. The synthetic catch-all bucket for
221
+ * elements without a landmark ancestor is `body`. */
222
+ kind: string;
223
+ /** Optional human-readable label (landmark aria-label, fallback to
224
+ * the region's first heading). */
225
+ label?: string;
226
+ /** Position relative to the current viewport. Omitted when the
227
+ * region's bounding box is entirely inside the viewport. */
228
+ viewport?: RegionViewport;
229
+ actions: OutlineAction[];
230
+ }
231
+ interface OutlineTree {
232
+ regions: OutlineRegion[];
233
+ _meta: {
234
+ totalActions: number;
235
+ totalRegions: number;
236
+ /** Per-viewport region counts. `inView` covers regions with no
237
+ * viewport tag (fully visible). Zero entries are still emitted so
238
+ * the shape is stable across snapshots. */
239
+ regionCounts: {
240
+ inView: number;
241
+ above: number;
242
+ below: number;
243
+ partial: number;
244
+ };
245
+ viewport: {
246
+ width: number;
247
+ height: number;
248
+ };
249
+ mode: 'outline';
250
+ };
251
+ }
252
+
253
+ declare function outlineAlgorithm(assignRefs: boolean): OutlineTree;
254
+
255
+ /** State flags carried per ARIA node. Fixed render order in
256
+ * `renderAriaYaml` keeps diffs stable. */
257
+ interface AriaNodeState {
258
+ /** Heading level (1..6) or aria-level. Rendered as `[level=N]`. */
259
+ level?: number;
260
+ disabled?: true;
261
+ checked?: true | "mixed";
262
+ pressed?: true | "mixed";
263
+ expanded?: true;
264
+ selected?: true;
265
+ /** Element has keyboard focus at capture time. Rendered as `[active]`. */
266
+ active?: true;
267
+ }
268
+ /** A node in the ARIA snapshot tree. */
269
+ interface AriaNode {
270
+ /** ARIA role — explicit `role=` attribute (first token) when present,
271
+ * else implicit role from the host language mapping. `"text"` is a
272
+ * pseudo-role for free text leaves. */
273
+ role: string;
274
+ /** Accessible name. Empty string when the node has no name; renderer
275
+ * omits the trailing `"..."` block in that case. */
276
+ name: string;
277
+ /** State flags. Omitted entirely when no flags are set. */
278
+ state?: AriaNodeState;
279
+ /** Stable `eN` / `f<N>e<M>` ref shared with outline. Omitted for
280
+ * free text leaves and other ref-less nodes. */
281
+ ref?: string;
282
+ /** Shortened href for links. Rendered as `/url: <value>`. */
283
+ url?: string;
284
+ /** Child nodes in DOM order. */
285
+ children: AriaNode[];
286
+ }
287
+ /** Top-level snapshot tree. */
288
+ interface AriaTree {
289
+ /** Root children (no synthetic body wrapper — matches Playwright). */
290
+ nodes: AriaNode[];
291
+ _meta: {
292
+ /** Total nodes in the tree (excluding the implicit root). */
293
+ totalNodes: number;
294
+ /** Total refs assigned in this snapshot. */
295
+ totalRefs: number;
296
+ viewport: {
297
+ width: number;
298
+ height: number;
299
+ };
300
+ mode: "aria";
301
+ };
302
+ }
303
+
304
+ declare function ariaSnapshotAlgorithm(): AriaTree;
305
+
306
+ declare function serializeRuntimeDom(): string;
307
+
308
+ interface FindElementMatch {
309
+ /** Shared with outline / aria-snapshot via `data-unotest-ref`.
310
+ * Composite refs for matches inside same-origin iframes are NOT
311
+ * produced here — `find_element` operates on the top document only;
312
+ * use `enter_frame` then `find_element` again for iframe content. */
313
+ ref: string;
314
+ /** Computed ARIA role of the match. */
315
+ role: string;
316
+ /** Accessible name (W3C accname, trimmed/capped per snapshot conventions). */
317
+ name: string;
318
+ /** Element index — assigned when many elements match the same query.
319
+ * Stable within a single `find_element` response so the agent can
320
+ * re-query with `nth: K`. */
321
+ index: number;
322
+ /** Closest named ancestors, root-most first. Lets the agent see
323
+ * "this row lives inside table inside main" without re-running a
324
+ * full snapshot. Each entry is `{ role, name, ref }`; unnamed
325
+ * ancestors are skipped. Capped at 4 entries — deeper structure
326
+ * rarely informs disambiguation. */
327
+ ancestors: Array<{
328
+ role: string;
329
+ name: string;
330
+ ref?: string;
331
+ }>;
332
+ }
333
+ interface FindElementResult {
334
+ /** All matches; first `nth` (default 20) returned. */
335
+ matches: FindElementMatch[];
336
+ /** True count of matches before truncation. */
337
+ totalCount: number;
338
+ /** Echoed for cache-bust + diagnostics. */
339
+ query: {
340
+ role: string;
341
+ name?: string;
342
+ near?: string;
343
+ nth?: number;
344
+ };
345
+ }
346
+
347
+ declare function findElementAlgorithm(args: {
348
+ role: string;
349
+ name?: string;
350
+ near?: string;
351
+ nth?: number;
352
+ limit: number;
353
+ }): FindElementResult;
354
+
355
+ /** Raw element hint read off a live DOM element (mapped to `ElementHint`
356
+ * by the caller). Shape mirrors the fields the RefResolver consumes. */
357
+ interface ElementHintRaw {
358
+ testId?: string;
359
+ role?: string;
360
+ name?: string;
361
+ ariaLabel?: string;
362
+ placeholder?: string;
363
+ alt?: string;
364
+ titleAttr?: string;
365
+ text?: string;
366
+ href?: string;
367
+ elementId?: string;
368
+ nameAttr?: string;
369
+ dataAttr?: {
370
+ name: string;
371
+ value: string;
372
+ };
373
+ tooltipAttr?: {
374
+ name: string;
375
+ value: string;
376
+ };
377
+ ancestors?: Array<{
378
+ testId?: string;
379
+ dataAttr?: {
380
+ name: string;
381
+ value: string;
382
+ };
383
+ elementId?: string;
384
+ className?: string;
385
+ }>;
386
+ }
387
+ /** Set an input/textarea value via the native setter + fire input/change. */
388
+ declare function pasteValue(el: Element, pasted: string): void;
389
+ /** Live outerHTML of an element. */
390
+ declare function readOuterHtml(el: Element): string;
391
+ /** Read every identifying attribute off a live element (role inferred from
392
+ * tag + ARIA, accname approximated). Returns a structured raw hint. */
393
+ declare function inspectElementRaw(el: Element): ElementHintRaw;
394
+
395
+ export { type ElementHintRaw, ariaSnapshotAlgorithm, findElementAlgorithm, inspectElementRaw, outlineAlgorithm, pasteValue, readOuterHtml, semanticDomAlgorithm, serializeRuntimeDom };