@applitools/eyes-results 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.
- package/CHANGELOG.md +8 -0
- package/LICENSE +26 -0
- package/dist/api/api.js +17 -0
- package/dist/api/compare-step.js +20 -0
- package/dist/api/diff-fetch.js +13 -0
- package/dist/api/dom-diff.js +139 -0
- package/dist/api/dom-fetch.js +39 -0
- package/dist/api/find-regions.js +24 -0
- package/dist/api/fs-cache.js +19 -0
- package/dist/api/gzip.js +12 -0
- package/dist/api/image-fetch.js +31 -0
- package/dist/api/polling.js +117 -0
- package/dist/api/req-results.js +77 -0
- package/dist/api/same-diff-as.js +25 -0
- package/dist/api/selector-mapping.js +15 -0
- package/dist/api/session-fetch.js +35 -0
- package/dist/api/session-history-fetch.js +73 -0
- package/dist/api/session-updates.js +37 -0
- package/dist/api/tasks.js +48 -0
- package/dist/api/url-parsing.js +33 -0
- package/dist/common/common.js +2 -0
- package/dist/common/diff-rects.js +64 -0
- package/dist/common/region-mapping.js +50 -0
- package/dist/geometry/geometry.js +72 -0
- package/dist/index.js +14 -0
- package/dist/lib/api-key-provider.js +26 -0
- package/dist/lib/env.js +26 -0
- package/dist/lib/phase-tracker.js +223 -0
- package/dist/sessions/batch.js +98 -0
- package/dist/sessions/diffs.js +210 -0
- package/dist/sessions/dom-history.js +107 -0
- package/dist/sessions/dom-search.js +1150 -0
- package/dist/sessions/dom.js +513 -0
- package/dist/sessions/match.js +56 -0
- package/dist/sessions/regions.js +280 -0
- package/dist/sessions/session.js +115 -0
- package/llm-docs/dom-search.md +164 -0
- package/package.json +69 -0
- package/types/api/api.d.ts +17 -0
- package/types/api/compare-step.d.ts +52 -0
- package/types/api/diff-fetch.d.ts +21 -0
- package/types/api/dom-diff.d.ts +53 -0
- package/types/api/dom-fetch.d.ts +29 -0
- package/types/api/find-regions.d.ts +26 -0
- package/types/api/fs-cache.d.ts +12 -0
- package/types/api/gzip.d.ts +5 -0
- package/types/api/image-fetch.d.ts +29 -0
- package/types/api/polling.d.ts +41 -0
- package/types/api/req-results.d.ts +37 -0
- package/types/api/same-diff-as.d.ts +38 -0
- package/types/api/selector-mapping.d.ts +28 -0
- package/types/api/session-fetch.d.ts +34 -0
- package/types/api/session-history-fetch.d.ts +56 -0
- package/types/api/session-updates.d.ts +26 -0
- package/types/api/tasks.d.ts +33 -0
- package/types/api/url-parsing.d.ts +20 -0
- package/types/common/common.d.ts +2 -0
- package/types/common/diff-rects.d.ts +39 -0
- package/types/common/region-mapping.d.ts +26 -0
- package/types/geometry/geometry.d.ts +24 -0
- package/types/index.d.ts +14 -0
- package/types/lib/api-key-provider.d.ts +31 -0
- package/types/lib/env.d.ts +14 -0
- package/types/lib/phase-tracker.d.ts +146 -0
- package/types/sessions/batch.d.ts +67 -0
- package/types/sessions/diffs.d.ts +26 -0
- package/types/sessions/dom-history.d.ts +119 -0
- package/types/sessions/dom-search.d.ts +113 -0
- package/types/sessions/dom.d.ts +263 -0
- package/types/sessions/match.d.ts +72 -0
- package/types/sessions/regions.d.ts +171 -0
- package/types/sessions/session.d.ts +97 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { type Rect } from '../geometry/geometry.js';
|
|
2
|
+
/**
|
|
3
|
+
* A DOM snapshot as the backend's pruned-DOM endpoint serves it (`api`'s `fetchPrunedDom`) —
|
|
4
|
+
* distinct from the raw dom the raw-DOM endpoint returns. Every step below is the *backend's* —
|
|
5
|
+
* the client-side pruner was removed at the DOM-diff cutover, so no function in this package
|
|
6
|
+
* performs any of them. Relative to a raw dom, a PrunedDom has:
|
|
7
|
+
* - embedded UFG CDN URLs rewritten to their canonical form, so the same resource looks
|
|
8
|
+
* identical whether captured on a baseline or checkpoint run
|
|
9
|
+
* - every element assigned a stable, sequential `id` (the numbering `assignNodeIds` mirrors),
|
|
10
|
+
* against the full unpruned tree, before any cropping happens
|
|
11
|
+
* - top-level metadata fields stripped (`css`, `images`, `version`, `scriptVersion`) — never
|
|
12
|
+
* part of the tree structure itself
|
|
13
|
+
* - whitespace-only `#text` node children removed throughout the tree
|
|
14
|
+
* It has *not* been cropped to any area — that's `getDomArea`'s separate, uncached, per-call
|
|
15
|
+
* job, applied to a PrunedDom after it's already been fetched.
|
|
16
|
+
*/
|
|
17
|
+
export type PrunedDom = any;
|
|
18
|
+
/**
|
|
19
|
+
* Extracts the bounding rect from an Applitools DOM snapshot node.
|
|
20
|
+
* Returns null if the node has no rect or its values are not numbers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function nodeRect(node: PrunedDom): Rect | null;
|
|
23
|
+
/**
|
|
24
|
+
* Recursively crops a PrunedDom tree to only the branches relevant to the given area.
|
|
25
|
+
*
|
|
26
|
+
* Retention rules:
|
|
27
|
+
* 1. Nodes that overlap the area without fully containing it get full detail — they are
|
|
28
|
+
* direct neighbours of the change.
|
|
29
|
+
* 2. The tightest container (innermost ancestor whose rect strictly contains the area,
|
|
30
|
+
* with no retained child also containing it) gets full detail.
|
|
31
|
+
* 3. Ancestors within ANCESTOR_SIMILARITY_FACTOR× the tightest container's pixel area
|
|
32
|
+
* get full detail — they are likely part of the immediate component structure.
|
|
33
|
+
* 4. All other (distant) ancestors are skipped entirely; their retained children are
|
|
34
|
+
* promoted up, so the output starts at the innermost relevant node.
|
|
35
|
+
* - Text/comment nodes ("#text", "#comment") are passed through with their parent.
|
|
36
|
+
* - Returns null if no node in the tree intersects the area.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getDomArea(node: PrunedDom, area: Rect): PrunedDom | PrunedDom[] | null;
|
|
39
|
+
/**
|
|
40
|
+
* Validates that a domId is usable. Returns an error string if not, null if valid.
|
|
41
|
+
* Guards against passing the literal string "null" copied from `info` JSON output.
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateDomId(domId: string | null | undefined): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* Assigns a sequential, 1-based id to every element node as a top-level `id` field, via a
|
|
46
|
+
* pre-order DFS over `root`. Mutates nodes in place.
|
|
47
|
+
*
|
|
48
|
+
* Must be called against the full, unpruned tree so a node's id is stable no matter
|
|
49
|
+
* what area a later `dom`/`dom-diff` call happens to crop to.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assignNodeIds(root: any): void;
|
|
52
|
+
/** A node located by id or by walking a `domSelector` path — the selector that reaches it, and its rect. */
|
|
53
|
+
export interface DomNode {
|
|
54
|
+
domSelector: string;
|
|
55
|
+
rect: Rect;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolves a single node id to its `domSelector` and `rect`. Returns `null` if the id isn't
|
|
59
|
+
* present in the tree, or if the node found has no usable rect.
|
|
60
|
+
*/
|
|
61
|
+
export declare function findNodeById(root: PrunedDom, domId: string): DomNode | null;
|
|
62
|
+
/**
|
|
63
|
+
* Resolves a single node id to the node object itself — unlike `findNodeById`, which only
|
|
64
|
+
* returns `{domSelector, rect}`. Needed wherever a caller must inspect or serialize the node
|
|
65
|
+
* itself, not just locate it. Returns `null` if the id isn't present in the tree.
|
|
66
|
+
*/
|
|
67
|
+
export declare function findNodeObjectById(root: PrunedDom, domId: string): any | null;
|
|
68
|
+
/**
|
|
69
|
+
* The inverse of `findNodeById`: walks `root` following a `domSelector` path (e.g.
|
|
70
|
+
* `/html/body[0]/div[2]`, as produced by `findNodeById`/`findNodeByRect`) and returns the
|
|
71
|
+
* rect of whatever element it still resolves to — or `null` if it doesn't (the element that
|
|
72
|
+
* path was built against has been removed, or a sibling's tag/order changed enough to shift
|
|
73
|
+
* indices). Returns the same `{domSelector, rect}` shape as `findNodeById`/`findNodeByRect`,
|
|
74
|
+
* so callers can use whichever of the three applies interchangeably.
|
|
75
|
+
*
|
|
76
|
+
* The first path segment is the root's own bare tag name with no `[index]` (see
|
|
77
|
+
* `findNodeById`) — there's only one root, so it's skipped rather than checked. Every
|
|
78
|
+
* segment after that must match `tagName[index]`, `index` being 0-based among `node`'s
|
|
79
|
+
* children sharing that tag.
|
|
80
|
+
*/
|
|
81
|
+
export declare function findNodeBySelector(root: PrunedDom, path: string): DomNode | null;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves a batch of node ids against the same already-fetched, already-id-assigned
|
|
84
|
+
* tree, one `findNodeById` walk per id. Pure — takes the tree as-is, does no fetching of
|
|
85
|
+
* its own (callers fetch the DOM snapshot once and reuse it here).
|
|
86
|
+
*
|
|
87
|
+
* Throws if any id can't be resolved, naming all of them, rather than silently omitting
|
|
88
|
+
* an entry from the returned map — a caller indexing the result by id (e.g.
|
|
89
|
+
* `result.get(id)!`) would otherwise get `undefined` for a bad id with no signal why.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveNodeIdsToNodes(root: PrunedDom, nodeIds: string[]): Map<string, DomNode>;
|
|
92
|
+
/**
|
|
93
|
+
* Finds the element node in `root` whose rect most closely matches `targetRect` — within
|
|
94
|
+
* `tolerancePx` on all four of left/top/width/height — returning the same {domSelector,
|
|
95
|
+
* rect} shape as `findNodeById`. Lets a caller upgrade a raw pixel rect (e.g. one eyeballed off
|
|
96
|
+
* a screenshot) into a domSelector-backed region when a matching element actually exists,
|
|
97
|
+
* so the region tracks that element rather than a fixed set of pixels. Returns `null` if no
|
|
98
|
+
* node is within tolerance. When multiple nodes qualify (e.g. a wrapper and its one child
|
|
99
|
+
* share nearly the same rect), the one with the smallest total deviation wins.
|
|
100
|
+
*/
|
|
101
|
+
export declare function findNodeByRect(root: PrunedDom, targetRect: Rect, tolerancePx?: number): DomNode | null;
|
|
102
|
+
export interface FlatNode {
|
|
103
|
+
node: PrunedDom;
|
|
104
|
+
depth: number;
|
|
105
|
+
/** This node's parent FlatNode in the same tree, or null/undefined at the root or when built
|
|
106
|
+
* without parent tracking (e.g. hand-built test fixtures). Lets a consumer walk up to an
|
|
107
|
+
* ancestor, or find true structural siblings, without re-traversing the tree. */
|
|
108
|
+
parent?: FlatNode | null;
|
|
109
|
+
}
|
|
110
|
+
export interface DisplacedEntry {
|
|
111
|
+
status: 'displaced';
|
|
112
|
+
count: number;
|
|
113
|
+
depth: number;
|
|
114
|
+
rect: {
|
|
115
|
+
left: number;
|
|
116
|
+
top: number;
|
|
117
|
+
width: number;
|
|
118
|
+
height: number;
|
|
119
|
+
};
|
|
120
|
+
baseline: {
|
|
121
|
+
rect: {
|
|
122
|
+
left: number;
|
|
123
|
+
top: number;
|
|
124
|
+
width: number;
|
|
125
|
+
height: number;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export interface CollapsedMissingEntry {
|
|
130
|
+
status: 'missing';
|
|
131
|
+
tagName: string;
|
|
132
|
+
id: string;
|
|
133
|
+
depth: number;
|
|
134
|
+
count: number;
|
|
135
|
+
attributes?: Record<string, string>;
|
|
136
|
+
rect: {
|
|
137
|
+
left: number;
|
|
138
|
+
top: number;
|
|
139
|
+
width: number;
|
|
140
|
+
height: number;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/** Common shape shared by `ChangedDiffEntry`/`NewDiffEntry`/`MissingDiffEntry`'s own side of a
|
|
144
|
+
* comparison — never the full `PrunedDom` node, only the properties an entry ever surfaces. */
|
|
145
|
+
interface DiffEntrySide {
|
|
146
|
+
style?: Record<string, any>;
|
|
147
|
+
attributes?: Record<string, string>;
|
|
148
|
+
text?: string;
|
|
149
|
+
}
|
|
150
|
+
export interface ChangedDiffEntry extends DiffEntrySide {
|
|
151
|
+
status: 'changed';
|
|
152
|
+
depth: number;
|
|
153
|
+
tagName: string;
|
|
154
|
+
id: string;
|
|
155
|
+
rect: Rect;
|
|
156
|
+
baseline: DiffEntrySide & {
|
|
157
|
+
tagName?: string;
|
|
158
|
+
id: string;
|
|
159
|
+
rect: Rect;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export interface NewDiffEntry extends DiffEntrySide {
|
|
163
|
+
status: 'new';
|
|
164
|
+
depth: number;
|
|
165
|
+
tagName: string;
|
|
166
|
+
id: string;
|
|
167
|
+
rect: Rect;
|
|
168
|
+
}
|
|
169
|
+
/** One baseline-side element with no checkpoint counterpart at all — a single removed element,
|
|
170
|
+
* as opposed to `CollapsedMissingEntry`'s whole removed subtree. */
|
|
171
|
+
export interface MissingDiffEntry extends DiffEntrySide {
|
|
172
|
+
status: 'missing';
|
|
173
|
+
depth: number;
|
|
174
|
+
tagName: string;
|
|
175
|
+
id: string;
|
|
176
|
+
rect: Rect;
|
|
177
|
+
}
|
|
178
|
+
/** One entry of an already-computed `DomAlignment.diff` — the same compact, per-element shape
|
|
179
|
+
* `serializeDiff` formats into its final string, carrying only each side's own id/rect/changed
|
|
180
|
+
* properties, never a full `PrunedDom` node or its ancestor chain. */
|
|
181
|
+
export type DiffEntry = ChangedDiffEntry | NewDiffEntry | MissingDiffEntry | DisplacedEntry | CollapsedMissingEntry;
|
|
182
|
+
/** Kept past the DOM-diff backend cutover: `findNodeByRect` walks the tree through it. */
|
|
183
|
+
export declare function flattenTree(root: PrunedDom): FlatNode[];
|
|
184
|
+
/**
|
|
185
|
+
* Returns whether an Applitools DOM capture node could plausibly paint a pixel: it has a
|
|
186
|
+
* non-zero-size rect and isn't hidden via `display:none`, `visibility:hidden`,
|
|
187
|
+
* `opacity:0`, or the `hidden` HTML attribute.
|
|
188
|
+
*/
|
|
189
|
+
export declare function isVisible(node: PrunedDom): boolean;
|
|
190
|
+
export declare function filterDiffToArea(diffs: (DiffEntry | null)[], area: Rect): (DiffEntry | null)[];
|
|
191
|
+
export declare function excludeNonVisualNodeDiffs(diffs: (DiffEntry | null)[], baselineDiffs: Rect[], checkpointDiffs: Rect[]): (DiffEntry | null)[];
|
|
192
|
+
export declare function serializeDiff(diffs: (DiffEntry | null)[]): string;
|
|
193
|
+
/** One checkpoint node's own match info, keyed by that node's id in `DomAlignment.matches` — its
|
|
194
|
+
* matched baseline node's own id (`baselineId`) and the checkpoint node's own `checkpointRect`.
|
|
195
|
+
* Never the full node objects on either side — see `DomAlignment.matches`'s own doc for why. A
|
|
196
|
+
* checkpoint node with no baseline counterpart (e.g. a genuinely new element) has no rect worth
|
|
197
|
+
* keeping here either — nothing about it can be aligned further, so `matches` maps that id to
|
|
198
|
+
* `null` instead of a `CompactMatch` (see `DomAlignment.matches`'s own doc for the full
|
|
199
|
+
* three-state shape). */
|
|
200
|
+
export interface CompactMatch {
|
|
201
|
+
baselineId: string;
|
|
202
|
+
checkpointRect: Rect;
|
|
203
|
+
}
|
|
204
|
+
export interface DomAlignment {
|
|
205
|
+
/** One entry per checkpoint node, in one of three states: the key itself absent means the id
|
|
206
|
+
* doesn't exist on the checkpoint side at all; present but mapped to `null` means the id exists
|
|
207
|
+
* but has no baseline counterpart; present and mapped to a `CompactMatch` means it does. Compact
|
|
208
|
+
* by design — never full node objects for the two sides of a match; serializing those (to a
|
|
209
|
+
* cache file, or a network response) would duplicate most of the tree's own data once per node
|
|
210
|
+
* it appears in as an ancestor, large enough on a big page to exhaust memory outright. */
|
|
211
|
+
matches: Record<string, CompactMatch | null>;
|
|
212
|
+
/** One entry per changed/new/missing/displaced element, already narrowed to `DiffEntry`'s
|
|
213
|
+
* compact shape — never a full `PrunedDom` node or its ancestor
|
|
214
|
+
* chain. Cropping to an area (`filterDiffToArea`) or excluding non-visual entries
|
|
215
|
+
* (`excludeNonVisualNodeDiffs`) are separate, later steps a caller applies to this array
|
|
216
|
+
* itself. */
|
|
217
|
+
diff: (DiffEntry | null)[];
|
|
218
|
+
}
|
|
219
|
+
export interface NodeDiffResult {
|
|
220
|
+
/** Whether `targetNodeId` was found at all on the checkpoint side of the alignment. `false`
|
|
221
|
+
* means the id genuinely doesn't exist in whichever tree was aligned as the checkpoint side —
|
|
222
|
+
* distinct from `matchedNodeId: null`, which means the id *was* found but has no baseline
|
|
223
|
+
* counterpart (e.g. it's a genuinely new element). A caller that only cares whether the id it
|
|
224
|
+
* asked about actually exists (rather than fetching a whole tree just to check) can rely on
|
|
225
|
+
* this without an extra call, since it's read off an alignment already being computed. */
|
|
226
|
+
found: boolean;
|
|
227
|
+
/** The id of the baseline-side node aligned to `targetNodeId`, or `null` if the global
|
|
228
|
+
* alignment found no counterpart for it (e.g. it doesn't exist yet on the baseline side).
|
|
229
|
+
* Always `null` when `found` is `false`. */
|
|
230
|
+
matchedNodeId: string | null;
|
|
231
|
+
/** Same compact format `serializeDiff` produces, filtered to the target node's own rect, but
|
|
232
|
+
* without `serializeDiff`'s own leading per-line indentation — that indent communicates
|
|
233
|
+
* nesting depth for direct terminal viewing (`dom-diff`'s own use), which is meaningless
|
|
234
|
+
* once this diff is embedded as a string value inside another JSON structure, and would
|
|
235
|
+
* vary arbitrarily with how deep the target node itself happens to sit in the page. Always
|
|
236
|
+
* `''` when `found` is `false` or `matchedNodeId` is `null` — either way there's no baseline
|
|
237
|
+
* counterpart's own rect left to scope a diff to (`DomAlignment.matches` never keeps one for
|
|
238
|
+
* an unmatched node). */
|
|
239
|
+
diff: string;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Extracts one target node's aligned counterpart and rect-filtered diff from an
|
|
243
|
+
* already-computed alignment (the backend's `fetchDomDiff` result, or an equivalent
|
|
244
|
+
* `DomAlignment` obtained some other way) — the cheap part once the
|
|
245
|
+
* whole-tree alignment is done, so a caller with several target nodes against the same pair of
|
|
246
|
+
* trees runs this once per node without paying for the alignment itself more than once.
|
|
247
|
+
* `targetNodeId` must be a node id assigned (see `assignNodeIds`) on whichever tree was aligned
|
|
248
|
+
* as the checkpoint side — its matched baseline id and its own checkpoint rect are read straight
|
|
249
|
+
* off `alignment.matches` by that id, so a caller never needs to hold onto the actual node object,
|
|
250
|
+
* only its id, and this works just as well against an alignment that was serialized (e.g. read
|
|
251
|
+
* back from a cache or a network response) as against one just computed in-process.
|
|
252
|
+
*
|
|
253
|
+
* Reading a target's own match off a *global*, whole-tree alignment — rather than a standalone
|
|
254
|
+
* best-match search for that one node — is deliberate: an element that occupies the same
|
|
255
|
+
* structural position across captures but now shows entirely different content (e.g. a rotating
|
|
256
|
+
* carousel slot, or a live counter) would otherwise get matched to wherever its *old* content
|
|
257
|
+
* happens to reappear elsewhere on the page, rather than to whatever now occupies its own slot.
|
|
258
|
+
* Aligning the whole page at once keeps a node's in-place replacement — not a content lookalike
|
|
259
|
+
* somewhere else — as its counterpart, because the aligner is scored on overall sequence
|
|
260
|
+
* correspondence, not one node in isolation.
|
|
261
|
+
*/
|
|
262
|
+
export declare function extractNodeDiff(alignment: DomAlignment, targetNodeId: string): NodeDiffResult;
|
|
263
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Region } from './regions.js';
|
|
2
|
+
/**
|
|
3
|
+
* The subset of `AnnotationCategory` (`sessions/regions.ts`) Applitools Eyes' own
|
|
4
|
+
* `ImageMatchSettings` schema actually has a field for — a raw session's own
|
|
5
|
+
* `imageMatchSettings` object never has a `mismatching` or `remarks` key, unlike
|
|
6
|
+
* `annotations`/`identifiedRegions`, which do (those two are a dashboard/annotations-only
|
|
7
|
+
* concept, not a real match-settings one).
|
|
8
|
+
*/
|
|
9
|
+
export declare const MATCH_SETTINGS_CATEGORIES: readonly ["content", "dynamic", "floating", "ignore", "layout", "strict", "accessibility"];
|
|
10
|
+
export type MatchSettingsCategory = (typeof MATCH_SETTINGS_CATEGORIES)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Applitools Eyes' own `ImageMatchSettings` structure — the same shape both as stored on a raw
|
|
13
|
+
* session's `actualAppOutput[stepIndex].imageMatchSettings` and as accepted by an
|
|
14
|
+
* `images/compare` request body (see `api`'s `compareStep`), built from a raw session via
|
|
15
|
+
* `getEffectiveMatchSettings` below. Every field is optional — a raw session's own
|
|
16
|
+
* `imageMatchSettings` isn't guaranteed to carry all of them (nothing here enforces it did), and
|
|
17
|
+
* a caller building a partial one by hand (e.g. a test fixture) shouldn't have to restate every
|
|
18
|
+
* scalar/category just to exercise the one it cares about.
|
|
19
|
+
*/
|
|
20
|
+
export interface ImageMatchSettings {
|
|
21
|
+
matchLevel?: string;
|
|
22
|
+
ignoreCaret?: boolean;
|
|
23
|
+
ignoreDisplacements?: boolean;
|
|
24
|
+
useDom?: boolean;
|
|
25
|
+
useDL?: boolean;
|
|
26
|
+
enablePatterns?: boolean;
|
|
27
|
+
scale?: number;
|
|
28
|
+
remainder?: number;
|
|
29
|
+
splitTopHeight?: number;
|
|
30
|
+
splitBottomHeight?: number;
|
|
31
|
+
content?: Region[];
|
|
32
|
+
dynamic?: Region[];
|
|
33
|
+
floating?: Region[];
|
|
34
|
+
ignore?: Region[];
|
|
35
|
+
layout?: Region[];
|
|
36
|
+
strict?: Region[];
|
|
37
|
+
accessibility?: Region[];
|
|
38
|
+
identifiedRegions?: Record<MatchSettingsCategory, Region[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Only meaningful on an `images/compare` request body — governs what a `Dynamic` matchLevel
|
|
41
|
+
* treats as "the same." Never observed on a raw session's own `imageMatchSettings`; omitted,
|
|
42
|
+
* the server applies its own default.
|
|
43
|
+
*/
|
|
44
|
+
dynamicMatchLevelSettings?: {
|
|
45
|
+
ignorePatterns: string[];
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds the full `ImageMatchSettings` an `images/compare` request body expects for a step —
|
|
50
|
+
* ready to use as-is. The rest of the request (`image1`/`image2`/`sessionId`/`batchId`/
|
|
51
|
+
* `runningSessionId`/`stepIndex`) is the caller's own job, not this function's — none of those
|
|
52
|
+
* are match-settings concerns.
|
|
53
|
+
*
|
|
54
|
+
* - Scalars (`matchLevel`, `scale`, `ignoreCaret`, `ignoreDisplacements`, `useDom`, etc.) come
|
|
55
|
+
* straight from `actualAppOutput[stepIndex].imageMatchSettings` — the match settings actually
|
|
56
|
+
* in effect for this run.
|
|
57
|
+
* - `identifiedRegions` (this run's own coded regions) also comes from that same
|
|
58
|
+
* `imageMatchSettings` object's own per-category arrays — never falling back to the
|
|
59
|
+
* baseline's `identifiedRegions` snapshot: if a category has no entry there, there is no coded
|
|
60
|
+
* region of that kind in the current match, full stop.
|
|
61
|
+
* - The plain, domSelector-anchored region categories are overwritten from
|
|
62
|
+
* `appOutputResolution[stepIndex].annotations` if a resolution exists, else
|
|
63
|
+
* `expectedAppOutput[stepIndex].annotations` (the baseline's own regions) — since
|
|
64
|
+
* `imageMatchSettings`'s own arrays only reflect what was true when the test last ran, not any
|
|
65
|
+
* region a reviewer added since.
|
|
66
|
+
*
|
|
67
|
+
* No baseline/checkpoint side-picking or healing happens here: the raw session already resolves
|
|
68
|
+
* which side a region describes (see `regions.ts`'s `getEffectiveRegions`, used instead for a
|
|
69
|
+
* diagnostic display that needs both sides shown) — this only ever needs the one side already in
|
|
70
|
+
* effect, relayed as-is, by reference.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getEffectiveMatchSettings(rawSession: any, stepIndex: number): ImageMatchSettings;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { type PrunedDom } from './dom.js';
|
|
2
|
+
/**
|
|
3
|
+
* A single region, unified across every place this repo encounters one — a dashboard-added,
|
|
4
|
+
* `domSelector`-anchored annotation; a test-code-defined, `regionId`-anchored coded region; or
|
|
5
|
+
* an `images/compare` request's own region entry (per Applitools' own compare-endpoint schema,
|
|
6
|
+
* which is the origin of the `padding*` fields). `domSelector` and `regionId` are each optional
|
|
7
|
+
* and independent: a region may be anchored by one, the other, both, or neither (a plain rect —
|
|
8
|
+
* confirmed live all of domSelector-only/regionId-only/plain-rect/combined work), and padding
|
|
9
|
+
* only ever layers on top of whichever anchor is given.
|
|
10
|
+
*/
|
|
11
|
+
export interface Region {
|
|
12
|
+
left: number;
|
|
13
|
+
top: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
regionId?: string;
|
|
17
|
+
domSelector?: string;
|
|
18
|
+
paddingLeft?: number;
|
|
19
|
+
paddingTop?: number;
|
|
20
|
+
paddingRight?: number;
|
|
21
|
+
paddingBottom?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const ANNOTATION_CATEGORIES: readonly ["content", "dynamic", "floating", "ignore", "mismatching", "layout", "remarks", "strict", "accessibility"];
|
|
24
|
+
export type AnnotationCategory = (typeof ANNOTATION_CATEGORIES)[number];
|
|
25
|
+
export declare function emptyAnnotations(): Record<AnnotationCategory, Region[]>;
|
|
26
|
+
export interface RegionEntry {
|
|
27
|
+
category: AnnotationCategory;
|
|
28
|
+
region: Region;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The same logical region as known on each image side — either may be absent, e.g. because the
|
|
32
|
+
* raw session doesn't encode that side's version at all, or the region doesn't exist there.
|
|
33
|
+
* Used both for plain (`annotations`-backed) regions, where either side may be absent, and for
|
|
34
|
+
* coded regions (see `getEffectiveRegions`'s own `pairCodedRegions`), where `checkpoint` is
|
|
35
|
+
* always populated by construction — a coded region is only ever surfaced there when it's
|
|
36
|
+
* defined on the checkpoint side, so a caller working with `codedRegions` can rely on
|
|
37
|
+
* `checkpoint` being present without an extra check, even though the type itself doesn't
|
|
38
|
+
* enforce that narrower guarantee.
|
|
39
|
+
*/
|
|
40
|
+
export interface RegionPair {
|
|
41
|
+
category: AnnotationCategory;
|
|
42
|
+
baseline?: Region;
|
|
43
|
+
checkpoint?: Region;
|
|
44
|
+
}
|
|
45
|
+
export interface EffectiveRegions {
|
|
46
|
+
regions: RegionPair[];
|
|
47
|
+
codedRegions: RegionPair[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Determines which side's DOM a step's regions should reference. `accept`/`reject` always
|
|
51
|
+
* resolve against the side they're about to make the (new) baseline going forward —
|
|
52
|
+
* checkpoint or baseline respectively. `unchanged` means an action that doesn't itself
|
|
53
|
+
* change the step's resolution (e.g. `baseline-regions`/`checkpoint-regions`, or just
|
|
54
|
+
* inspecting current state), so it must defer to whatever the step's *existing* resolution
|
|
55
|
+
* already is: a step accepted (but not yet saved) by an earlier call already has the
|
|
56
|
+
* checkpoint as its baseline-to-be, so a region added now should reference that, not the
|
|
57
|
+
* original baseline. Only an explicit prior reject, or no resolution at all, means the
|
|
58
|
+
* original baseline is still the one to reference. `expectedAppOutput`/`actualAppOutput`
|
|
59
|
+
* never change to reflect a pending resolution — only
|
|
60
|
+
* `appOutputResolution[stepIndex].replaceExpected` does.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveDomIdSide(action: 'accept' | 'reject' | 'unchanged', rawSession: any, stepIndex: number): 'baseline' | 'checkpoint';
|
|
63
|
+
/** The raw session's own image record (`{ id, domId, size, hasDom, location, rectangle }`) for a step's baseline or checkpoint image. */
|
|
64
|
+
export declare function imageForSide(rawSession: any, stepIndex: number, side: 'baseline' | 'checkpoint'): any;
|
|
65
|
+
/**
|
|
66
|
+
* Extracts every region-shaped structure the raw session encodes for a step, as
|
|
67
|
+
* baseline/checkpoint pairs — either side of a pair may be `undefined`, for a caller to fill
|
|
68
|
+
* in later (e.g. via `checkRegions` and the other functions below):
|
|
69
|
+
*
|
|
70
|
+
* - `regions` — domSelector-backed `Region[]`, from `annotations`. Only one side is ever
|
|
71
|
+
* populated here, since the raw session never encodes an `annotations`-shaped structure for
|
|
72
|
+
* both sides at once — `resolveDomIdSide` determines which side these domSelectors actually
|
|
73
|
+
* describe. `appOutputResolution[stepIndex].annotations`, when present, overrides
|
|
74
|
+
* `expectedAppOutput[stepIndex].annotations` entirely rather than being combined with it —
|
|
75
|
+
* the same way an empty category there means "resolved to have none" even when the
|
|
76
|
+
* baseline has one.
|
|
77
|
+
* - `codedRegions` — `regionId`-backed `Region[]`, paired up from `identifiedRegions` (on
|
|
78
|
+
* `expectedAppOutput`/`appOutputResolution`, same override rule as `annotations` — always the
|
|
79
|
+
* baseline side) and `imageMatchSettings` (on `actualAppOutput` — always the checkpoint side).
|
|
80
|
+
* Only ever includes an entry that's defined on the checkpoint side — a baseline
|
|
81
|
+
* `identifiedRegions` entry with no matching checkpoint `regionId` is dropped entirely (see
|
|
82
|
+
* `pairCodedRegions`), so `checkpoint` is never `undefined` here, only `baseline`.
|
|
83
|
+
*
|
|
84
|
+
* Both `annotations` and `identifiedRegions` deliberately exclude `removedAnnotations` — a
|
|
85
|
+
* historical record of past removals, not part of the current, effective region set.
|
|
86
|
+
*/
|
|
87
|
+
export declare function getEffectiveRegions(rawSession: any, stepIndex: number): EffectiveRegions;
|
|
88
|
+
export interface CheckedRegions {
|
|
89
|
+
/** Has a `domSelector` that resolved against `domSource`. */
|
|
90
|
+
anchored: RegionPair[];
|
|
91
|
+
/** No `domSelector`, or one that failed to resolve against `domSource` (including when `domSource` itself is unavailable) — left as-is either way, *not* cleared. */
|
|
92
|
+
nonAnchored: RegionPair[];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Splits every incomplete `RegionPair` (missing one side — see `getEffectiveRegions`) into
|
|
96
|
+
* anchored/non-anchored, verifying each anchored candidate's `domSelector` against `domSource`
|
|
97
|
+
* (the DOM of whichever side is already populated) along the way. Purely a classification —
|
|
98
|
+
* nothing is mutated, so a caller that just wants to inspect a step's regions as currently
|
|
99
|
+
* encoded (e.g. `eyes-inspect`'s `regions` command) can call this without side effects: a
|
|
100
|
+
* stale `domSelector` (one that no longer resolves) lands in `nonAnchored` exactly as encoded,
|
|
101
|
+
* not cleared — only a caller that intends to *heal* it (see `clearStaleSelectors`, below)
|
|
102
|
+
* should clear it. `anchored` means "has a `domSelector` that actually resolved" — if
|
|
103
|
+
* `domSource` is `null` (couldn't be fetched), nothing can be verified, so every pair lands in
|
|
104
|
+
* `nonAnchored`, domSelector or not.
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkRegions(regions: RegionPair[], domSource: PrunedDom): CheckedRegions;
|
|
107
|
+
/**
|
|
108
|
+
* Clears the `domSelector` (mutating in place) of every pair in `nonAnchored` that still has
|
|
109
|
+
* one — i.e. a stale one `checkRegions` left untouched — so it can be retried by
|
|
110
|
+
* `healNonAnchoredRegionsBySourceRect`. A stale selector is never sent to
|
|
111
|
+
* get-compatible-selectors as-is: live-confirmed against the real API, a single bad input
|
|
112
|
+
* selector fails the *entire* batched call, not just itself. Returns whether anything was
|
|
113
|
+
* actually cleared. Only a caller that intends to re-anchor a region calls this (e.g.
|
|
114
|
+
* `eyes-resolve`'s `resolveStepFlow`) — a caller that just wants to inspect regions as encoded
|
|
115
|
+
* (`checkRegions`'s other caller) should leave a stale selector alone for diagnosis rather
|
|
116
|
+
* than heal past it.
|
|
117
|
+
*/
|
|
118
|
+
export declare function clearStaleSelectors(nonAnchored: RegionPair[]): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Attempts to anchor each `nonAnchored` pair by finding its region's rect on `domSource` — the
|
|
121
|
+
* same mechanism a brand-new rect-form region uses to upgrade into a domSelector-backed one.
|
|
122
|
+
* Mutates a healed pair's existing (source-side) region in place with the found `domSelector`
|
|
123
|
+
* and accurate rect; a pair with no match is left as a plain rect, unchanged. Returns whether
|
|
124
|
+
* anything was healed. Pure and synchronous — `domSource` must already be fetched.
|
|
125
|
+
*/
|
|
126
|
+
export declare function healNonAnchoredRegionsBySourceRect(nonAnchored: RegionPair[], domSource: PrunedDom): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Fills in whichever side of each `RegionPair` is still missing with a plain, unanchored
|
|
129
|
+
* copy of its populated side — same rect, no `domSelector`. This doesn't try to find or heal
|
|
130
|
+
* anything (unlike every other function above); it just guarantees every pair comes out with
|
|
131
|
+
* both sides present, for a caller (`eyes-inspect`'s `regions` command) that wants a
|
|
132
|
+
* consistently-shaped, fully "mapped" output to inspect rather than an occasionally-partial
|
|
133
|
+
* one. Once this runs, a `domSelector` present on one side but empty on the other is itself
|
|
134
|
+
* diagnostically meaningful — it means that side's `domSelector` is stale, or the anchoring
|
|
135
|
+
* element genuinely doesn't exist there (get-compatible-selectors mapped it to `null`) —
|
|
136
|
+
* rather than being an accident of the pair simply never having been looked at.
|
|
137
|
+
*/
|
|
138
|
+
export declare function fillMissingSideWithPlainRect(regions: RegionPair[]): void;
|
|
139
|
+
/**
|
|
140
|
+
* The coded-region counterpart to `fillMissingSideWithPlainRect` — but one-directional, since
|
|
141
|
+
* `checkpoint` is never missing to begin with on a coded `RegionPair` (see
|
|
142
|
+
* `pairCodedRegions`/`getEffectiveRegions`): only `baseline` ever needs filling. Fills it with a
|
|
143
|
+
* plain copy of `checkpoint` at the same rect, with `regionId` cleared to `''`, rather than
|
|
144
|
+
* leaving it `undefined`.
|
|
145
|
+
*/
|
|
146
|
+
export declare function fillMissingBaselineForCodedRegions(codedRegions: RegionPair[]): void;
|
|
147
|
+
/**
|
|
148
|
+
* Finds an existing region "the same" as `candidate`: an exact `domSelector` match
|
|
149
|
+
* (checked across all candidates first — the stronger signal, survives rect drift), or
|
|
150
|
+
* else a rect match within `tolerancePx` on all four of left/top/width/height. Searches
|
|
151
|
+
* across every category, not just the one `candidate` belongs to, so a region that's been
|
|
152
|
+
* reclassified (e.g. layout -> ignore) is still found and replaced rather than duplicated.
|
|
153
|
+
*/
|
|
154
|
+
export declare function findSimilarExistingRegion(existing: RegionEntry[], candidate: Region, tolerancePx?: number): RegionEntry | null;
|
|
155
|
+
/**
|
|
156
|
+
* Removes (in place) the entry in `entries` "similar" to `candidate` (see
|
|
157
|
+
* `findSimilarExistingRegion`), if any, returning the removed entry — or `null` if nothing
|
|
158
|
+
* matched, so a caller can report exactly what was actually removed (e.g. for a `-<matchLevel>-
|
|
159
|
+
* <target>` regionOp) rather than just whether something was.
|
|
160
|
+
*
|
|
161
|
+
* Live-confirmed against the real API: the `stepUpdates[].annotations` field is a full
|
|
162
|
+
* replacement of the resolution's active regions, not a delta merged against whatever's
|
|
163
|
+
* already stored — and `removedAnnotations` isn't actually used for removal at all (the
|
|
164
|
+
* real Test Manager UI sends it as an empty skeleton even when deleting a region). So the
|
|
165
|
+
* only correct way to add/remove a region is to fetch the current full set, mutate it
|
|
166
|
+
* in memory (this function, plus a plain `.push` for adds), and send the *entire* result
|
|
167
|
+
* back as `annotations` — see eyes-resolve's `resolveStepFlow`.
|
|
168
|
+
*/
|
|
169
|
+
export declare function removeSimilarRegion(entries: RegionEntry[], candidate: Region, tolerancePx?: number): RegionEntry | null;
|
|
170
|
+
/** Buckets a flat, category-tagged region list back into the category-keyed shape the API expects. */
|
|
171
|
+
export declare function bucketRegions(entries: RegionEntry[]): Record<AnnotationCategory, Region[]>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export interface ImageInfo {
|
|
2
|
+
id: string | null;
|
|
3
|
+
domId: string | null;
|
|
4
|
+
size: {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
} | null;
|
|
8
|
+
}
|
|
9
|
+
export interface SessionEnvironment {
|
|
10
|
+
browser: string | null;
|
|
11
|
+
device: string | null;
|
|
12
|
+
os: string | null;
|
|
13
|
+
width: number | null;
|
|
14
|
+
height: number | null;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionStep {
|
|
17
|
+
tag: string | undefined;
|
|
18
|
+
baseline: ImageInfo;
|
|
19
|
+
checkpoint: ImageInfo;
|
|
20
|
+
isMatching: boolean;
|
|
21
|
+
resolution: 'accepted' | 'rejected' | 'none';
|
|
22
|
+
/** A pending resolution or region change on this step, not yet committed via `save`. */
|
|
23
|
+
isUnsaved: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface SimplifiedSession {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
sessionName: string;
|
|
28
|
+
appName: string;
|
|
29
|
+
status: string;
|
|
30
|
+
isUnsaved: boolean;
|
|
31
|
+
isAborted: boolean;
|
|
32
|
+
environment: SessionEnvironment;
|
|
33
|
+
startedAt: string;
|
|
34
|
+
/** `null` when shaped with `{ includeSteps: false }` — see `simplifySession`. */
|
|
35
|
+
steps: SessionStep[] | null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a raw session currently has any pending, unsaved change — an accept/reject decision
|
|
39
|
+
* or a region edit not yet committed via `save`. Checks every step's own
|
|
40
|
+
* `appOutputResolution[i].isChanged` flag (the same signal each step's own `isUnsaved` below is
|
|
41
|
+
* derived from), not the raw session's own top-level `isModified` field — confirmed live:
|
|
42
|
+
* `isModified` is sticky, staying `true` forever once a session was ever changed, even long
|
|
43
|
+
* after that change was fully cleared (e.g. via a batch-wide reset), so it can't signal "has a
|
|
44
|
+
* pending change right now." Independent of whether `simplifySession` was asked to compute
|
|
45
|
+
* `steps` — this reads `appOutputResolution` directly, so it's correct either way.
|
|
46
|
+
*/
|
|
47
|
+
export declare function hasUnsavedChanges(rawData: any): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether `stepIndex`'s checkpoint already carries a human accept/reject decision — the same
|
|
50
|
+
* `resolution` a step gets in `simplifySession` below, pulled out standalone so a caller only
|
|
51
|
+
* needs one step's status without shaping (or paying for) the whole session's `steps` array.
|
|
52
|
+
* A resolution entry can exist purely from a region edit, with no accept/reject decision behind
|
|
53
|
+
* it (no `replaceExpected` field) — only a real boolean there counts as resolved, however each
|
|
54
|
+
* side originally read.
|
|
55
|
+
*/
|
|
56
|
+
export declare function getStepResolution(rawData: any, stepIndex: number): 'accepted' | 'rejected' | 'none';
|
|
57
|
+
/**
|
|
58
|
+
* Extracts a raw session's scenario id — the join key for the session-history query (see
|
|
59
|
+
* `api`'s `fetchSessionHistory`) — confirmed live as a top-level `scenarioId` field,
|
|
60
|
+
* distinct from `scenarioName` (already mapped to `SimplifiedSession.sessionName`). Throws
|
|
61
|
+
* rather than returning `undefined` so a caller building a history query fails loudly instead
|
|
62
|
+
* of silently querying with a bad/missing id.
|
|
63
|
+
*/
|
|
64
|
+
export declare function extractScenarioId(rawData: any): string;
|
|
65
|
+
/**
|
|
66
|
+
* Throws a clear "step index N is out of range" error if `stepIndex` doesn't correspond to an
|
|
67
|
+
* actual step in the raw session. Callers that work from the raw, unshaped session directly
|
|
68
|
+
* (unlike this module's own `simplifySession`, which already has a bounds-checked `steps`
|
|
69
|
+
* array) would otherwise silently index past the end of `expectedAppOutput`/`actualAppOutput`
|
|
70
|
+
* and read `undefined` — which `validateDomId` then reports as "this step has no DOM capture",
|
|
71
|
+
* indistinguishable from a real step that simply wasn't DOM-captured.
|
|
72
|
+
*/
|
|
73
|
+
export declare function validateStepIndex(rawSession: any, stepIndex: number): void;
|
|
74
|
+
/** Total step count for a raw session — the longer of its baseline/checkpoint image arrays. */
|
|
75
|
+
export declare function getStepCount(rawSession: any): number;
|
|
76
|
+
/**
|
|
77
|
+
* Shapes a raw Eyes session API response into a compact summary — one representation
|
|
78
|
+
* shared by both a single-session inspection (`steps` command, full `steps` array) and a
|
|
79
|
+
* batch listing (`batch` command, one entry per session — pass `{ includeSteps: false }`
|
|
80
|
+
* there to skip computing `steps` for every session in the batch, which that listing has
|
|
81
|
+
* no use for since step-level counts are already aggregated separately into `sessions/batch`'s
|
|
82
|
+
* `BatchStats`).
|
|
83
|
+
*
|
|
84
|
+
* Overloaded so the common case (steps included, by default or explicitly) types `steps`
|
|
85
|
+
* as a real array rather than `Array | null` — only `{ includeSteps: false }` narrows it to
|
|
86
|
+
* `null`, matching what the implementation actually returns in each case.
|
|
87
|
+
*/
|
|
88
|
+
export declare function simplifySession(rawData: any, options?: {
|
|
89
|
+
includeSteps?: true;
|
|
90
|
+
}): SimplifiedSession & {
|
|
91
|
+
steps: SessionStep[];
|
|
92
|
+
};
|
|
93
|
+
export declare function simplifySession(rawData: any, options: {
|
|
94
|
+
includeSteps: false;
|
|
95
|
+
}): SimplifiedSession & {
|
|
96
|
+
steps: null;
|
|
97
|
+
};
|