@cronn/element-snapshot 0.14.0 → 0.15.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/dist/browser-lib.iife.js +89 -95
- package/dist/index.d.mts +16 -2
- package/dist/index.mjs +91 -45
- package/package.json +6 -6
package/dist/browser-lib.iife.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
(function() {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
//#region src/browser/attribute.ts
|
|
2
|
+
//#region src/browser/attribute.ts
|
|
5
3
|
function stringAttribute(value) {
|
|
6
4
|
return value !== null && value !== "" ? value : void 0;
|
|
7
5
|
}
|
|
@@ -22,15 +20,16 @@
|
|
|
22
20
|
if (referenceId === null) return null;
|
|
23
21
|
return element.ownerDocument.getElementById(referenceId);
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
//#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/utils/factories.ts
|
|
25
|
+
function textSnapshot(name) {
|
|
26
|
+
return {
|
|
27
|
+
role: "text",
|
|
28
|
+
name
|
|
29
|
+
};
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
//#
|
|
33
|
-
//#region src/utils/guards.ts
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/utils/guards.ts
|
|
34
33
|
function isTextSnapshot(snapshot) {
|
|
35
34
|
return hasRole("text", snapshot);
|
|
36
35
|
}
|
|
@@ -38,9 +37,8 @@
|
|
|
38
37
|
if (typeof role === "string") return role === snapshot.role;
|
|
39
38
|
return role.includes(snapshot.role);
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
//#
|
|
43
|
-
//#region src/utils/filter.ts
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/utils/filter.ts
|
|
44
42
|
function filter(options) {
|
|
45
43
|
const { predicate, snapshots, recurse = false } = options;
|
|
46
44
|
return snapshots.flatMap((snapshot) => {
|
|
@@ -63,24 +61,39 @@
|
|
|
63
61
|
function hasChildren(snapshot) {
|
|
64
62
|
return "children" in snapshot && snapshot.children.length > 0;
|
|
65
63
|
}
|
|
66
|
-
|
|
67
|
-
//#
|
|
68
|
-
|
|
69
|
-
/**
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils/text.ts
|
|
66
|
+
/**
|
|
70
67
|
* Aggregates and normalizes the text content of all provided snapshots
|
|
71
68
|
*/
|
|
72
69
|
function getTextContent(snapshots) {
|
|
73
|
-
return
|
|
70
|
+
return mergeTexts(filter({
|
|
74
71
|
snapshots,
|
|
75
72
|
predicate: isTextSnapshot
|
|
76
|
-
})
|
|
73
|
+
}));
|
|
77
74
|
}
|
|
78
75
|
function normalizeText(text) {
|
|
79
76
|
return text.replaceAll(/\s+/g, " ").trim();
|
|
80
77
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
function mergeTexts(texts) {
|
|
79
|
+
return normalizeText(texts.map((textNode) => textNode.name).join(" "));
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/browser/children.ts
|
|
83
|
+
function snapshotChildren(node, excludedNodes = []) {
|
|
84
|
+
return mergeConsecutiveTexts(Array.from(node.childNodes).map((childNode) => snapshotNodeRecursive(childNode, excludedNodes)).filter((childSnapshot) => childSnapshot !== null).flat());
|
|
85
|
+
}
|
|
86
|
+
function mergeConsecutiveTexts(children) {
|
|
87
|
+
return children.reduce((mergedChildren, child) => {
|
|
88
|
+
const lastMergedChildIndex = mergedChildren.length - 1;
|
|
89
|
+
const lastMergedChild = mergedChildren[lastMergedChildIndex];
|
|
90
|
+
if (lastMergedChild !== void 0 && isTextSnapshot(lastMergedChild) && isTextSnapshot(child)) mergedChildren[lastMergedChildIndex] = textSnapshot(mergeTexts([lastMergedChild, child]));
|
|
91
|
+
else mergedChildren.push(child);
|
|
92
|
+
return mergedChildren;
|
|
93
|
+
}, []);
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/browser/text.ts
|
|
84
97
|
function snapshotTextNode(textNode) {
|
|
85
98
|
if (textNode.textContent === null) return null;
|
|
86
99
|
const normalizedText = normalizeText(textNode.textContent);
|
|
@@ -104,18 +117,16 @@
|
|
|
104
117
|
const textContent = getTextContent(children);
|
|
105
118
|
return textContent.length > 0 ? textContent : void 0;
|
|
106
119
|
}
|
|
107
|
-
|
|
108
|
-
//#
|
|
109
|
-
//#region src/browser/name.ts
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/browser/name.ts
|
|
110
122
|
function resolveAccessibleName(element, includeTextContent = true) {
|
|
111
123
|
const labelledByElement = resolveElementReference(element, "aria-labelledby");
|
|
112
124
|
if (labelledByElement !== null) return resolveAccessibleTextContent(labelledByElement);
|
|
113
125
|
if (element.ariaLabel !== null) return element.ariaLabel;
|
|
114
126
|
if (includeTextContent) return resolveAccessibleTextContent(element);
|
|
115
127
|
}
|
|
116
|
-
|
|
117
|
-
//#
|
|
118
|
-
//#region src/browser/state.ts
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/browser/state.ts
|
|
119
130
|
function disableableAttributes(element) {
|
|
120
131
|
return { disabled: booleanAttribute(isNativeInputElement(element) || element instanceof HTMLButtonElement ? element.hasAttribute("disabled") : false) ?? booleanAttribute(element.ariaDisabled) };
|
|
121
132
|
}
|
|
@@ -136,9 +147,8 @@
|
|
|
136
147
|
function isNativeInputElement(element) {
|
|
137
148
|
return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement;
|
|
138
149
|
}
|
|
139
|
-
|
|
140
|
-
//#
|
|
141
|
-
//#region src/browser/button.ts
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/browser/button.ts
|
|
142
152
|
const PRESSED_VALUES = new Set([
|
|
143
153
|
"true",
|
|
144
154
|
"mixed",
|
|
@@ -161,9 +171,8 @@
|
|
|
161
171
|
if (pressed === "mixed") return pressed;
|
|
162
172
|
return booleanAttribute(pressed === "true");
|
|
163
173
|
}
|
|
164
|
-
|
|
165
|
-
//#
|
|
166
|
-
//#region src/browser/description.ts
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/browser/description.ts
|
|
167
176
|
function discribableAttributes(element) {
|
|
168
177
|
return { description: resolveDescription(element) };
|
|
169
178
|
}
|
|
@@ -172,9 +181,8 @@
|
|
|
172
181
|
if (describedByElement !== null) return resolveAccessibleTextContent(describedByElement);
|
|
173
182
|
if (element.ariaDescription !== null) return element.ariaDescription;
|
|
174
183
|
}
|
|
175
|
-
|
|
176
|
-
//#
|
|
177
|
-
//#region src/browser/container.ts
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/browser/container.ts
|
|
178
186
|
const CONTAINER_ROLES = new Set([
|
|
179
187
|
"paragraph",
|
|
180
188
|
"list",
|
|
@@ -202,13 +210,15 @@
|
|
|
202
210
|
"alert",
|
|
203
211
|
"menu",
|
|
204
212
|
"tablist",
|
|
205
|
-
"tabpanel"
|
|
213
|
+
"tabpanel",
|
|
214
|
+
"label"
|
|
206
215
|
]);
|
|
216
|
+
const NAMEABLE_CONTAINER_ROLES = new Set(["label"]);
|
|
207
217
|
function snapshotContainer(role, element) {
|
|
208
218
|
const children = snapshotChildren(element);
|
|
209
219
|
return {
|
|
210
220
|
role,
|
|
211
|
-
name: resolveAccessibleName(element,
|
|
221
|
+
name: resolveAccessibleName(element, NAMEABLE_CONTAINER_ROLES.has(role)),
|
|
212
222
|
attributes: {},
|
|
213
223
|
children
|
|
214
224
|
};
|
|
@@ -216,9 +226,8 @@
|
|
|
216
226
|
function isContainerRole(role) {
|
|
217
227
|
return CONTAINER_ROLES.has(role);
|
|
218
228
|
}
|
|
219
|
-
|
|
220
|
-
//#
|
|
221
|
-
//#region src/browser/selector.ts
|
|
229
|
+
//#endregion
|
|
230
|
+
//#region src/browser/selector.ts
|
|
222
231
|
function selectorList(...selectors) {
|
|
223
232
|
return selectors.join(",");
|
|
224
233
|
}
|
|
@@ -228,18 +237,16 @@
|
|
|
228
237
|
function attributeSelector(name, value) {
|
|
229
238
|
return `[${name}='${value}']`;
|
|
230
239
|
}
|
|
231
|
-
|
|
232
|
-
//#
|
|
233
|
-
//#region src/browser/utils.ts
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/browser/utils.ts
|
|
234
242
|
function getElementTagName(element) {
|
|
235
243
|
return element.tagName.toLowerCase();
|
|
236
244
|
}
|
|
237
245
|
function isWithinElement(element, containerSelector) {
|
|
238
246
|
return element.closest(containerSelector) !== null;
|
|
239
247
|
}
|
|
240
|
-
|
|
241
|
-
//#
|
|
242
|
-
//#region src/browser/role.ts
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/browser/role.ts
|
|
243
250
|
const ELEMENT_ROLES = {
|
|
244
251
|
main: "main",
|
|
245
252
|
nav: "navigation",
|
|
@@ -279,7 +286,9 @@
|
|
|
279
286
|
fieldset: "group",
|
|
280
287
|
progress: "progressbar",
|
|
281
288
|
hr: "separator",
|
|
282
|
-
img: "img"
|
|
289
|
+
img: "img",
|
|
290
|
+
label: "label",
|
|
291
|
+
legend: "label"
|
|
283
292
|
};
|
|
284
293
|
const CONTEXT_DEPENDENT_ROLES = {
|
|
285
294
|
banner: isTopLevelBanner,
|
|
@@ -397,9 +406,8 @@
|
|
|
397
406
|
function isWithinTablist(element) {
|
|
398
407
|
return isWithinElement(element, roleSelector("tablist"));
|
|
399
408
|
}
|
|
400
|
-
|
|
401
|
-
//#
|
|
402
|
-
//#region src/browser/input.ts
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/browser/input.ts
|
|
403
411
|
function snapshotInput(element) {
|
|
404
412
|
if (element instanceof HTMLInputElement) return snapshotInputElement(element);
|
|
405
413
|
if (element instanceof HTMLTextAreaElement) return snapshotTextareaElement(element);
|
|
@@ -462,9 +470,8 @@
|
|
|
462
470
|
if (element instanceof HTMLSelectElement || element instanceof HTMLButtonElement) return;
|
|
463
471
|
return stringAttribute(element.placeholder);
|
|
464
472
|
}
|
|
465
|
-
|
|
466
|
-
//#
|
|
467
|
-
//#region src/browser/combobox.ts
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/browser/combobox.ts
|
|
468
475
|
function snapshotCombobox(element) {
|
|
469
476
|
if (!isCombobox(element)) return null;
|
|
470
477
|
const options = snapshotOptions(element);
|
|
@@ -511,9 +518,8 @@
|
|
|
511
518
|
children: snapshotChildren(element)
|
|
512
519
|
};
|
|
513
520
|
}
|
|
514
|
-
|
|
515
|
-
//#
|
|
516
|
-
//#region src/browser/dialog.ts
|
|
521
|
+
//#endregion
|
|
522
|
+
//#region src/browser/dialog.ts
|
|
517
523
|
function snapshotDialogWithRole(role) {
|
|
518
524
|
return (element) => snapshotDialog(role, element);
|
|
519
525
|
}
|
|
@@ -528,9 +534,8 @@
|
|
|
528
534
|
children: snapshotChildren(element)
|
|
529
535
|
};
|
|
530
536
|
}
|
|
531
|
-
|
|
532
|
-
//#
|
|
533
|
-
//#region src/browser/group.ts
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/browser/group.ts
|
|
534
539
|
function snapshotGroup(element) {
|
|
535
540
|
return {
|
|
536
541
|
role: "group",
|
|
@@ -552,9 +557,8 @@
|
|
|
552
557
|
children: snapshotChildren(element)
|
|
553
558
|
};
|
|
554
559
|
}
|
|
555
|
-
|
|
556
|
-
//#
|
|
557
|
-
//#region src/browser/heading.ts
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region src/browser/heading.ts
|
|
558
562
|
const LEVEL_REGEXP = /^H([1-6])$/;
|
|
559
563
|
function snapshotHeading(element) {
|
|
560
564
|
if (!(element instanceof HTMLHeadingElement)) return null;
|
|
@@ -568,9 +572,8 @@
|
|
|
568
572
|
function resolveLevel(element) {
|
|
569
573
|
return numericAttribute(LEVEL_REGEXP.exec(element.nodeName)?.at(1) ?? null);
|
|
570
574
|
}
|
|
571
|
-
|
|
572
|
-
//#
|
|
573
|
-
//#region src/browser/image.ts
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/browser/image.ts
|
|
574
577
|
function snapshotImage(element) {
|
|
575
578
|
const imageName = resolveImageName(element);
|
|
576
579
|
if (imageName === void 0) return null;
|
|
@@ -585,9 +588,8 @@
|
|
|
585
588
|
if (element instanceof HTMLImageElement) return stringAttribute(element.alt);
|
|
586
589
|
return resolveAccessibleName(element, true);
|
|
587
590
|
}
|
|
588
|
-
|
|
589
|
-
//#
|
|
590
|
-
//#region src/browser/link.ts
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/browser/link.ts
|
|
591
593
|
function snapshotLink(element) {
|
|
592
594
|
return {
|
|
593
595
|
role: "link",
|
|
@@ -599,9 +601,8 @@
|
|
|
599
601
|
function linkAttributes(element) {
|
|
600
602
|
return { url: stringAttribute(element.getAttribute("href")) };
|
|
601
603
|
}
|
|
602
|
-
|
|
603
|
-
//#
|
|
604
|
-
//#region src/browser/list.ts
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/browser/list.ts
|
|
605
606
|
function snapshotMenuitem(element) {
|
|
606
607
|
return {
|
|
607
608
|
role: "menuitem",
|
|
@@ -613,9 +614,8 @@
|
|
|
613
614
|
children: snapshotChildren(element)
|
|
614
615
|
};
|
|
615
616
|
}
|
|
616
|
-
|
|
617
|
-
//#
|
|
618
|
-
//#region src/browser/progressbar.ts
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/browser/progressbar.ts
|
|
619
619
|
function snapshotProgressbar(element) {
|
|
620
620
|
return {
|
|
621
621
|
role: "progressbar",
|
|
@@ -628,9 +628,8 @@
|
|
|
628
628
|
if (element instanceof HTMLProgressElement) return element.value;
|
|
629
629
|
return numericAttribute(element.ariaValueNow);
|
|
630
630
|
}
|
|
631
|
-
|
|
632
|
-
//#
|
|
633
|
-
//#region src/browser/separator.ts
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/browser/separator.ts
|
|
634
633
|
function snapshotSeparator(element) {
|
|
635
634
|
return {
|
|
636
635
|
role: "separator",
|
|
@@ -638,9 +637,8 @@
|
|
|
638
637
|
children: snapshotPresentationalChildren(element)
|
|
639
638
|
};
|
|
640
639
|
}
|
|
641
|
-
|
|
642
|
-
//#
|
|
643
|
-
//#region src/browser/tab.ts
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region src/browser/tab.ts
|
|
644
642
|
function snapshotTab(element) {
|
|
645
643
|
return {
|
|
646
644
|
role: "tab",
|
|
@@ -652,9 +650,8 @@
|
|
|
652
650
|
children: snapshotChildren(element)
|
|
653
651
|
};
|
|
654
652
|
}
|
|
655
|
-
|
|
656
|
-
//#
|
|
657
|
-
//#region src/browser/table.ts
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/browser/table.ts
|
|
658
655
|
const SORT_TYPES = new Set([
|
|
659
656
|
"ascending",
|
|
660
657
|
"descending",
|
|
@@ -668,9 +665,8 @@
|
|
|
668
665
|
children: snapshotChildren(element)
|
|
669
666
|
};
|
|
670
667
|
}
|
|
671
|
-
|
|
672
|
-
//#
|
|
673
|
-
//#region src/browser/element.ts
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region src/browser/element.ts
|
|
674
670
|
const ROLE_SNAPSHOTS = {
|
|
675
671
|
button: snapshotButton,
|
|
676
672
|
checkbox: snapshotInput,
|
|
@@ -739,10 +735,8 @@
|
|
|
739
735
|
const cssStyles = window.getComputedStyle(element);
|
|
740
736
|
return cssStyles.display === "none" || cssStyles.visibility === "hidden" || cssStyles.visibility === "collapse";
|
|
741
737
|
}
|
|
742
|
-
|
|
743
|
-
//#
|
|
744
|
-
//#region src/browser-lib.ts
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/browser-lib.ts
|
|
745
740
|
window.snapshotElement = snapshotElement;
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
})();
|
|
741
|
+
//#endregion
|
|
742
|
+
})();
|
package/dist/index.d.mts
CHANGED
|
@@ -51,7 +51,7 @@ interface OptionAttributes {
|
|
|
51
51
|
}
|
|
52
52
|
//#endregion
|
|
53
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">;
|
|
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
55
|
interface ContainerSnapshot extends GenericElementSnapshot<ContainerRole> {}
|
|
56
56
|
type ContainerRole = SetValues<typeof CONTAINER_ROLES>;
|
|
57
57
|
//#endregion
|
|
@@ -126,6 +126,20 @@ 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/playwright/markdown-table.d.ts
|
|
130
|
+
interface MarkdownTableSnapshotOptions {
|
|
131
|
+
/**
|
|
132
|
+
* Show sort indicator for column headers
|
|
133
|
+
*
|
|
134
|
+
* @default true
|
|
135
|
+
*/
|
|
136
|
+
showSortIndicator?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Creates a Markdown table snapshot from a table or grid element
|
|
140
|
+
*/
|
|
141
|
+
declare function markdownTableSnapshot(locator: Locator, options?: MarkdownTableSnapshotOptions): Promise<string>;
|
|
142
|
+
//#endregion
|
|
129
143
|
//#region src/types/snapshot.d.ts
|
|
130
144
|
type SnapshotByRole<TRole extends NodeRole> = NodeSnapshot & {
|
|
131
145
|
role: TRole;
|
|
@@ -230,4 +244,4 @@ declare function excludeRole<TRole extends NodeRole>(role: TRole | Array<TRole>)
|
|
|
230
244
|
*/
|
|
231
245
|
declare function getTextContent(snapshots: Array<NodeSnapshot>): string;
|
|
232
246
|
//#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 };
|
|
247
|
+
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 SeparatorSnapshot, type SnapshotByRole, type TabSnapshot, type TextSnapshot, excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, snapshotElement, snapshotElementRaw };
|
package/dist/index.mjs
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
|
+
import { markdownTable } from "markdown-table";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { packageDirectory } from "package-directory";
|
|
3
|
-
|
|
4
|
-
//#region src/playwright/proxy.ts
|
|
5
|
-
var ElementSnapshotProxy = class {
|
|
6
|
-
page;
|
|
7
|
-
constructor(page) {
|
|
8
|
-
this.page = page;
|
|
9
|
-
}
|
|
10
|
-
async snapshotElement(locator) {
|
|
11
|
-
if (!await this.isLibraryLoaded()) await this.loadLibrary();
|
|
12
|
-
return await locator.evaluate((element) => {
|
|
13
|
-
return window.snapshotElement(element);
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
async isLibraryLoaded() {
|
|
17
|
-
return await this.page.evaluate(() => typeof window.snapshotElement === "function");
|
|
18
|
-
}
|
|
19
|
-
async loadLibrary() {
|
|
20
|
-
const packageRootDir = await this.resolvePackageRootDir();
|
|
21
|
-
const libPath = path.resolve(packageRootDir, "dist", "browser-lib.iife.js");
|
|
22
|
-
await this.page.addScriptTag({ path: libPath });
|
|
23
|
-
}
|
|
24
|
-
async resolvePackageRootDir() {
|
|
25
|
-
const packageDir = await packageDirectory({ cwd: import.meta.dirname });
|
|
26
|
-
if (packageDir === void 0) throw new Error("Unable to resolve root directory of package");
|
|
27
|
-
return packageDir;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
//#endregion
|
|
32
4
|
//#region src/utils/guards.ts
|
|
33
5
|
function isTextSnapshot(snapshot) {
|
|
34
6
|
return hasRole("text", snapshot);
|
|
@@ -41,7 +13,6 @@ function isEmpty(value) {
|
|
|
41
13
|
if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
|
|
42
14
|
return Object.keys(value).length === 0;
|
|
43
15
|
}
|
|
44
|
-
|
|
45
16
|
//#endregion
|
|
46
17
|
//#region src/utils/predicates.ts
|
|
47
18
|
/**
|
|
@@ -56,7 +27,6 @@ function includeRole(role) {
|
|
|
56
27
|
function excludeRole(role) {
|
|
57
28
|
return (snapshot) => !hasRole(role, snapshot);
|
|
58
29
|
}
|
|
59
|
-
|
|
60
30
|
//#endregion
|
|
61
31
|
//#region src/utils/filter.ts
|
|
62
32
|
function filter(options) {
|
|
@@ -91,7 +61,50 @@ function filterByRole(role, snapshots) {
|
|
|
91
61
|
function hasChildren(snapshot) {
|
|
92
62
|
return "children" in snapshot && snapshot.children.length > 0;
|
|
93
63
|
}
|
|
94
|
-
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/utils/text.ts
|
|
66
|
+
/**
|
|
67
|
+
* Aggregates and normalizes the text content of all provided snapshots
|
|
68
|
+
*/
|
|
69
|
+
function getTextContent(snapshots) {
|
|
70
|
+
return mergeTexts(filter({
|
|
71
|
+
snapshots,
|
|
72
|
+
predicate: isTextSnapshot
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
function normalizeText(text) {
|
|
76
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
77
|
+
}
|
|
78
|
+
function mergeTexts(texts) {
|
|
79
|
+
return normalizeText(texts.map((textNode) => textNode.name).join(" "));
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/playwright/proxy.ts
|
|
83
|
+
var ElementSnapshotProxy = class {
|
|
84
|
+
page;
|
|
85
|
+
constructor(page) {
|
|
86
|
+
this.page = page;
|
|
87
|
+
}
|
|
88
|
+
async snapshotElement(locator) {
|
|
89
|
+
if (!await this.isLibraryLoaded()) await this.loadLibrary();
|
|
90
|
+
return await locator.evaluate((element) => {
|
|
91
|
+
return window.snapshotElement(element);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async isLibraryLoaded() {
|
|
95
|
+
return await this.page.evaluate(() => typeof window.snapshotElement === "function");
|
|
96
|
+
}
|
|
97
|
+
async loadLibrary() {
|
|
98
|
+
const packageRootDir = await this.resolvePackageRootDir();
|
|
99
|
+
const libPath = path.resolve(packageRootDir, "dist", "browser-lib.iife.js");
|
|
100
|
+
await this.page.addScriptTag({ path: libPath });
|
|
101
|
+
}
|
|
102
|
+
async resolvePackageRootDir() {
|
|
103
|
+
const packageDir = await packageDirectory({ cwd: import.meta.dirname });
|
|
104
|
+
if (packageDir === void 0) throw new Error("Unable to resolve root directory of package");
|
|
105
|
+
return packageDir;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
95
108
|
//#endregion
|
|
96
109
|
//#region src/playwright/transformer.ts
|
|
97
110
|
var ElementSnapshotTransformer = class {
|
|
@@ -190,7 +203,6 @@ var ElementSnapshotTransformer = class {
|
|
|
190
203
|
return { [role]: content };
|
|
191
204
|
}
|
|
192
205
|
};
|
|
193
|
-
|
|
194
206
|
//#endregion
|
|
195
207
|
//#region src/playwright/snapshot.ts
|
|
196
208
|
/**
|
|
@@ -216,21 +228,55 @@ async function snapshotElement(locator, options) {
|
|
|
216
228
|
async function snapshotElementRaw(locator) {
|
|
217
229
|
return await new ElementSnapshotProxy(locator.page()).snapshotElement(locator);
|
|
218
230
|
}
|
|
219
|
-
|
|
220
231
|
//#endregion
|
|
221
|
-
//#region src/
|
|
232
|
+
//#region src/playwright/markdown-table.ts
|
|
233
|
+
const tableRoles = ["table", "grid"];
|
|
234
|
+
const cellRoles = [
|
|
235
|
+
"rowheader",
|
|
236
|
+
"cell",
|
|
237
|
+
"gridcell"
|
|
238
|
+
];
|
|
239
|
+
const sortIndicators = {
|
|
240
|
+
ascending: "⯅",
|
|
241
|
+
descending: "⯆",
|
|
242
|
+
other: "⯁"
|
|
243
|
+
};
|
|
222
244
|
/**
|
|
223
|
-
*
|
|
245
|
+
* Creates a Markdown table snapshot from a table or grid element
|
|
224
246
|
*/
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
predicate: isTextSnapshot
|
|
229
|
-
}).map((textNode) => textNode.name).join(" "));
|
|
247
|
+
async function markdownTableSnapshot(locator, options = {}) {
|
|
248
|
+
const { columnHeaders, rows } = parseTable(await snapshotElementRaw(locator), options);
|
|
249
|
+
return markdownTable([columnHeaders, ...rows]);
|
|
230
250
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
251
|
+
function parseTable(snapshot, options) {
|
|
252
|
+
const { showSortIndicator = true } = options;
|
|
253
|
+
const tableOrGridResult = filter({
|
|
254
|
+
predicate: includeRole(tableRoles),
|
|
255
|
+
snapshots: snapshot
|
|
256
|
+
});
|
|
257
|
+
if (tableOrGridResult.length > 1) throw new Error("Multiple tables or grids found");
|
|
258
|
+
const [tableOrGrid] = tableOrGridResult;
|
|
259
|
+
if (tableOrGrid === void 0) throw new Error("No table or grid found");
|
|
260
|
+
const [headerRow, ...bodyRows] = filterByRole("row", tableOrGrid.children);
|
|
261
|
+
if (headerRow === void 0) throw new Error("No header row found");
|
|
262
|
+
const columnHeaders = filterByRole("columnheader", headerRow.children);
|
|
263
|
+
if (columnHeaders.length === 0) throw new Error("No column headers found");
|
|
264
|
+
return {
|
|
265
|
+
columnHeaders: columnHeaders.map((columnHeader) => getColumnHeaderText(columnHeader, showSortIndicator)),
|
|
266
|
+
rows: bodyRows.map(getCellTexts)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function getColumnHeaderText(columnHeader, showSortIndicator) {
|
|
270
|
+
const headerText = getTextContent([columnHeader]);
|
|
271
|
+
const sortType = columnHeader.attributes.sort;
|
|
272
|
+
if (!showSortIndicator || sortType === void 0) return headerText;
|
|
273
|
+
return `${headerText} ${sortIndicators[sortType]}`;
|
|
274
|
+
}
|
|
275
|
+
function getCellTexts(row) {
|
|
276
|
+
return filter({
|
|
277
|
+
predicate: includeRole(cellRoles),
|
|
278
|
+
snapshots: row.children
|
|
279
|
+
}).map((cell) => getTextContent(cell.children));
|
|
233
280
|
}
|
|
234
|
-
|
|
235
281
|
//#endregion
|
|
236
|
-
export { excludeRole, filter, filterByRole, getTextContent, includeRole, snapshotElement, snapshotElementRaw };
|
|
282
|
+
export { excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, snapshotElement, snapshotElementRaw };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/element-snapshot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Element snapshots for Playwright",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/cronn/file-snapshots/issues"
|
|
@@ -47,20 +47,20 @@
|
|
|
47
47
|
"@arethetypeswrong/core": "0.18.2",
|
|
48
48
|
"@playwright/test": "1.58.2",
|
|
49
49
|
"@trivago/prettier-plugin-sort-imports": "6.0.2",
|
|
50
|
-
"@types/node": "24.
|
|
51
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
50
|
+
"@types/node": "24.12.0",
|
|
51
|
+
"@typescript/native-preview": "7.0.0-dev.20260309.1",
|
|
52
52
|
"@vitest/coverage-v8": "4.0.18",
|
|
53
|
-
"eslint": "10.0.
|
|
53
|
+
"eslint": "10.0.3",
|
|
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
|
-
"tsdown": "0.
|
|
59
|
+
"tsdown": "0.21.1",
|
|
60
60
|
"typescript-eslint": "8.56.1",
|
|
61
61
|
"vitest": "4.0.18",
|
|
62
|
-
"@cronn/shared-configs": "0.0.0",
|
|
63
62
|
"@cronn/playwright-file-snapshots": "1.0.1",
|
|
63
|
+
"@cronn/shared-configs": "0.0.0",
|
|
64
64
|
"@cronn/test-utils": "0.0.0",
|
|
65
65
|
"@cronn/vitest-file-snapshots": "1.0.1"
|
|
66
66
|
},
|