@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.
- package/.claude/skills/write-e2e-test.md +551 -0
- package/CHANGELOG.md +158 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/unotest-web.js +197 -0
- package/dist/config/schema.d.ts +356 -0
- package/dist/config/schema.js +1 -0
- package/dist/driver/index.d.ts +74 -0
- package/dist/driver/index.js +1 -0
- package/dist/dsl/index.d.ts +338 -0
- package/dist/dsl/index.js +1 -0
- package/dist/inspection/page-inject.d.ts +395 -0
- package/dist/inspection/page-inject.js +3533 -0
- package/dist/interfaces-iTfjd1zT.d.ts +407 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +1 -0
- package/dist/runner/cli.d.ts +22 -0
- package/dist/runner/cli.js +1 -0
- package/dist/runner/init.d.ts +6 -0
- package/dist/runner/init.js +1 -0
- package/dist/runner/install-chromium.d.ts +9 -0
- package/dist/runner/install-chromium.js +1 -0
- package/dist/runner/prepare-fix.d.ts +8 -0
- package/dist/runner/prepare-fix.js +1 -0
- package/dist/runner/serve-fixture.d.ts +2 -0
- package/dist/runner/serve-fixture.js +1 -0
- package/dist/runner/web-runner-adapter.d.ts +8 -0
- package/dist/runner/web-runner-adapter.js +1 -0
- package/examples/fixtures/break-fix-app/README.md +45 -0
- package/examples/fixtures/break-fix-app/index.html +101 -0
- package/examples/fixtures/break-fix-app/serve.mjs +95 -0
- package/package.json +115 -0
- package/src/mcp/prompts/agent-test-author.md +208 -0
|
@@ -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 };
|