@cronn/element-snapshot 0.12.0 → 0.14.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/README.md +16 -0
- package/dist/browser-lib.iife.js +51 -24
- package/dist/index.d.mts +65 -23
- package/dist/index.mjs +80 -24
- package/package.json +20 -12
package/README.md
CHANGED
|
@@ -25,6 +25,12 @@ pnpm add -D @cronn/element-snapshot
|
|
|
25
25
|
|
|
26
26
|
## Writing Tests
|
|
27
27
|
|
|
28
|
+
This package provides multiple snapshot formats for different use cases:
|
|
29
|
+
|
|
30
|
+
- **General-Purpose Snapshots**: Human-readable JSON format for all element types
|
|
31
|
+
- **Markdown Table Snapshots**: Specialized format for tables and grids ([documentation](docs/markdown-table-snapshot.md))
|
|
32
|
+
- **Custom Snapshots**: Build your own snapshot formats using raw element data and utility functions
|
|
33
|
+
|
|
28
34
|
### General-Purpose Snapshots
|
|
29
35
|
|
|
30
36
|
The function `snapshotElement` provides a general-purpose snapshot including all supported roles and attributes. It can be used to achieve a high test coverage, but can become hard to read for complex HTML structures.
|
|
@@ -113,3 +119,13 @@ function transformToMarkdownTable(snapshot: Array<NodeSnapshot>): string {
|
|
|
113
119
|
// transform table snapshot to markdown table
|
|
114
120
|
}
|
|
115
121
|
```
|
|
122
|
+
|
|
123
|
+
#### Utility Functions for Custom Snapshots
|
|
124
|
+
|
|
125
|
+
| Function | Description |
|
|
126
|
+
| ---------------- | --------------------------------------------------------------------- |
|
|
127
|
+
| `filter` | Filters node snapshots based on the provided predicate function. |
|
|
128
|
+
| `filterByRole` | Filters node snapshots by the specified role. |
|
|
129
|
+
| `includeRole` | Includes only elements with the specified role. |
|
|
130
|
+
| `excludeRole` | Excludes elements with the specified role. |
|
|
131
|
+
| `getTextContent` | Aggregates and normalizes the text content of all provided snapshots. |
|
package/dist/browser-lib.iife.js
CHANGED
|
@@ -26,32 +26,57 @@
|
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/browser/children.ts
|
|
28
28
|
function snapshotChildren(node, excludedNodes = []) {
|
|
29
|
-
|
|
30
|
-
return children.length > 0 ? children : void 0;
|
|
29
|
+
return Array.from(node.childNodes).map((childNode) => snapshotNodeRecursive(childNode, excludedNodes)).filter((childSnapshot) => childSnapshot !== null).flat();
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
//#endregion
|
|
34
|
-
//#region src/utils/
|
|
35
|
-
function
|
|
36
|
-
|
|
33
|
+
//#region src/utils/guards.ts
|
|
34
|
+
function isTextSnapshot(snapshot) {
|
|
35
|
+
return hasRole("text", snapshot);
|
|
36
|
+
}
|
|
37
|
+
function hasRole(role, snapshot) {
|
|
38
|
+
if (typeof role === "string") return role === snapshot.role;
|
|
39
|
+
return role.includes(snapshot.role);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/utils/filter.ts
|
|
44
|
+
function filter(options) {
|
|
45
|
+
const { predicate, snapshots, recurse = false } = options;
|
|
37
46
|
return snapshots.flatMap((snapshot) => {
|
|
38
|
-
const isFilteredIn =
|
|
47
|
+
const isFilteredIn = predicate(snapshot);
|
|
39
48
|
if (isFilteredIn && !recurse) return snapshot;
|
|
40
49
|
if (!isFilteredIn && recurse) return [];
|
|
41
|
-
const filteredChildren = hasChildren(snapshot) ?
|
|
42
|
-
|
|
50
|
+
const filteredChildren = hasChildren(snapshot) ? filter({
|
|
51
|
+
predicate,
|
|
43
52
|
snapshots: snapshot.children ?? [],
|
|
44
53
|
recurse
|
|
45
54
|
}) : [];
|
|
46
55
|
if (!isFilteredIn) return filteredChildren;
|
|
56
|
+
if (isTextSnapshot(snapshot)) return snapshot;
|
|
47
57
|
return {
|
|
48
58
|
...snapshot,
|
|
49
|
-
children: filteredChildren
|
|
59
|
+
children: filteredChildren
|
|
50
60
|
};
|
|
51
61
|
});
|
|
52
62
|
}
|
|
53
63
|
function hasChildren(snapshot) {
|
|
54
|
-
return "children" in snapshot && snapshot.children
|
|
64
|
+
return "children" in snapshot && snapshot.children.length > 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/utils/text.ts
|
|
69
|
+
/**
|
|
70
|
+
* Aggregates and normalizes the text content of all provided snapshots
|
|
71
|
+
*/
|
|
72
|
+
function getTextContent(snapshots) {
|
|
73
|
+
return normalizeText(filter({
|
|
74
|
+
snapshots,
|
|
75
|
+
predicate: isTextSnapshot
|
|
76
|
+
}).map((textNode) => textNode.name).join(" "));
|
|
77
|
+
}
|
|
78
|
+
function normalizeText(text) {
|
|
79
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
//#endregion
|
|
@@ -67,7 +92,7 @@
|
|
|
67
92
|
}
|
|
68
93
|
function snapshotPresentationalChildren(node) {
|
|
69
94
|
const accessibleTextContent = resolveAccessibleTextContent(node);
|
|
70
|
-
if (accessibleTextContent === void 0 || accessibleTextContent.length === 0) return;
|
|
95
|
+
if (accessibleTextContent === void 0 || accessibleTextContent.length === 0) return [];
|
|
71
96
|
return [{
|
|
72
97
|
role: "text",
|
|
73
98
|
name: accessibleTextContent
|
|
@@ -76,14 +101,8 @@
|
|
|
76
101
|
function resolveAccessibleTextContent(node, excludedNodes = []) {
|
|
77
102
|
const children = snapshotChildren(node, excludedNodes);
|
|
78
103
|
if (children === void 0) return;
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
filter: (element) => element.role === "text"
|
|
82
|
-
}).map((textNode) => textNode.name).join(" "));
|
|
83
|
-
return normalizedText.length > 0 ? normalizedText : void 0;
|
|
84
|
-
}
|
|
85
|
-
function normalizeText(text) {
|
|
86
|
-
return text.replaceAll(/\s+/g, " ").trim();
|
|
104
|
+
const textContent = getTextContent(children);
|
|
105
|
+
return textContent.length > 0 ? textContent : void 0;
|
|
87
106
|
}
|
|
88
107
|
|
|
89
108
|
//#endregion
|
|
@@ -190,6 +209,7 @@
|
|
|
190
209
|
return {
|
|
191
210
|
role,
|
|
192
211
|
name: resolveAccessibleName(element, false),
|
|
212
|
+
attributes: {},
|
|
193
213
|
children
|
|
194
214
|
};
|
|
195
215
|
}
|
|
@@ -396,7 +416,8 @@
|
|
|
396
416
|
value,
|
|
397
417
|
checked: resolveChecked(element),
|
|
398
418
|
...snapshotCommonInputAttributes(element, value === void 0)
|
|
399
|
-
}
|
|
419
|
+
},
|
|
420
|
+
children: []
|
|
400
421
|
};
|
|
401
422
|
}
|
|
402
423
|
function snapshotTextareaElement(element) {
|
|
@@ -407,7 +428,8 @@
|
|
|
407
428
|
attributes: {
|
|
408
429
|
value,
|
|
409
430
|
...snapshotCommonInputAttributes(element, value === void 0)
|
|
410
|
-
}
|
|
431
|
+
},
|
|
432
|
+
children: []
|
|
411
433
|
};
|
|
412
434
|
}
|
|
413
435
|
function snapshotCommonInputAttributes(element, isEmpty) {
|
|
@@ -454,6 +476,7 @@
|
|
|
454
476
|
value,
|
|
455
477
|
...snapshotCommonInputAttributes(element, value === void 0)
|
|
456
478
|
},
|
|
479
|
+
children: [],
|
|
457
480
|
options
|
|
458
481
|
};
|
|
459
482
|
}
|
|
@@ -512,6 +535,7 @@
|
|
|
512
535
|
return {
|
|
513
536
|
role: "group",
|
|
514
537
|
name: resolveGroupName(element),
|
|
538
|
+
attributes: {},
|
|
515
539
|
children: snapshotChildren(element)
|
|
516
540
|
};
|
|
517
541
|
}
|
|
@@ -552,7 +576,9 @@
|
|
|
552
576
|
if (imageName === void 0) return null;
|
|
553
577
|
return {
|
|
554
578
|
role: "img",
|
|
555
|
-
name: imageName
|
|
579
|
+
name: imageName,
|
|
580
|
+
attributes: {},
|
|
581
|
+
children: []
|
|
556
582
|
};
|
|
557
583
|
}
|
|
558
584
|
function resolveImageName(element) {
|
|
@@ -608,6 +634,7 @@
|
|
|
608
634
|
function snapshotSeparator(element) {
|
|
609
635
|
return {
|
|
610
636
|
role: "separator",
|
|
637
|
+
attributes: {},
|
|
611
638
|
children: snapshotPresentationalChildren(element)
|
|
612
639
|
};
|
|
613
640
|
}
|
|
@@ -628,7 +655,7 @@
|
|
|
628
655
|
|
|
629
656
|
//#endregion
|
|
630
657
|
//#region src/browser/table.ts
|
|
631
|
-
const
|
|
658
|
+
const SORT_TYPES = new Set([
|
|
632
659
|
"ascending",
|
|
633
660
|
"descending",
|
|
634
661
|
"other"
|
|
@@ -637,7 +664,7 @@
|
|
|
637
664
|
return {
|
|
638
665
|
role: "columnheader",
|
|
639
666
|
name: resolveAccessibleName(element),
|
|
640
|
-
attributes: { sort: enumAttribute(element.ariaSort,
|
|
667
|
+
attributes: { sort: enumAttribute(element.ariaSort, SORT_TYPES) },
|
|
641
668
|
children: snapshotChildren(element)
|
|
642
669
|
};
|
|
643
670
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -40,7 +40,7 @@ interface CommonInputAttributes extends InputStateAttributes, DiscribableAttribu
|
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/browser/combobox.d.ts
|
|
42
42
|
interface ComboboxSnapshot extends GenericElementSnapshot<"combobox", ComboboxAttributes> {
|
|
43
|
-
options
|
|
43
|
+
options: Array<OptionSnapshot>;
|
|
44
44
|
}
|
|
45
45
|
interface ComboboxAttributes extends CommonInputAttributes {
|
|
46
46
|
value?: string | Array<string>;
|
|
@@ -102,10 +102,10 @@ interface TabAttributes extends DisableableAttributes, SelectableAttributes {}
|
|
|
102
102
|
//#region src/browser/table.d.ts
|
|
103
103
|
interface ColumnheaderSnapshot extends GenericElementSnapshot<"columnheader", ColumnheaderAttributes> {}
|
|
104
104
|
interface ColumnheaderAttributes {
|
|
105
|
-
sort?:
|
|
105
|
+
sort?: SortType;
|
|
106
106
|
}
|
|
107
|
-
declare const
|
|
108
|
-
type
|
|
107
|
+
declare const SORT_TYPES: Set<"ascending" | "descending" | "other">;
|
|
108
|
+
type SortType = SetValues<typeof SORT_TYPES>;
|
|
109
109
|
//#endregion
|
|
110
110
|
//#region src/browser/text.d.ts
|
|
111
111
|
interface TextSnapshot {
|
|
@@ -118,32 +118,58 @@ type NodeRole = ElementRole | "text";
|
|
|
118
118
|
type ElementRole = "heading" | "link" | "button" | "option" | "tab" | "menuitem" | "columnheader" | "group" | "radiogroup" | "progressbar" | "img" | "separator" | ContainerRole | InputRole | DialogRole;
|
|
119
119
|
type NodeSnapshot = ElementSnapshot | TextSnapshot;
|
|
120
120
|
type ElementSnapshot = ContainerSnapshot | HeadingSnapshot | LinkSnapshot | ButtonSnapshot | InputSnapshot | ComboboxSnapshot | OptionSnapshot | DialogSnapshot | TabSnapshot | MenuitemSnapshot | ColumnheaderSnapshot | GroupSnapshot | RadiogroupSnapshot | ProgressbarSnapshot | ImageSnapshot | SeparatorSnapshot;
|
|
121
|
-
interface GenericElementSnapshot<TRole extends NodeRole = NodeRole, TAttributes = unknown
|
|
121
|
+
interface GenericElementSnapshot<TRole extends NodeRole = NodeRole, TAttributes = Record<string, unknown>> {
|
|
122
122
|
role: TRole;
|
|
123
123
|
name?: string;
|
|
124
|
-
attributes
|
|
125
|
-
children
|
|
124
|
+
attributes: TAttributes;
|
|
125
|
+
children: Array<NodeSnapshot>;
|
|
126
126
|
}
|
|
127
127
|
type SetValues<TSet> = TSet extends Set<infer TValue> ? TValue : never;
|
|
128
128
|
//#endregion
|
|
129
|
-
//#region src/
|
|
130
|
-
type
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
//#region src/types/snapshot.d.ts
|
|
130
|
+
type SnapshotByRole<TRole extends NodeRole> = NodeSnapshot & {
|
|
131
|
+
role: TRole;
|
|
132
|
+
};
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/utils/filter.d.ts
|
|
135
|
+
type FilterPredicate = (snapshot: NodeSnapshot) => boolean;
|
|
136
|
+
type GuardedFilterPredicate<TSnapshot extends NodeSnapshot> = (snapshot: NodeSnapshot) => snapshot is TSnapshot;
|
|
137
|
+
interface FilterOptions extends FilterBaseOptions {
|
|
138
|
+
/**
|
|
139
|
+
* Include only elements in the snapshot for which the specified predicate returns `true`
|
|
140
|
+
*/
|
|
141
|
+
predicate: FilterPredicate;
|
|
142
|
+
}
|
|
143
|
+
interface GuardedFilterOptions<TSnapshot extends NodeSnapshot> extends FilterBaseOptions {
|
|
144
|
+
/**
|
|
145
|
+
* Include only elements in the snapshot for which the specified predicate returns `true`
|
|
146
|
+
*/
|
|
147
|
+
predicate: GuardedFilterPredicate<TSnapshot>;
|
|
139
148
|
}
|
|
140
|
-
interface
|
|
141
|
-
|
|
149
|
+
interface FilterBaseOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Array of snapshots to filter.
|
|
152
|
+
*/
|
|
142
153
|
snapshots: Array<NodeSnapshot>;
|
|
154
|
+
/**
|
|
155
|
+
* Recursively apply specified filter to children of filtered elements
|
|
156
|
+
*
|
|
157
|
+
* By default, recursion ends when the filter returns `true` for an element.
|
|
158
|
+
* Should be `true` for filters intended to remove specific elements recursively.
|
|
159
|
+
*
|
|
160
|
+
* @default false
|
|
161
|
+
*/
|
|
143
162
|
recurse?: boolean;
|
|
144
163
|
}
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Filters node snapshots based on the provided predicate function
|
|
166
|
+
*/
|
|
167
|
+
declare function filter<TSnapshot extends NodeSnapshot = NodeSnapshot>(options: GuardedFilterOptions<TSnapshot>): Array<TSnapshot>;
|
|
168
|
+
declare function filter(options: FilterOptions): Array<NodeSnapshot>;
|
|
169
|
+
/**
|
|
170
|
+
* Filters node snapshots by a specific role
|
|
171
|
+
*/
|
|
172
|
+
declare function filterByRole<TRole extends NodeRole>(role: TRole, snapshots: Array<NodeSnapshot>): Array<SnapshotByRole<TRole>>;
|
|
147
173
|
//#endregion
|
|
148
174
|
//#region src/playwright/snapshot.d.ts
|
|
149
175
|
interface ElementSnapshotOptions {
|
|
@@ -152,7 +178,7 @@ interface ElementSnapshotOptions {
|
|
|
152
178
|
*
|
|
153
179
|
* @default () => true
|
|
154
180
|
*/
|
|
155
|
-
filter?:
|
|
181
|
+
filter?: FilterPredicate;
|
|
156
182
|
/**
|
|
157
183
|
* Recursively apply specified filter to children of filtered elements
|
|
158
184
|
*
|
|
@@ -188,4 +214,20 @@ declare function snapshotElement(locator: Locator, options?: ElementSnapshotOpti
|
|
|
188
214
|
*/
|
|
189
215
|
declare function snapshotElementRaw(locator: Locator): Promise<Array<NodeSnapshot>>;
|
|
190
216
|
//#endregion
|
|
191
|
-
|
|
217
|
+
//#region src/utils/predicates.d.ts
|
|
218
|
+
/**
|
|
219
|
+
* Include only elements with the specified role
|
|
220
|
+
*/
|
|
221
|
+
declare function includeRole<TRole extends NodeRole>(role: TRole | Array<TRole>): GuardedFilterPredicate<SnapshotByRole<TRole>>;
|
|
222
|
+
/**
|
|
223
|
+
* Exclude elements with the specified role
|
|
224
|
+
*/
|
|
225
|
+
declare function excludeRole<TRole extends NodeRole>(role: TRole | Array<TRole>): GuardedFilterPredicate<SnapshotByRole<TRole>>;
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/utils/text.d.ts
|
|
228
|
+
/**
|
|
229
|
+
* Aggregates and normalizes the text content of all provided snapshots
|
|
230
|
+
*/
|
|
231
|
+
declare function getTextContent(snapshots: Array<NodeSnapshot>): string;
|
|
232
|
+
//#endregion
|
|
233
|
+
export { type ButtonSnapshot, type ColumnheaderSnapshot, type ComboboxSnapshot, type ContainerSnapshot, type DialogSnapshot, type ElementRole, type ElementSnapshot, type GroupSnapshot, type HeadingSnapshot, type ImageSnapshot, type InputSnapshot, type LinkSnapshot, type MenuitemSnapshot, type NodeRole, type NodeSnapshot, type OptionSnapshot, type ProgressbarSnapshot, type RadiogroupSnapshot, type SeparatorSnapshot, type TabSnapshot, type TextSnapshot, excludeRole, filter, filterByRole, getTextContent, includeRole, snapshotElement, snapshotElementRaw };
|
package/dist/index.mjs
CHANGED
|
@@ -29,27 +29,67 @@ var ElementSnapshotProxy = class {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
//#endregion
|
|
32
|
-
//#region src/utils/
|
|
33
|
-
function
|
|
34
|
-
|
|
32
|
+
//#region src/utils/guards.ts
|
|
33
|
+
function isTextSnapshot(snapshot) {
|
|
34
|
+
return hasRole("text", snapshot);
|
|
35
|
+
}
|
|
36
|
+
function hasRole(role, snapshot) {
|
|
37
|
+
if (typeof role === "string") return role === snapshot.role;
|
|
38
|
+
return role.includes(snapshot.role);
|
|
39
|
+
}
|
|
40
|
+
function isEmpty(value) {
|
|
41
|
+
if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
|
|
42
|
+
return Object.keys(value).length === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/utils/predicates.ts
|
|
47
|
+
/**
|
|
48
|
+
* Include only elements with the specified role
|
|
49
|
+
*/
|
|
50
|
+
function includeRole(role) {
|
|
51
|
+
return (snapshot) => hasRole(role, snapshot);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Exclude elements with the specified role
|
|
55
|
+
*/
|
|
56
|
+
function excludeRole(role) {
|
|
57
|
+
return (snapshot) => !hasRole(role, snapshot);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/utils/filter.ts
|
|
62
|
+
function filter(options) {
|
|
63
|
+
const { predicate, snapshots, recurse = false } = options;
|
|
35
64
|
return snapshots.flatMap((snapshot) => {
|
|
36
|
-
const isFilteredIn =
|
|
65
|
+
const isFilteredIn = predicate(snapshot);
|
|
37
66
|
if (isFilteredIn && !recurse) return snapshot;
|
|
38
67
|
if (!isFilteredIn && recurse) return [];
|
|
39
|
-
const filteredChildren = hasChildren(snapshot) ?
|
|
40
|
-
|
|
68
|
+
const filteredChildren = hasChildren(snapshot) ? filter({
|
|
69
|
+
predicate,
|
|
41
70
|
snapshots: snapshot.children ?? [],
|
|
42
71
|
recurse
|
|
43
72
|
}) : [];
|
|
44
73
|
if (!isFilteredIn) return filteredChildren;
|
|
74
|
+
if (isTextSnapshot(snapshot)) return snapshot;
|
|
45
75
|
return {
|
|
46
76
|
...snapshot,
|
|
47
|
-
children: filteredChildren
|
|
77
|
+
children: filteredChildren
|
|
48
78
|
};
|
|
49
79
|
});
|
|
50
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Filters node snapshots by a specific role
|
|
83
|
+
*/
|
|
84
|
+
function filterByRole(role, snapshots) {
|
|
85
|
+
return filter({
|
|
86
|
+
predicate: includeRole(role),
|
|
87
|
+
recurse: false,
|
|
88
|
+
snapshots
|
|
89
|
+
});
|
|
90
|
+
}
|
|
51
91
|
function hasChildren(snapshot) {
|
|
52
|
-
return "children" in snapshot && snapshot.children
|
|
92
|
+
return "children" in snapshot && snapshot.children.length > 0;
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
//#endregion
|
|
@@ -71,8 +111,8 @@ var ElementSnapshotTransformer = class {
|
|
|
71
111
|
}
|
|
72
112
|
filterSnapshots(snapshots) {
|
|
73
113
|
if (this.filter === void 0) return snapshots;
|
|
74
|
-
return
|
|
75
|
-
|
|
114
|
+
return filter({
|
|
115
|
+
predicate: this.filter,
|
|
76
116
|
snapshots,
|
|
77
117
|
recurse: this.recurseFilter
|
|
78
118
|
});
|
|
@@ -81,7 +121,7 @@ var ElementSnapshotTransformer = class {
|
|
|
81
121
|
return snapshots.map((snapshot) => this.transformSnapshotRecursive(snapshot));
|
|
82
122
|
}
|
|
83
123
|
transformSnapshotRecursive(snapshot) {
|
|
84
|
-
if (snapshot
|
|
124
|
+
if (isTextSnapshot(snapshot)) return this.simplifyTextSnapshot(snapshot);
|
|
85
125
|
return this.simplifyElementSnapshot(snapshot);
|
|
86
126
|
}
|
|
87
127
|
simplifyTextSnapshot(snapshot) {
|
|
@@ -104,25 +144,26 @@ var ElementSnapshotTransformer = class {
|
|
|
104
144
|
if (snapshot.role === "combobox") return this.simplifyComboboxSnapshot(snapshot);
|
|
105
145
|
const { role, name, attributes, children } = normalizedSnapshot;
|
|
106
146
|
return this.transformedSnapshot(role, {
|
|
107
|
-
name,
|
|
147
|
+
name: name.length === 0 ? void 0 : name,
|
|
108
148
|
...attributes,
|
|
109
|
-
children
|
|
149
|
+
children: children.length === 0 ? void 0 : children
|
|
110
150
|
});
|
|
111
151
|
}
|
|
112
152
|
normalizeElementSnapshot(snapshot) {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const
|
|
153
|
+
const normalizedName = snapshot.name ?? "";
|
|
154
|
+
const sparseAttributes = this.removeUndefinedProperties(snapshot.attributes);
|
|
155
|
+
const transformedChildren = this.transformSnapshots(snapshot.children);
|
|
156
|
+
const nameEqualsChildren = transformedChildren.length === 1 && normalizedName === transformedChildren.at(0);
|
|
116
157
|
return {
|
|
117
158
|
role: snapshot.role,
|
|
118
|
-
name:
|
|
159
|
+
name: normalizedName,
|
|
119
160
|
attributes: sparseAttributes,
|
|
120
|
-
children: nameEqualsChildren ?
|
|
161
|
+
children: nameEqualsChildren ? [] : transformedChildren
|
|
121
162
|
};
|
|
122
163
|
}
|
|
123
164
|
simplifyComboboxSnapshot(snapshot) {
|
|
124
165
|
const { role, name, attributes } = this.normalizeElementSnapshot(snapshot);
|
|
125
|
-
const transformedOptions =
|
|
166
|
+
const transformedOptions = snapshot.options.map((optionSnapshot) => this.simplifyElementSnapshot(optionSnapshot));
|
|
126
167
|
return this.transformedSnapshot(role, {
|
|
127
168
|
name,
|
|
128
169
|
...attributes,
|
|
@@ -130,20 +171,20 @@ var ElementSnapshotTransformer = class {
|
|
|
130
171
|
});
|
|
131
172
|
}
|
|
132
173
|
isEmpty(snapshot) {
|
|
133
|
-
return snapshot.name
|
|
174
|
+
return isEmpty(snapshot.name) && isEmpty(snapshot.attributes) && isEmpty(snapshot.children);
|
|
134
175
|
}
|
|
135
176
|
hasOnlyName(snapshot) {
|
|
136
|
-
return snapshot.name
|
|
177
|
+
return !isEmpty(snapshot.name) && isEmpty(snapshot.attributes) && isEmpty(snapshot.children);
|
|
137
178
|
}
|
|
138
179
|
hasOnlyChildren(snapshot) {
|
|
139
|
-
return snapshot.name
|
|
180
|
+
return isEmpty(snapshot.name) && isEmpty(snapshot.attributes) && !isEmpty(snapshot.children);
|
|
140
181
|
}
|
|
141
182
|
removeUndefinedProperties(attributes) {
|
|
142
183
|
const sparseAttributes = {};
|
|
143
184
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
144
185
|
if (value !== void 0) sparseAttributes[key] = value;
|
|
145
186
|
});
|
|
146
|
-
return
|
|
187
|
+
return sparseAttributes;
|
|
147
188
|
}
|
|
148
189
|
transformedSnapshot(role, content) {
|
|
149
190
|
return { [role]: content };
|
|
@@ -177,4 +218,19 @@ async function snapshotElementRaw(locator) {
|
|
|
177
218
|
}
|
|
178
219
|
|
|
179
220
|
//#endregion
|
|
180
|
-
|
|
221
|
+
//#region src/utils/text.ts
|
|
222
|
+
/**
|
|
223
|
+
* Aggregates and normalizes the text content of all provided snapshots
|
|
224
|
+
*/
|
|
225
|
+
function getTextContent(snapshots) {
|
|
226
|
+
return normalizeText(filter({
|
|
227
|
+
snapshots,
|
|
228
|
+
predicate: isTextSnapshot
|
|
229
|
+
}).map((textNode) => textNode.name).join(" "));
|
|
230
|
+
}
|
|
231
|
+
function normalizeText(text) {
|
|
232
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
export { excludeRole, filter, filterByRole, getTextContent, includeRole, snapshotElement, snapshotElementRaw };
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/element-snapshot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Element snapshots for Playwright",
|
|
5
|
+
"bugs": {
|
|
6
|
+
"url": "https://github.com/cronn/file-snapshots/issues"
|
|
7
|
+
},
|
|
5
8
|
"author": "cronn",
|
|
6
9
|
"license": "Apache-2.0",
|
|
7
10
|
"repository": {
|
|
8
11
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/cronn/file-snapshots.git",
|
|
12
|
+
"url": "git+https://github.com/cronn/file-snapshots.git",
|
|
10
13
|
"directory": "packages/element-snapshot"
|
|
11
14
|
},
|
|
12
15
|
"homepage": "https://github.com/cronn/file-snapshots",
|
|
@@ -37,36 +40,41 @@
|
|
|
37
40
|
"dist"
|
|
38
41
|
],
|
|
39
42
|
"dependencies": {
|
|
43
|
+
"markdown-table": "3.0.4",
|
|
40
44
|
"package-directory": "8.2.0"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
47
|
+
"@arethetypeswrong/core": "0.18.2",
|
|
43
48
|
"@playwright/test": "1.58.2",
|
|
44
49
|
"@trivago/prettier-plugin-sort-imports": "6.0.2",
|
|
45
|
-
"@types/node": "24.
|
|
50
|
+
"@types/node": "24.11.0",
|
|
51
|
+
"@typescript/native-preview": "7.0.0-dev.20260302.1",
|
|
46
52
|
"@vitest/coverage-v8": "4.0.18",
|
|
47
|
-
"eslint": "
|
|
53
|
+
"eslint": "10.0.2",
|
|
48
54
|
"eslint-config-prettier": "10.1.8",
|
|
49
|
-
"eslint-plugin-
|
|
55
|
+
"eslint-plugin-check-file": "3.3.1",
|
|
56
|
+
"eslint-plugin-unused-imports": "4.4.1",
|
|
50
57
|
"prettier": "3.8.1",
|
|
58
|
+
"publint": "0.3.18",
|
|
51
59
|
"tsdown": "0.20.3",
|
|
52
|
-
"typescript": "
|
|
53
|
-
"typescript-eslint": "8.54.0",
|
|
60
|
+
"typescript-eslint": "8.56.1",
|
|
54
61
|
"vitest": "4.0.18",
|
|
55
|
-
"@cronn/playwright-file-snapshots": "0.38.1",
|
|
56
62
|
"@cronn/shared-configs": "0.0.0",
|
|
57
|
-
"@cronn/
|
|
63
|
+
"@cronn/playwright-file-snapshots": "1.0.1",
|
|
64
|
+
"@cronn/test-utils": "0.0.0",
|
|
65
|
+
"@cronn/vitest-file-snapshots": "1.0.1"
|
|
58
66
|
},
|
|
59
67
|
"peerDependencies": {
|
|
60
68
|
"@playwright/test": "^1.55"
|
|
61
69
|
},
|
|
62
70
|
"scripts": {
|
|
63
|
-
"lint:check": "eslint src/ --max-warnings=0",
|
|
64
|
-
"lint:fix": "eslint src/ --max-warnings=0 --fix",
|
|
71
|
+
"lint:check": "eslint src/ tests/ --max-warnings=0",
|
|
72
|
+
"lint:fix": "eslint src/ tests/ --max-warnings=0 --fix",
|
|
65
73
|
"test:unit": "vitest run",
|
|
66
74
|
"test:unit:coverage": "vitest run --coverage",
|
|
67
75
|
"test:integration": "playwright test",
|
|
68
76
|
"playwright:install": "playwright install chromium",
|
|
69
|
-
"compile": "
|
|
77
|
+
"compile": "tsgo",
|
|
70
78
|
"build": "tsdown"
|
|
71
79
|
}
|
|
72
80
|
}
|