@cronn/element-snapshot 0.13.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 +31 -17
- package/dist/index.d.mts +61 -16
- package/dist/index.mjs +55 -10
- package/package.json +11 -8
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
|
@@ -32,23 +32,28 @@
|
|
|
32
32
|
//#endregion
|
|
33
33
|
//#region src/utils/guards.ts
|
|
34
34
|
function isTextSnapshot(snapshot) {
|
|
35
|
-
return
|
|
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);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
//#endregion
|
|
39
|
-
//#region src/utils/
|
|
40
|
-
function
|
|
41
|
-
const {
|
|
43
|
+
//#region src/utils/filter.ts
|
|
44
|
+
function filter(options) {
|
|
45
|
+
const { predicate, snapshots, recurse = false } = options;
|
|
42
46
|
return snapshots.flatMap((snapshot) => {
|
|
43
|
-
const isFilteredIn =
|
|
47
|
+
const isFilteredIn = predicate(snapshot);
|
|
44
48
|
if (isFilteredIn && !recurse) return snapshot;
|
|
45
49
|
if (!isFilteredIn && recurse) return [];
|
|
46
|
-
const filteredChildren = hasChildren(snapshot) ?
|
|
47
|
-
|
|
50
|
+
const filteredChildren = hasChildren(snapshot) ? filter({
|
|
51
|
+
predicate,
|
|
48
52
|
snapshots: snapshot.children ?? [],
|
|
49
53
|
recurse
|
|
50
54
|
}) : [];
|
|
51
55
|
if (!isFilteredIn) return filteredChildren;
|
|
56
|
+
if (isTextSnapshot(snapshot)) return snapshot;
|
|
52
57
|
return {
|
|
53
58
|
...snapshot,
|
|
54
59
|
children: filteredChildren
|
|
@@ -59,6 +64,21 @@
|
|
|
59
64
|
return "children" in snapshot && snapshot.children.length > 0;
|
|
60
65
|
}
|
|
61
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();
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
//#endregion
|
|
63
83
|
//#region src/browser/text.ts
|
|
64
84
|
function snapshotTextNode(textNode) {
|
|
@@ -81,14 +101,8 @@
|
|
|
81
101
|
function resolveAccessibleTextContent(node, excludedNodes = []) {
|
|
82
102
|
const children = snapshotChildren(node, excludedNodes);
|
|
83
103
|
if (children === void 0) return;
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
filter: isTextSnapshot
|
|
87
|
-
}).map((textNode) => textNode.name).join(" "));
|
|
88
|
-
return normalizedText.length > 0 ? normalizedText : void 0;
|
|
89
|
-
}
|
|
90
|
-
function normalizeText(text) {
|
|
91
|
-
return text.replaceAll(/\s+/g, " ").trim();
|
|
104
|
+
const textContent = getTextContent(children);
|
|
105
|
+
return textContent.length > 0 ? textContent : void 0;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
//#endregion
|
|
@@ -641,7 +655,7 @@
|
|
|
641
655
|
|
|
642
656
|
//#endregion
|
|
643
657
|
//#region src/browser/table.ts
|
|
644
|
-
const
|
|
658
|
+
const SORT_TYPES = new Set([
|
|
645
659
|
"ascending",
|
|
646
660
|
"descending",
|
|
647
661
|
"other"
|
|
@@ -650,7 +664,7 @@
|
|
|
650
664
|
return {
|
|
651
665
|
role: "columnheader",
|
|
652
666
|
name: resolveAccessibleName(element),
|
|
653
|
-
attributes: { sort: enumAttribute(element.ariaSort,
|
|
667
|
+
attributes: { sort: enumAttribute(element.ariaSort, SORT_TYPES) },
|
|
654
668
|
children: snapshotChildren(element)
|
|
655
669
|
};
|
|
656
670
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -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 {
|
|
@@ -126,21 +126,50 @@ interface GenericElementSnapshot<TRole extends NodeRole = NodeRole, TAttributes
|
|
|
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
|
-
|
|
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>;
|
|
136
148
|
}
|
|
137
|
-
interface
|
|
138
|
-
|
|
149
|
+
interface FilterBaseOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Array of snapshots to filter.
|
|
152
|
+
*/
|
|
139
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
|
+
*/
|
|
140
162
|
recurse?: boolean;
|
|
141
163
|
}
|
|
142
|
-
|
|
143
|
-
|
|
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>>;
|
|
144
173
|
//#endregion
|
|
145
174
|
//#region src/playwright/snapshot.d.ts
|
|
146
175
|
interface ElementSnapshotOptions {
|
|
@@ -149,7 +178,7 @@ interface ElementSnapshotOptions {
|
|
|
149
178
|
*
|
|
150
179
|
* @default () => true
|
|
151
180
|
*/
|
|
152
|
-
filter?:
|
|
181
|
+
filter?: FilterPredicate;
|
|
153
182
|
/**
|
|
154
183
|
* Recursively apply specified filter to children of filtered elements
|
|
155
184
|
*
|
|
@@ -185,4 +214,20 @@ declare function snapshotElement(locator: Locator, options?: ElementSnapshotOpti
|
|
|
185
214
|
*/
|
|
186
215
|
declare function snapshotElementRaw(locator: Locator): Promise<Array<NodeSnapshot>>;
|
|
187
216
|
//#endregion
|
|
188
|
-
|
|
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
|
@@ -31,7 +31,11 @@ var ElementSnapshotProxy = class {
|
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region src/utils/guards.ts
|
|
33
33
|
function isTextSnapshot(snapshot) {
|
|
34
|
-
return
|
|
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);
|
|
35
39
|
}
|
|
36
40
|
function isEmpty(value) {
|
|
37
41
|
if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
|
|
@@ -39,25 +43,51 @@ function isEmpty(value) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
//#endregion
|
|
42
|
-
//#region src/utils/
|
|
43
|
-
|
|
44
|
-
|
|
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;
|
|
45
64
|
return snapshots.flatMap((snapshot) => {
|
|
46
|
-
const isFilteredIn =
|
|
65
|
+
const isFilteredIn = predicate(snapshot);
|
|
47
66
|
if (isFilteredIn && !recurse) return snapshot;
|
|
48
67
|
if (!isFilteredIn && recurse) return [];
|
|
49
|
-
const filteredChildren = hasChildren(snapshot) ?
|
|
50
|
-
|
|
68
|
+
const filteredChildren = hasChildren(snapshot) ? filter({
|
|
69
|
+
predicate,
|
|
51
70
|
snapshots: snapshot.children ?? [],
|
|
52
71
|
recurse
|
|
53
72
|
}) : [];
|
|
54
73
|
if (!isFilteredIn) return filteredChildren;
|
|
74
|
+
if (isTextSnapshot(snapshot)) return snapshot;
|
|
55
75
|
return {
|
|
56
76
|
...snapshot,
|
|
57
77
|
children: filteredChildren
|
|
58
78
|
};
|
|
59
79
|
});
|
|
60
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
|
+
}
|
|
61
91
|
function hasChildren(snapshot) {
|
|
62
92
|
return "children" in snapshot && snapshot.children.length > 0;
|
|
63
93
|
}
|
|
@@ -81,8 +111,8 @@ var ElementSnapshotTransformer = class {
|
|
|
81
111
|
}
|
|
82
112
|
filterSnapshots(snapshots) {
|
|
83
113
|
if (this.filter === void 0) return snapshots;
|
|
84
|
-
return
|
|
85
|
-
|
|
114
|
+
return filter({
|
|
115
|
+
predicate: this.filter,
|
|
86
116
|
snapshots,
|
|
87
117
|
recurse: this.recurseFilter
|
|
88
118
|
});
|
|
@@ -188,4 +218,19 @@ async function snapshotElementRaw(locator) {
|
|
|
188
218
|
}
|
|
189
219
|
|
|
190
220
|
//#endregion
|
|
191
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/element-snapshot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Element snapshots for Playwright",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/cronn/file-snapshots/issues"
|
|
@@ -40,26 +40,29 @@
|
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"markdown-table": "3.0.4",
|
|
43
44
|
"package-directory": "8.2.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@arethetypeswrong/core": "0.18.2",
|
|
47
48
|
"@playwright/test": "1.58.2",
|
|
48
49
|
"@trivago/prettier-plugin-sort-imports": "6.0.2",
|
|
49
|
-
"@
|
|
50
|
-
"@
|
|
50
|
+
"@types/node": "24.11.0",
|
|
51
|
+
"@typescript/native-preview": "7.0.0-dev.20260302.1",
|
|
51
52
|
"@vitest/coverage-v8": "4.0.18",
|
|
52
|
-
"eslint": "10.0.
|
|
53
|
+
"eslint": "10.0.2",
|
|
53
54
|
"eslint-config-prettier": "10.1.8",
|
|
55
|
+
"eslint-plugin-check-file": "3.3.1",
|
|
54
56
|
"eslint-plugin-unused-imports": "4.4.1",
|
|
55
57
|
"prettier": "3.8.1",
|
|
56
|
-
"publint": "0.3.
|
|
58
|
+
"publint": "0.3.18",
|
|
57
59
|
"tsdown": "0.20.3",
|
|
58
|
-
"typescript-eslint": "8.56.
|
|
60
|
+
"typescript-eslint": "8.56.1",
|
|
59
61
|
"vitest": "4.0.18",
|
|
60
|
-
"@cronn/playwright-file-snapshots": "1.0.0",
|
|
61
62
|
"@cronn/shared-configs": "0.0.0",
|
|
62
|
-
"@cronn/
|
|
63
|
+
"@cronn/playwright-file-snapshots": "1.0.1",
|
|
64
|
+
"@cronn/test-utils": "0.0.0",
|
|
65
|
+
"@cronn/vitest-file-snapshots": "1.0.1"
|
|
63
66
|
},
|
|
64
67
|
"peerDependencies": {
|
|
65
68
|
"@playwright/test": "^1.55"
|