@cronn/element-snapshot 0.15.0 → 0.17.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 +20 -79
- package/dist/browser-lib.iife.js +124 -106
- package/dist/index.d.mts +74 -75
- package/dist/index.mjs +34 -30
- package/package.json +19 -17
package/README.md
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
# Element Snapshots
|
|
1
|
+
# Playwright Element Snapshots
|
|
2
2
|
|
|
3
|
-
Element
|
|
3
|
+
Element snapshots for Playwright.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Read the full documentation](https://cronn.github.io/file-snapshots/playwright/element-snapshots/)
|
|
6
6
|
|
|
7
7
|
> [!NOTE]
|
|
8
8
|
> Element Snapshots are currently experimental. Not all HTML elements, ARIA roles and attributes are covered. Breaking changes to the snapshot format are possible.
|
|
9
9
|
|
|
10
10
|
## Getting Started
|
|
11
11
|
|
|
12
|
-
###
|
|
12
|
+
### Installation
|
|
13
13
|
|
|
14
14
|
```shell
|
|
15
|
-
|
|
15
|
+
pnpm add -D @cronn/element-snapshot
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```shell
|
|
19
|
-
|
|
19
|
+
npm install -D @cronn/element-snapshot
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```shell
|
|
23
|
-
|
|
23
|
+
yarn add -D @cronn/element-snapshot
|
|
24
24
|
```
|
|
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
|
-
|
|
34
|
-
### General-Purpose Snapshots
|
|
35
|
-
|
|
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.
|
|
37
|
-
|
|
38
28
|
```ts
|
|
39
|
-
import {
|
|
29
|
+
import { semanticSnapshot } from "@cronn/element-snapshot";
|
|
40
30
|
import { defineValidationFileExpect } from "@cronn/playwright-file-snapshots";
|
|
41
31
|
|
|
42
32
|
const expect = defineValidationFileExpect();
|
|
43
33
|
|
|
44
34
|
test("matches element snapshot", async ({ page }) => {
|
|
45
|
-
await
|
|
35
|
+
await page.setContent(`
|
|
36
|
+
<main>
|
|
37
|
+
<h1>List</h1>
|
|
38
|
+
<ul>
|
|
39
|
+
<li>Apple</li>
|
|
40
|
+
<li>Peach</li>
|
|
41
|
+
</ul>
|
|
42
|
+
</main>
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
await expect(semanticSnapshot(page.getByRole("main"))).toMatchJsonFile();
|
|
46
46
|
});
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**Output (`matches_element_snapshot.json`):**
|
|
50
50
|
|
|
51
51
|
```json
|
|
52
52
|
{
|
|
@@ -70,62 +70,3 @@ Element Snapshot Example:
|
|
|
70
70
|
]
|
|
71
71
|
}
|
|
72
72
|
```
|
|
73
|
-
|
|
74
|
-
To improve the specificity of certain tests, `snapshotElement` can be called on certain areas of the page only:
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
test("matches navigation snapshot", async ({ page }) => {
|
|
78
|
-
await expect(snapshotElement(page.getByRole("navigation"))).toMatchJsonFile({
|
|
79
|
-
name: "navigation",
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
#### Snapshot Options
|
|
85
|
-
|
|
86
|
-
Snapshot options can be passed when calling the snapshot function:
|
|
87
|
-
|
|
88
|
-
```ts
|
|
89
|
-
await expect(
|
|
90
|
-
snapshotElement(page.getByLabel("My select"), {
|
|
91
|
-
filter: (element) => element.role === "heading",
|
|
92
|
-
}),
|
|
93
|
-
).toMatchJsonFile({
|
|
94
|
-
name: "headings",
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
| Option | Default Value | Description |
|
|
99
|
-
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
100
|
-
| `filter` | `() => true` | Include only elements in the snapshot for which the specified filter returns `true`. |
|
|
101
|
-
| `recurseFilter` | `false` | Recursively apply specified filter to children of filtered elements. By default, recursion ends when the filter returns `true` for an element. Should be `true` for filters intended to remove specific elements recursively. |
|
|
102
|
-
| `includeComboboxOptions` | `false` | Include combobox options in the snapshot. |
|
|
103
|
-
|
|
104
|
-
### Custom Snapshots
|
|
105
|
-
|
|
106
|
-
The function `snapshotElementRaw` provides the raw element snapshot in a strictly typed structure. This format is not well suited to be read by humans, but can be utilized to derive custom snapshot formats, e.g. for HTML tables.
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
import { snapshotElementRaw } from "@cronn/element-snapshot";
|
|
110
|
-
|
|
111
|
-
test("matches custom snapshot", async ({ page }) => {
|
|
112
|
-
const tableSnapshot = await snapshotElementRaw(page.getByRole("table"));
|
|
113
|
-
const markdownTable = transformToMarkdownTable(tableSnapshot);
|
|
114
|
-
|
|
115
|
-
await expect(markdownTable).toMatchTextFile({ fileExtension: "md" });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
function transformToMarkdownTable(snapshot: Array<NodeSnapshot>): string {
|
|
119
|
-
// transform table snapshot to markdown table
|
|
120
|
-
}
|
|
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
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
name
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
function elementSnapshot(values) {
|
|
32
|
+
return {
|
|
33
|
+
role: values.role,
|
|
34
|
+
name: values.name,
|
|
35
|
+
attributes: values.attributes ?? {},
|
|
36
|
+
children: values.children ?? []
|
|
37
|
+
};
|
|
38
|
+
}
|
|
31
39
|
//#endregion
|
|
32
40
|
//#region src/utils/guards.ts
|
|
33
41
|
function isTextSnapshot(snapshot) {
|
|
@@ -38,6 +46,14 @@
|
|
|
38
46
|
return role.includes(snapshot.role);
|
|
39
47
|
}
|
|
40
48
|
//#endregion
|
|
49
|
+
//#region src/utils/predicates.ts
|
|
50
|
+
/**
|
|
51
|
+
* Include only elements with the specified role
|
|
52
|
+
*/
|
|
53
|
+
function includeRole(role) {
|
|
54
|
+
return (snapshot) => hasRole(role, snapshot);
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
41
57
|
//#region src/utils/filter.ts
|
|
42
58
|
function filter(options) {
|
|
43
59
|
const { predicate, snapshots, recurse = false } = options;
|
|
@@ -58,6 +74,16 @@
|
|
|
58
74
|
};
|
|
59
75
|
});
|
|
60
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Filters node snapshots by a specific role
|
|
79
|
+
*/
|
|
80
|
+
function filterByRole(role, snapshots) {
|
|
81
|
+
return filter({
|
|
82
|
+
predicate: includeRole(role),
|
|
83
|
+
recurse: false,
|
|
84
|
+
snapshots
|
|
85
|
+
});
|
|
86
|
+
}
|
|
61
87
|
function hasChildren(snapshot) {
|
|
62
88
|
return "children" in snapshot && snapshot.children.length > 0;
|
|
63
89
|
}
|
|
@@ -98,18 +124,12 @@
|
|
|
98
124
|
if (textNode.textContent === null) return null;
|
|
99
125
|
const normalizedText = normalizeText(textNode.textContent);
|
|
100
126
|
if (normalizedText.length === 0) return null;
|
|
101
|
-
return
|
|
102
|
-
role: "text",
|
|
103
|
-
name: normalizedText
|
|
104
|
-
};
|
|
127
|
+
return textSnapshot(normalizedText);
|
|
105
128
|
}
|
|
106
129
|
function snapshotPresentationalChildren(node) {
|
|
107
130
|
const accessibleTextContent = resolveAccessibleTextContent(node);
|
|
108
131
|
if (accessibleTextContent === void 0 || accessibleTextContent.length === 0) return [];
|
|
109
|
-
return [
|
|
110
|
-
role: "text",
|
|
111
|
-
name: accessibleTextContent
|
|
112
|
-
}];
|
|
132
|
+
return [textSnapshot(accessibleTextContent)];
|
|
113
133
|
}
|
|
114
134
|
function resolveAccessibleTextContent(node, excludedNodes = []) {
|
|
115
135
|
const children = snapshotChildren(node, excludedNodes);
|
|
@@ -128,10 +148,10 @@
|
|
|
128
148
|
//#endregion
|
|
129
149
|
//#region src/browser/state.ts
|
|
130
150
|
function disableableAttributes(element) {
|
|
131
|
-
return { disabled: booleanAttribute(isNativeInputElement(element) || element instanceof HTMLButtonElement ? element.hasAttribute("disabled") : false) ?? booleanAttribute(element.ariaDisabled) };
|
|
151
|
+
return { disabled: booleanAttribute(isNativeInputElement(element) || element instanceof HTMLButtonElement || element instanceof HTMLOptionElement ? element.hasAttribute("disabled") : false) ?? booleanAttribute(element.ariaDisabled) };
|
|
132
152
|
}
|
|
133
153
|
function selectableAttributes(element) {
|
|
134
|
-
return { selected: booleanAttribute(element.ariaSelected) };
|
|
154
|
+
return { selected: booleanAttribute(element instanceof HTMLOptionElement ? element.hasAttribute("selected") : false) ?? booleanAttribute(element.ariaSelected) };
|
|
135
155
|
}
|
|
136
156
|
function inputStateAttributes(element) {
|
|
137
157
|
const supportsHtmlAttributes = isNativeInputElement(element);
|
|
@@ -182,7 +202,7 @@
|
|
|
182
202
|
if (element.ariaDescription !== null) return element.ariaDescription;
|
|
183
203
|
}
|
|
184
204
|
//#endregion
|
|
185
|
-
//#region src/
|
|
205
|
+
//#region src/types/elements/container.ts
|
|
186
206
|
const CONTAINER_ROLES = new Set([
|
|
187
207
|
"paragraph",
|
|
188
208
|
"list",
|
|
@@ -213,15 +233,16 @@
|
|
|
213
233
|
"tabpanel",
|
|
214
234
|
"label"
|
|
215
235
|
]);
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/browser/container.ts
|
|
216
238
|
const NAMEABLE_CONTAINER_ROLES = new Set(["label"]);
|
|
217
239
|
function snapshotContainer(role, element) {
|
|
218
240
|
const children = snapshotChildren(element);
|
|
219
|
-
return {
|
|
241
|
+
return elementSnapshot({
|
|
220
242
|
role,
|
|
221
243
|
name: resolveAccessibleName(element, NAMEABLE_CONTAINER_ROLES.has(role)),
|
|
222
|
-
attributes: {},
|
|
223
244
|
children
|
|
224
|
-
};
|
|
245
|
+
});
|
|
225
246
|
}
|
|
226
247
|
function isContainerRole(role) {
|
|
227
248
|
return CONTAINER_ROLES.has(role);
|
|
@@ -248,62 +269,62 @@
|
|
|
248
269
|
//#endregion
|
|
249
270
|
//#region src/browser/role.ts
|
|
250
271
|
const ELEMENT_ROLES = {
|
|
251
|
-
|
|
252
|
-
nav: "navigation",
|
|
253
|
-
form: "form",
|
|
254
|
-
header: "banner",
|
|
255
|
-
section: "region",
|
|
272
|
+
a: "link",
|
|
256
273
|
article: "article",
|
|
257
274
|
aside: "complementary",
|
|
258
|
-
|
|
275
|
+
button: "button",
|
|
276
|
+
dd: "definition",
|
|
277
|
+
dl: "descriptionlist",
|
|
278
|
+
dt: "term",
|
|
279
|
+
fieldset: "group",
|
|
259
280
|
footer: "contentinfo",
|
|
260
|
-
|
|
281
|
+
form: "form",
|
|
261
282
|
h1: "heading",
|
|
262
283
|
h2: "heading",
|
|
263
284
|
h3: "heading",
|
|
264
285
|
h4: "heading",
|
|
265
286
|
h5: "heading",
|
|
266
287
|
h6: "heading",
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
a: "link",
|
|
271
|
-
button: "button",
|
|
288
|
+
header: "banner",
|
|
289
|
+
hr: "separator",
|
|
290
|
+
img: "img",
|
|
272
291
|
input: resolveInputRole,
|
|
273
|
-
|
|
274
|
-
|
|
292
|
+
label: "label",
|
|
293
|
+
legend: "label",
|
|
294
|
+
li: "listitem",
|
|
295
|
+
main: "main",
|
|
296
|
+
nav: "navigation",
|
|
297
|
+
ol: "list",
|
|
275
298
|
option: "option",
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
299
|
+
p: "paragraph",
|
|
300
|
+
progress: "progressbar",
|
|
301
|
+
search: "search",
|
|
302
|
+
section: "region",
|
|
303
|
+
select: "combobox",
|
|
279
304
|
table: "table",
|
|
280
|
-
thead: "rowgroup",
|
|
281
305
|
tbody: "rowgroup",
|
|
306
|
+
textarea: "textbox",
|
|
307
|
+
td: "cell",
|
|
282
308
|
tfoot: "rowgroup",
|
|
283
|
-
tr: "row",
|
|
284
309
|
th: resolveTableHeaderCellRole,
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
hr: "separator",
|
|
289
|
-
img: "img",
|
|
290
|
-
label: "label",
|
|
291
|
-
legend: "label"
|
|
310
|
+
thead: "rowgroup",
|
|
311
|
+
tr: "row",
|
|
312
|
+
ul: "list"
|
|
292
313
|
};
|
|
293
314
|
const CONTEXT_DEPENDENT_ROLES = {
|
|
294
315
|
banner: isTopLevelBanner,
|
|
295
|
-
|
|
296
|
-
|
|
316
|
+
cell: isWithinTableRow,
|
|
317
|
+
columnheader: isWithinTableRowOrGridRow,
|
|
297
318
|
definition: isWithinDescriptionList,
|
|
319
|
+
gridcell: isWithinGridRow,
|
|
298
320
|
listitem: isWithinList,
|
|
299
321
|
menuitem: isWithinMenu,
|
|
300
|
-
|
|
322
|
+
region: hasAccessibleName,
|
|
301
323
|
row: isWithinTableOrGrid,
|
|
302
|
-
|
|
324
|
+
rowgroup: isWithinTableOrGrid,
|
|
303
325
|
rowheader: isWithinTableRowOrGridRow,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
tab: isWithinTablist
|
|
326
|
+
tab: isWithinTablist,
|
|
327
|
+
term: isWithinDescriptionList
|
|
307
328
|
};
|
|
308
329
|
const INPUT_ROLES = {
|
|
309
330
|
button: "button",
|
|
@@ -416,34 +437,30 @@
|
|
|
416
437
|
function snapshotInputElement(element) {
|
|
417
438
|
const inputRole = resolveInputRole(element);
|
|
418
439
|
if (inputRole === void 0 || inputRole === "button" || inputRole === "combobox") return null;
|
|
419
|
-
|
|
420
|
-
return {
|
|
440
|
+
return elementSnapshot({
|
|
421
441
|
role: inputRole,
|
|
422
442
|
name: resolveInputLabel(element),
|
|
423
443
|
attributes: {
|
|
424
|
-
value,
|
|
444
|
+
value: resolveInputValue(element),
|
|
425
445
|
checked: resolveChecked(element),
|
|
426
|
-
...snapshotCommonInputAttributes(element
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
};
|
|
446
|
+
...snapshotCommonInputAttributes(element)
|
|
447
|
+
}
|
|
448
|
+
});
|
|
430
449
|
}
|
|
431
450
|
function snapshotTextareaElement(element) {
|
|
432
|
-
|
|
433
|
-
return {
|
|
451
|
+
return elementSnapshot({
|
|
434
452
|
role: "textbox",
|
|
435
453
|
name: resolveInputLabel(element),
|
|
436
454
|
attributes: {
|
|
437
|
-
value,
|
|
438
|
-
...snapshotCommonInputAttributes(element
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
};
|
|
455
|
+
value: resolveInputValue(element),
|
|
456
|
+
...snapshotCommonInputAttributes(element)
|
|
457
|
+
}
|
|
458
|
+
});
|
|
442
459
|
}
|
|
443
|
-
function snapshotCommonInputAttributes(element
|
|
460
|
+
function snapshotCommonInputAttributes(element) {
|
|
444
461
|
return {
|
|
445
462
|
...discribableAttributes(element),
|
|
446
|
-
placeholder:
|
|
463
|
+
placeholder: resolvePlaceholder(element),
|
|
447
464
|
...inputStateAttributes(element)
|
|
448
465
|
};
|
|
449
466
|
}
|
|
@@ -475,16 +492,15 @@
|
|
|
475
492
|
function snapshotCombobox(element) {
|
|
476
493
|
if (!isCombobox(element)) return null;
|
|
477
494
|
const options = snapshotOptions(element);
|
|
478
|
-
const value = resolveValue$1(element);
|
|
479
495
|
return {
|
|
480
496
|
role: "combobox",
|
|
481
497
|
name: resolveInputLabel(element),
|
|
482
498
|
attributes: {
|
|
483
|
-
value,
|
|
484
|
-
...snapshotCommonInputAttributes(element,
|
|
499
|
+
value: resolveValue$1(element),
|
|
500
|
+
...snapshotCommonInputAttributes(element),
|
|
501
|
+
options
|
|
485
502
|
},
|
|
486
|
-
children: []
|
|
487
|
-
options
|
|
503
|
+
children: []
|
|
488
504
|
};
|
|
489
505
|
}
|
|
490
506
|
function isCombobox(element) {
|
|
@@ -501,20 +517,24 @@
|
|
|
501
517
|
return selectedLabels;
|
|
502
518
|
}
|
|
503
519
|
function snapshotOptions(element) {
|
|
504
|
-
|
|
520
|
+
const optionsContainer = resolveOptionsContainer(element);
|
|
521
|
+
if (optionsContainer === null) return [];
|
|
522
|
+
return filterByRole("option", snapshotChildren(optionsContainer));
|
|
505
523
|
}
|
|
506
|
-
function
|
|
507
|
-
if (element instanceof HTMLSelectElement) return
|
|
524
|
+
function resolveOptionsContainer(element) {
|
|
525
|
+
if (element instanceof HTMLSelectElement) return element;
|
|
508
526
|
const controlledElement = resolveElementReference(element, "aria-controls");
|
|
509
|
-
if (controlledElement === null) return
|
|
510
|
-
|
|
511
|
-
return Array.from(controlledElement.querySelectorAll(optionSelector));
|
|
527
|
+
if (controlledElement === null) return null;
|
|
528
|
+
return controlledElement;
|
|
512
529
|
}
|
|
513
530
|
function snapshotOption(element) {
|
|
514
531
|
return {
|
|
515
532
|
role: "option",
|
|
516
533
|
name: resolveAccessibleName(element),
|
|
517
|
-
attributes: {
|
|
534
|
+
attributes: {
|
|
535
|
+
...selectableAttributes(element),
|
|
536
|
+
...disableableAttributes(element)
|
|
537
|
+
},
|
|
518
538
|
children: snapshotChildren(element)
|
|
519
539
|
};
|
|
520
540
|
}
|
|
@@ -537,19 +557,18 @@
|
|
|
537
557
|
//#endregion
|
|
538
558
|
//#region src/browser/group.ts
|
|
539
559
|
function snapshotGroup(element) {
|
|
540
|
-
return {
|
|
560
|
+
return elementSnapshot({
|
|
541
561
|
role: "group",
|
|
542
562
|
name: resolveGroupName(element),
|
|
543
|
-
attributes: {},
|
|
544
563
|
children: snapshotChildren(element)
|
|
545
|
-
};
|
|
564
|
+
});
|
|
546
565
|
}
|
|
547
566
|
function resolveGroupName(element) {
|
|
548
567
|
const legendElement = element instanceof HTMLFieldSetElement ? element.querySelector("legend") : null;
|
|
549
568
|
if (legendElement !== null) return resolveAccessibleTextContent(legendElement);
|
|
550
569
|
return resolveAccessibleName(element, false);
|
|
551
570
|
}
|
|
552
|
-
function
|
|
571
|
+
function snapshotRadioGroup(element) {
|
|
553
572
|
return {
|
|
554
573
|
role: "radiogroup",
|
|
555
574
|
name: resolveAccessibleName(element, false),
|
|
@@ -577,12 +596,10 @@
|
|
|
577
596
|
function snapshotImage(element) {
|
|
578
597
|
const imageName = resolveImageName(element);
|
|
579
598
|
if (imageName === void 0) return null;
|
|
580
|
-
return {
|
|
599
|
+
return elementSnapshot({
|
|
581
600
|
role: "img",
|
|
582
|
-
name: imageName
|
|
583
|
-
|
|
584
|
-
children: []
|
|
585
|
-
};
|
|
601
|
+
name: imageName
|
|
602
|
+
});
|
|
586
603
|
}
|
|
587
604
|
function resolveImageName(element) {
|
|
588
605
|
if (element instanceof HTMLImageElement) return stringAttribute(element.alt);
|
|
@@ -631,11 +648,10 @@
|
|
|
631
648
|
//#endregion
|
|
632
649
|
//#region src/browser/separator.ts
|
|
633
650
|
function snapshotSeparator(element) {
|
|
634
|
-
return {
|
|
651
|
+
return elementSnapshot({
|
|
635
652
|
role: "separator",
|
|
636
|
-
attributes: {},
|
|
637
653
|
children: snapshotPresentationalChildren(element)
|
|
638
|
-
};
|
|
654
|
+
});
|
|
639
655
|
}
|
|
640
656
|
//#endregion
|
|
641
657
|
//#region src/browser/tab.ts
|
|
@@ -651,13 +667,15 @@
|
|
|
651
667
|
};
|
|
652
668
|
}
|
|
653
669
|
//#endregion
|
|
654
|
-
//#region src/
|
|
670
|
+
//#region src/types/elements/table.ts
|
|
655
671
|
const SORT_TYPES = new Set([
|
|
656
672
|
"ascending",
|
|
657
673
|
"descending",
|
|
658
674
|
"other"
|
|
659
675
|
]);
|
|
660
|
-
|
|
676
|
+
//#endregion
|
|
677
|
+
//#region src/browser/table.ts
|
|
678
|
+
function snapshotColumnHeader(element) {
|
|
661
679
|
return {
|
|
662
680
|
role: "columnheader",
|
|
663
681
|
name: resolveAccessibleName(element),
|
|
@@ -668,27 +686,27 @@
|
|
|
668
686
|
//#endregion
|
|
669
687
|
//#region src/browser/element.ts
|
|
670
688
|
const ROLE_SNAPSHOTS = {
|
|
689
|
+
alertdialog: snapshotDialogWithRole("alertdialog"),
|
|
671
690
|
button: snapshotButton,
|
|
672
691
|
checkbox: snapshotInput,
|
|
692
|
+
columnheader: snapshotColumnHeader,
|
|
673
693
|
combobox: snapshotCombobox,
|
|
674
|
-
|
|
694
|
+
dialog: snapshotDialogWithRole("dialog"),
|
|
695
|
+
group: snapshotGroup,
|
|
675
696
|
heading: snapshotHeading,
|
|
697
|
+
img: snapshotImage,
|
|
698
|
+
menuitem: snapshotMenuitem,
|
|
699
|
+
option: snapshotOption,
|
|
700
|
+
progressbar: snapshotProgressbar,
|
|
676
701
|
link: snapshotLink,
|
|
677
702
|
radio: snapshotInput,
|
|
703
|
+
radiogroup: snapshotRadioGroup,
|
|
678
704
|
searchbox: snapshotInput,
|
|
705
|
+
separator: snapshotSeparator,
|
|
679
706
|
slider: snapshotInput,
|
|
680
707
|
spinbutton: snapshotInput,
|
|
681
|
-
textbox: snapshotInput,
|
|
682
|
-
dialog: snapshotDialogWithRole("dialog"),
|
|
683
|
-
alertdialog: snapshotDialogWithRole("alertdialog"),
|
|
684
708
|
tab: snapshotTab,
|
|
685
|
-
|
|
686
|
-
columnheader: snapshotColumnheader,
|
|
687
|
-
group: snapshotGroup,
|
|
688
|
-
radiogroup: snapshotRadiogroup,
|
|
689
|
-
progressbar: snapshotProgressbar,
|
|
690
|
-
img: snapshotImage,
|
|
691
|
-
separator: snapshotSeparator
|
|
709
|
+
textbox: snapshotInput
|
|
692
710
|
};
|
|
693
711
|
function snapshotElement(element) {
|
|
694
712
|
return snapshotNodeRecursive(element);
|
|
@@ -712,14 +730,14 @@
|
|
|
712
730
|
return snapshotContainer(elementRole, node);
|
|
713
731
|
}
|
|
714
732
|
const UNSUPPORTED_ELEMENTS = new Set([
|
|
715
|
-
"style",
|
|
716
|
-
"script",
|
|
717
|
-
"noscript",
|
|
718
|
-
"picture",
|
|
719
733
|
"audio",
|
|
720
|
-
"video",
|
|
721
734
|
"figure",
|
|
722
|
-
"
|
|
735
|
+
"noscript",
|
|
736
|
+
"picture",
|
|
737
|
+
"script",
|
|
738
|
+
"style",
|
|
739
|
+
"template",
|
|
740
|
+
"video"
|
|
723
741
|
]);
|
|
724
742
|
function isSupportedElement(node) {
|
|
725
743
|
if (!(node instanceof HTMLElement || node instanceof SVGElement)) return false;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { Locator } from "@playwright/test";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/types/attributes.d.ts
|
|
4
4
|
interface DisableableAttributes {
|
|
5
5
|
disabled?: boolean;
|
|
6
6
|
}
|
|
7
7
|
interface SelectableAttributes {
|
|
8
8
|
selected?: boolean;
|
|
9
9
|
}
|
|
10
|
+
interface DiscribableAttributes {
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
10
13
|
interface InputStateAttributes extends DisableableAttributes {
|
|
11
14
|
readonly?: boolean;
|
|
12
15
|
required?: boolean;
|
|
13
16
|
invalid?: boolean;
|
|
14
17
|
}
|
|
15
18
|
//#endregion
|
|
16
|
-
//#region src/
|
|
19
|
+
//#region src/types/elements/button.d.ts
|
|
17
20
|
interface ButtonSnapshot extends GenericElementSnapshot<"button", ButtonAttributes> {}
|
|
18
21
|
interface ButtonAttributes extends DisableableAttributes {
|
|
19
22
|
expanded?: boolean;
|
|
@@ -21,110 +24,108 @@ interface ButtonAttributes extends DisableableAttributes {
|
|
|
21
24
|
}
|
|
22
25
|
type PressedValue = true | "mixed";
|
|
23
26
|
//#endregion
|
|
24
|
-
//#region src/
|
|
25
|
-
interface DiscribableAttributes {
|
|
26
|
-
description?: string;
|
|
27
|
-
}
|
|
28
|
-
//#endregion
|
|
29
|
-
//#region src/browser/input.d.ts
|
|
30
|
-
interface InputSnapshot extends GenericElementSnapshot<CommonInputRole, InputAttributes> {}
|
|
31
|
-
type InputRole = "button" | "combobox" | CommonInputRole;
|
|
32
|
-
type CommonInputRole = "checkbox" | "radio" | "searchbox" | "slider" | "spinbutton" | "textbox";
|
|
33
|
-
interface InputAttributes extends CommonInputAttributes {
|
|
34
|
-
value?: string;
|
|
35
|
-
checked?: boolean;
|
|
36
|
-
}
|
|
37
|
-
interface CommonInputAttributes extends InputStateAttributes, DiscribableAttributes {
|
|
38
|
-
placeholder?: string;
|
|
39
|
-
}
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/browser/combobox.d.ts
|
|
42
|
-
interface ComboboxSnapshot extends GenericElementSnapshot<"combobox", ComboboxAttributes> {
|
|
43
|
-
options: Array<OptionSnapshot>;
|
|
44
|
-
}
|
|
45
|
-
interface ComboboxAttributes extends CommonInputAttributes {
|
|
46
|
-
value?: string | Array<string>;
|
|
47
|
-
}
|
|
48
|
-
interface OptionSnapshot extends GenericElementSnapshot<"option", OptionAttributes> {}
|
|
49
|
-
interface OptionAttributes {
|
|
50
|
-
selected?: boolean;
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/browser/container.d.ts
|
|
54
|
-
declare const CONTAINER_ROLES: Set<"paragraph" | "list" | "listitem" | "listbox" | "article" | "banner" | "complementary" | "contentinfo" | "form" | "main" | "navigation" | "region" | "search" | "descriptionlist" | "term" | "definition" | "table" | "grid" | "rowgroup" | "row" | "rowheader" | "cell" | "gridcell" | "alert" | "menu" | "tablist" | "tabpanel" | "label">;
|
|
55
|
-
interface ContainerSnapshot extends GenericElementSnapshot<ContainerRole> {}
|
|
56
|
-
type ContainerRole = SetValues<typeof CONTAINER_ROLES>;
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region src/browser/dialog.d.ts
|
|
27
|
+
//#region src/types/elements/dialog.d.ts
|
|
59
28
|
interface DialogSnapshot extends GenericElementSnapshot<DialogRole, DialogAttributes> {}
|
|
60
29
|
type DialogRole = "dialog" | "alertdialog";
|
|
61
30
|
interface DialogAttributes extends DiscribableAttributes {
|
|
62
31
|
modal?: boolean;
|
|
63
32
|
}
|
|
64
33
|
//#endregion
|
|
65
|
-
//#region src/
|
|
34
|
+
//#region src/types/elements/group.d.ts
|
|
66
35
|
interface GroupSnapshot extends GenericElementSnapshot<"group"> {}
|
|
67
|
-
interface
|
|
36
|
+
interface RadioGroupSnapshot extends GenericElementSnapshot<"radiogroup", RadiogroupAttributes> {}
|
|
68
37
|
interface RadiogroupAttributes extends DiscribableAttributes {}
|
|
69
38
|
//#endregion
|
|
70
|
-
//#region src/
|
|
39
|
+
//#region src/types/elements/heading.d.ts
|
|
71
40
|
interface HeadingSnapshot extends GenericElementSnapshot<"heading", HeadingAttributes> {}
|
|
72
41
|
interface HeadingAttributes {
|
|
73
42
|
level?: number;
|
|
74
43
|
}
|
|
75
44
|
//#endregion
|
|
76
|
-
//#region src/
|
|
45
|
+
//#region src/types/elements/image.d.ts
|
|
77
46
|
interface ImageSnapshot extends GenericElementSnapshot<"img"> {}
|
|
78
47
|
//#endregion
|
|
79
|
-
//#region src/
|
|
48
|
+
//#region src/types/elements/input.d.ts
|
|
49
|
+
interface InputSnapshot extends GenericElementSnapshot<CommonInputRole, InputAttributes> {}
|
|
50
|
+
type InputRole = "combobox" | CommonInputRole;
|
|
51
|
+
type CommonInputRole = "button" | "checkbox" | "radio" | "searchbox" | "slider" | "spinbutton" | "textbox";
|
|
52
|
+
interface InputAttributes extends CommonInputAttributes {
|
|
53
|
+
value?: string;
|
|
54
|
+
checked?: boolean;
|
|
55
|
+
}
|
|
56
|
+
interface CommonInputAttributes extends InputStateAttributes, DiscribableAttributes {
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
}
|
|
59
|
+
interface ComboboxSnapshot extends GenericElementSnapshot<"combobox", ComboboxAttributes> {}
|
|
60
|
+
interface ComboboxAttributes extends CommonInputAttributes {
|
|
61
|
+
value?: string | Array<string>;
|
|
62
|
+
options: Array<OptionSnapshot>;
|
|
63
|
+
}
|
|
64
|
+
interface OptionSnapshot extends GenericElementSnapshot<"option", OptionAttributes> {}
|
|
65
|
+
interface OptionAttributes extends SelectableAttributes, DisableableAttributes {}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/types/elements/link.d.ts
|
|
80
68
|
interface LinkSnapshot extends GenericElementSnapshot<"link", LinkAttributes> {}
|
|
81
69
|
interface LinkAttributes {
|
|
82
70
|
url?: string;
|
|
83
71
|
}
|
|
84
72
|
//#endregion
|
|
85
|
-
//#region src/
|
|
86
|
-
interface
|
|
73
|
+
//#region src/types/elements/list.d.ts
|
|
74
|
+
interface MenuItemSnapshot extends GenericElementSnapshot<"menuitem", MenuitemAttributes> {}
|
|
87
75
|
interface MenuitemAttributes extends LinkAttributes, DisableableAttributes {}
|
|
88
76
|
//#endregion
|
|
89
|
-
//#region src/
|
|
77
|
+
//#region src/types/elements/progressbar.d.ts
|
|
90
78
|
interface ProgressbarSnapshot extends GenericElementSnapshot<"progressbar", ProgressbarAttributes> {}
|
|
91
79
|
interface ProgressbarAttributes {
|
|
92
80
|
value?: number;
|
|
93
81
|
}
|
|
94
82
|
//#endregion
|
|
95
|
-
//#region src/
|
|
83
|
+
//#region src/types/elements/separator.d.ts
|
|
96
84
|
interface SeparatorSnapshot extends GenericElementSnapshot<"separator"> {}
|
|
97
85
|
//#endregion
|
|
98
|
-
//#region src/
|
|
86
|
+
//#region src/types/elements/tab.d.ts
|
|
99
87
|
interface TabSnapshot extends GenericElementSnapshot<"tab", TabAttributes> {}
|
|
100
88
|
interface TabAttributes extends DisableableAttributes, SelectableAttributes {}
|
|
101
89
|
//#endregion
|
|
102
|
-
//#region src/
|
|
103
|
-
|
|
90
|
+
//#region src/types/utils.d.ts
|
|
91
|
+
type SetValues<TSet> = TSet extends Set<infer TValue> ? TValue : never;
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/types/elements/table.d.ts
|
|
94
|
+
declare const SORT_TYPES: Set<"ascending" | "descending" | "other">;
|
|
95
|
+
interface ColumnHeaderSnapshot extends GenericElementSnapshot<"columnheader", ColumnheaderAttributes> {}
|
|
104
96
|
interface ColumnheaderAttributes {
|
|
105
97
|
sort?: SortType;
|
|
106
98
|
}
|
|
107
|
-
declare const SORT_TYPES: Set<"ascending" | "descending" | "other">;
|
|
108
99
|
type SortType = SetValues<typeof SORT_TYPES>;
|
|
109
100
|
//#endregion
|
|
110
|
-
//#region src/
|
|
101
|
+
//#region src/types/elements/text.d.ts
|
|
111
102
|
interface TextSnapshot {
|
|
112
103
|
role: "text";
|
|
113
104
|
name: string;
|
|
114
105
|
}
|
|
115
106
|
//#endregion
|
|
116
|
-
//#region src/
|
|
117
|
-
type NodeRole = ElementRole |
|
|
118
|
-
type ElementRole = "heading" | "link" | "button" | "option" | "tab" | "menuitem" | "columnheader" | "group" | "radiogroup" | "progressbar" | "img" | "separator" | ContainerRole | InputRole | DialogRole;
|
|
107
|
+
//#region src/types/snapshot.d.ts
|
|
108
|
+
type NodeRole = ElementRole | TextRole;
|
|
119
109
|
type NodeSnapshot = ElementSnapshot | TextSnapshot;
|
|
120
|
-
type
|
|
110
|
+
type SnapshotByRole<TRole extends NodeRole> = NodeSnapshot & {
|
|
111
|
+
role: TRole;
|
|
112
|
+
};
|
|
121
113
|
interface GenericElementSnapshot<TRole extends NodeRole = NodeRole, TAttributes = Record<string, unknown>> {
|
|
122
114
|
role: TRole;
|
|
123
115
|
name?: string;
|
|
124
116
|
attributes: TAttributes;
|
|
125
117
|
children: Array<NodeSnapshot>;
|
|
126
118
|
}
|
|
127
|
-
type
|
|
119
|
+
type ElementSnapshot = ButtonSnapshot | ColumnHeaderSnapshot | ComboboxSnapshot | ContainerSnapshot | DialogSnapshot | GroupSnapshot | HeadingSnapshot | ImageSnapshot | InputSnapshot | LinkSnapshot | OptionSnapshot | ProgressbarSnapshot | TabSnapshot | MenuItemSnapshot | RadioGroupSnapshot | SeparatorSnapshot;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/types/elements/container.d.ts
|
|
122
|
+
declare const CONTAINER_ROLES: Set<"paragraph" | "list" | "listitem" | "listbox" | "article" | "banner" | "complementary" | "contentinfo" | "form" | "main" | "navigation" | "region" | "search" | "descriptionlist" | "term" | "definition" | "table" | "grid" | "rowgroup" | "row" | "rowheader" | "cell" | "gridcell" | "alert" | "menu" | "tablist" | "tabpanel" | "label">;
|
|
123
|
+
interface ContainerSnapshot extends GenericElementSnapshot<ContainerRole> {}
|
|
124
|
+
type ContainerRole = SetValues<typeof CONTAINER_ROLES>;
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/types/role.d.ts
|
|
127
|
+
type TextRole = "text";
|
|
128
|
+
type ElementRole = "button" | "columnheader" | "group" | "heading" | "img" | "link" | "menuitem" | "option" | "progressbar" | "radiogroup" | "separator" | "tab" | ContainerRole | DialogRole | InputRole;
|
|
128
129
|
//#endregion
|
|
129
130
|
//#region src/playwright/markdown-table.d.ts
|
|
130
131
|
interface MarkdownTableSnapshotOptions {
|
|
@@ -140,11 +141,6 @@ interface MarkdownTableSnapshotOptions {
|
|
|
140
141
|
*/
|
|
141
142
|
declare function markdownTableSnapshot(locator: Locator, options?: MarkdownTableSnapshotOptions): Promise<string>;
|
|
142
143
|
//#endregion
|
|
143
|
-
//#region src/types/snapshot.d.ts
|
|
144
|
-
type SnapshotByRole<TRole extends NodeRole> = NodeSnapshot & {
|
|
145
|
-
role: TRole;
|
|
146
|
-
};
|
|
147
|
-
//#endregion
|
|
148
144
|
//#region src/utils/filter.d.ts
|
|
149
145
|
type FilterPredicate = (snapshot: NodeSnapshot) => boolean;
|
|
150
146
|
type GuardedFilterPredicate<TSnapshot extends NodeSnapshot> = (snapshot: NodeSnapshot) => snapshot is TSnapshot;
|
|
@@ -186,7 +182,7 @@ declare function filter(options: FilterOptions): Array<NodeSnapshot>;
|
|
|
186
182
|
declare function filterByRole<TRole extends NodeRole>(role: TRole, snapshots: Array<NodeSnapshot>): Array<SnapshotByRole<TRole>>;
|
|
187
183
|
//#endregion
|
|
188
184
|
//#region src/playwright/snapshot.d.ts
|
|
189
|
-
interface
|
|
185
|
+
interface SemanticSnapshotOptions {
|
|
190
186
|
/**
|
|
191
187
|
* Include only elements in the snapshot for which the specified filter returns `true`
|
|
192
188
|
*
|
|
@@ -210,23 +206,26 @@ interface ElementSnapshotOptions {
|
|
|
210
206
|
includeComboboxOptions?: boolean;
|
|
211
207
|
}
|
|
212
208
|
/**
|
|
213
|
-
*
|
|
209
|
+
* Captures a semantic snapshot of the specified locator's DOM structure.
|
|
214
210
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* @param
|
|
220
|
-
* @
|
|
211
|
+
* This function creates a raw snapshot of the DOM element identified by the locator
|
|
212
|
+
* and transforms it into a semantic representation using the provided options.
|
|
213
|
+
*
|
|
214
|
+
* @param locator - The Locator instance that identifies the DOM element to snapshot
|
|
215
|
+
* @param options - Optional configuration settings for controlling the snapshot behavior
|
|
216
|
+
* @return A promise that resolves to the semantic snapshot
|
|
221
217
|
*/
|
|
222
|
-
declare function
|
|
218
|
+
declare function semanticSnapshot(locator: Locator, options?: SemanticSnapshotOptions): Promise<unknown>;
|
|
223
219
|
/**
|
|
224
|
-
*
|
|
220
|
+
* Captures a raw snapshot of the element matched by the provided locator.
|
|
221
|
+
*
|
|
222
|
+
* Returns an array of node snapshots representing the current state of the matched elements
|
|
223
|
+
* in the DOM, including their properties, attributes, and structure.
|
|
225
224
|
*
|
|
226
|
-
* @param locator Locator
|
|
227
|
-
* @
|
|
225
|
+
* @param locator - The Locator instance used to identify the target element to snapshot
|
|
226
|
+
* @return A promise that resolves to an array of NodeSnapshot objects representing the captured element
|
|
228
227
|
*/
|
|
229
|
-
declare function
|
|
228
|
+
declare function rawSnapshot(locator: Locator): Promise<Array<NodeSnapshot>>;
|
|
230
229
|
//#endregion
|
|
231
230
|
//#region src/utils/predicates.d.ts
|
|
232
231
|
/**
|
|
@@ -244,4 +243,4 @@ declare function excludeRole<TRole extends NodeRole>(role: TRole | Array<TRole>)
|
|
|
244
243
|
*/
|
|
245
244
|
declare function getTextContent(snapshots: Array<NodeSnapshot>): string;
|
|
246
245
|
//#endregion
|
|
247
|
-
export { type ButtonSnapshot, type
|
|
246
|
+
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 MarkdownTableSnapshotOptions, type MenuItemSnapshot, type NodeRole, type NodeSnapshot, type OptionSnapshot, type ProgressbarSnapshot, type RadioGroupSnapshot, type SemanticSnapshotOptions, type SeparatorSnapshot, type SnapshotByRole, type TabSnapshot, type TextSnapshot, excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, rawSnapshot, semanticSnapshot };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MarkdownTableSerializer } from "@cronn/lib-file-snapshots";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { packageDirectory } from "package-directory";
|
|
4
4
|
//#region src/utils/guards.ts
|
|
@@ -106,8 +106,8 @@ var ElementSnapshotProxy = class {
|
|
|
106
106
|
}
|
|
107
107
|
};
|
|
108
108
|
//#endregion
|
|
109
|
-
//#region src/playwright/transformer.ts
|
|
110
|
-
var
|
|
109
|
+
//#region src/playwright/semantic-snapshot-transformer.ts
|
|
110
|
+
var SemanticSnapshotTransformer = class {
|
|
111
111
|
filter;
|
|
112
112
|
recurseFilter;
|
|
113
113
|
includeComboboxOptions;
|
|
@@ -162,21 +162,21 @@ var ElementSnapshotTransformer = class {
|
|
|
162
162
|
children: children.length === 0 ? void 0 : children
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
-
normalizeElementSnapshot(snapshot) {
|
|
165
|
+
normalizeElementSnapshot(snapshot, excludeAttributes = []) {
|
|
166
166
|
const normalizedName = snapshot.name ?? "";
|
|
167
|
-
const
|
|
167
|
+
const filteredAttributes = this.filterAttributes(snapshot.attributes, excludeAttributes);
|
|
168
168
|
const transformedChildren = this.transformSnapshots(snapshot.children);
|
|
169
169
|
const nameEqualsChildren = transformedChildren.length === 1 && normalizedName === transformedChildren.at(0);
|
|
170
170
|
return {
|
|
171
171
|
role: snapshot.role,
|
|
172
172
|
name: normalizedName,
|
|
173
|
-
attributes:
|
|
173
|
+
attributes: filteredAttributes,
|
|
174
174
|
children: nameEqualsChildren ? [] : transformedChildren
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
177
|
simplifyComboboxSnapshot(snapshot) {
|
|
178
|
-
const { role, name, attributes } = this.normalizeElementSnapshot(snapshot);
|
|
179
|
-
const transformedOptions = snapshot.options.map((optionSnapshot) => this.simplifyElementSnapshot(optionSnapshot));
|
|
178
|
+
const { role, name, attributes } = this.normalizeElementSnapshot(snapshot, ["options"]);
|
|
179
|
+
const transformedOptions = snapshot.attributes.options.map((optionSnapshot) => this.simplifyElementSnapshot(optionSnapshot));
|
|
180
180
|
return this.transformedSnapshot(role, {
|
|
181
181
|
name,
|
|
182
182
|
...attributes,
|
|
@@ -192,12 +192,13 @@ var ElementSnapshotTransformer = class {
|
|
|
192
192
|
hasOnlyChildren(snapshot) {
|
|
193
193
|
return isEmpty(snapshot.name) && isEmpty(snapshot.attributes) && !isEmpty(snapshot.children);
|
|
194
194
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
195
|
+
filterAttributes(attributes, exclude) {
|
|
196
|
+
const filteredAttributes = {};
|
|
197
197
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
198
|
-
if (value
|
|
198
|
+
if (value === void 0 && !exclude.includes(key)) return;
|
|
199
|
+
filteredAttributes[key] = value;
|
|
199
200
|
});
|
|
200
|
-
return
|
|
201
|
+
return filteredAttributes;
|
|
201
202
|
}
|
|
202
203
|
transformedSnapshot(role, content) {
|
|
203
204
|
return { [role]: content };
|
|
@@ -206,26 +207,29 @@ var ElementSnapshotTransformer = class {
|
|
|
206
207
|
//#endregion
|
|
207
208
|
//#region src/playwright/snapshot.ts
|
|
208
209
|
/**
|
|
209
|
-
*
|
|
210
|
+
* Captures a semantic snapshot of the specified locator's DOM structure.
|
|
210
211
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* @param
|
|
216
|
-
* @
|
|
212
|
+
* This function creates a raw snapshot of the DOM element identified by the locator
|
|
213
|
+
* and transforms it into a semantic representation using the provided options.
|
|
214
|
+
*
|
|
215
|
+
* @param locator - The Locator instance that identifies the DOM element to snapshot
|
|
216
|
+
* @param options - Optional configuration settings for controlling the snapshot behavior
|
|
217
|
+
* @return A promise that resolves to the semantic snapshot
|
|
217
218
|
*/
|
|
218
|
-
async function
|
|
219
|
-
const snapshot = await
|
|
220
|
-
return new
|
|
219
|
+
async function semanticSnapshot(locator, options) {
|
|
220
|
+
const snapshot = await rawSnapshot(locator);
|
|
221
|
+
return new SemanticSnapshotTransformer(options).transform(snapshot);
|
|
221
222
|
}
|
|
222
223
|
/**
|
|
223
|
-
*
|
|
224
|
+
* Captures a raw snapshot of the element matched by the provided locator.
|
|
225
|
+
*
|
|
226
|
+
* Returns an array of node snapshots representing the current state of the matched elements
|
|
227
|
+
* in the DOM, including their properties, attributes, and structure.
|
|
224
228
|
*
|
|
225
|
-
* @param locator Locator
|
|
226
|
-
* @
|
|
229
|
+
* @param locator - The Locator instance used to identify the target element to snapshot
|
|
230
|
+
* @return A promise that resolves to an array of NodeSnapshot objects representing the captured element
|
|
227
231
|
*/
|
|
228
|
-
async function
|
|
232
|
+
async function rawSnapshot(locator) {
|
|
229
233
|
return await new ElementSnapshotProxy(locator.page()).snapshotElement(locator);
|
|
230
234
|
}
|
|
231
235
|
//#endregion
|
|
@@ -245,8 +249,8 @@ const sortIndicators = {
|
|
|
245
249
|
* Creates a Markdown table snapshot from a table or grid element
|
|
246
250
|
*/
|
|
247
251
|
async function markdownTableSnapshot(locator, options = {}) {
|
|
248
|
-
const
|
|
249
|
-
return
|
|
252
|
+
const parsedTable = parseTable(await rawSnapshot(locator), options);
|
|
253
|
+
return new MarkdownTableSerializer().serialize(parsedTable);
|
|
250
254
|
}
|
|
251
255
|
function parseTable(snapshot, options) {
|
|
252
256
|
const { showSortIndicator = true } = options;
|
|
@@ -262,7 +266,7 @@ function parseTable(snapshot, options) {
|
|
|
262
266
|
const columnHeaders = filterByRole("columnheader", headerRow.children);
|
|
263
267
|
if (columnHeaders.length === 0) throw new Error("No column headers found");
|
|
264
268
|
return {
|
|
265
|
-
|
|
269
|
+
columns: columnHeaders.map((columnHeader) => getColumnHeaderText(columnHeader, showSortIndicator)),
|
|
266
270
|
rows: bodyRows.map(getCellTexts)
|
|
267
271
|
};
|
|
268
272
|
}
|
|
@@ -279,4 +283,4 @@ function getCellTexts(row) {
|
|
|
279
283
|
}).map((cell) => getTextContent(cell.children));
|
|
280
284
|
}
|
|
281
285
|
//#endregion
|
|
282
|
-
export { excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot,
|
|
286
|
+
export { excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, rawSnapshot, semanticSnapshot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/element-snapshot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Element snapshots for Playwright",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/cronn/file-snapshots/issues"
|
|
@@ -12,19 +12,19 @@
|
|
|
12
12
|
"url": "git+https://github.com/cronn/file-snapshots.git",
|
|
13
13
|
"directory": "packages/element-snapshot"
|
|
14
14
|
},
|
|
15
|
-
"homepage": "https://github.
|
|
15
|
+
"homepage": "https://cronn.github.io/file-snapshots/playwright/element-snapshots/",
|
|
16
16
|
"engines": {
|
|
17
17
|
"node": "^20 || ^22 || >=24"
|
|
18
18
|
},
|
|
19
19
|
"devEngines": {
|
|
20
20
|
"runtime": {
|
|
21
21
|
"name": "node",
|
|
22
|
-
"version": ">=24.
|
|
22
|
+
"version": ">=24.14.1 <25",
|
|
23
23
|
"onFail": "error"
|
|
24
24
|
},
|
|
25
25
|
"packageManager": {
|
|
26
26
|
"name": "pnpm",
|
|
27
|
-
"version": ">=10.
|
|
27
|
+
"version": ">=10.33.0 <11",
|
|
28
28
|
"onFail": "error"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
@@ -40,29 +40,30 @@
|
|
|
40
40
|
"dist"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"
|
|
44
|
-
"
|
|
43
|
+
"package-directory": "8.2.0",
|
|
44
|
+
"@cronn/lib-file-snapshots": "1.1.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@arethetypeswrong/core": "0.18.2",
|
|
48
|
-
"@playwright/test": "1.
|
|
48
|
+
"@playwright/test": "1.59.1",
|
|
49
49
|
"@trivago/prettier-plugin-sort-imports": "6.0.2",
|
|
50
|
-
"@types/node": "24.12.
|
|
51
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
52
|
-
"@vitest/coverage-v8": "4.
|
|
53
|
-
"eslint": "10.0
|
|
50
|
+
"@types/node": "24.12.2",
|
|
51
|
+
"@typescript/native-preview": "7.0.0-dev.20260406.1",
|
|
52
|
+
"@vitest/coverage-v8": "4.1.4",
|
|
53
|
+
"eslint": "10.2.0",
|
|
54
54
|
"eslint-config-prettier": "10.1.8",
|
|
55
55
|
"eslint-plugin-check-file": "3.3.1",
|
|
56
56
|
"eslint-plugin-unused-imports": "4.4.1",
|
|
57
57
|
"prettier": "3.8.1",
|
|
58
58
|
"publint": "0.3.18",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
59
|
+
"rimraf": "6.1.3",
|
|
60
|
+
"tsdown": "0.21.7",
|
|
61
|
+
"typescript-eslint": "8.58.0",
|
|
62
|
+
"vitest": "4.1.4",
|
|
63
|
+
"@cronn/playwright-file-snapshots": "1.0.3",
|
|
63
64
|
"@cronn/shared-configs": "0.0.0",
|
|
64
65
|
"@cronn/test-utils": "0.0.0",
|
|
65
|
-
"@cronn/vitest-file-snapshots": "1.0
|
|
66
|
+
"@cronn/vitest-file-snapshots": "1.1.0"
|
|
66
67
|
},
|
|
67
68
|
"peerDependencies": {
|
|
68
69
|
"@playwright/test": "^1.55"
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"test:integration": "playwright test",
|
|
76
77
|
"playwright:install": "playwright install chromium",
|
|
77
78
|
"compile": "tsgo",
|
|
78
|
-
"build": "tsdown"
|
|
79
|
+
"build": "tsdown",
|
|
80
|
+
"clean": "rimraf .turbo dist playwright-report test-results coverage"
|
|
79
81
|
}
|
|
80
82
|
}
|