@cronn/element-snapshot 0.14.1 → 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.
@@ -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
- //#endregion
27
- //#region src/browser/children.ts
28
- function snapshotChildren(node, excludedNodes = []) {
29
- return Array.from(node.childNodes).map((childNode) => snapshotNodeRecursive(childNode, excludedNodes)).filter((childSnapshot) => childSnapshot !== null).flat();
23
+ //#endregion
24
+ //#region src/utils/factories.ts
25
+ function textSnapshot(name) {
26
+ return {
27
+ role: "text",
28
+ name
29
+ };
30
30
  }
31
-
32
- //#endregion
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
- //#endregion
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
- //#endregion
68
- //#region src/utils/text.ts
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 normalizeText(filter({
70
+ return mergeTexts(filter({
74
71
  snapshots,
75
72
  predicate: isTextSnapshot
76
- }).map((textNode) => textNode.name).join(" "));
73
+ }));
77
74
  }
78
75
  function normalizeText(text) {
79
76
  return text.replaceAll(/\s+/g, " ").trim();
80
77
  }
81
-
82
- //#endregion
83
- //#region src/browser/text.ts
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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, false),
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
744
- //#region src/browser-lib.ts
738
+ //#endregion
739
+ //#region src/browser-lib.ts
745
740
  window.snapshotElement = snapshotElement;
746
-
747
- //#endregion
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
@@ -127,7 +127,7 @@ interface GenericElementSnapshot<TRole extends NodeRole = NodeRole, TAttributes
127
127
  type SetValues<TSet> = TSet extends Set<infer TValue> ? TValue : never;
128
128
  //#endregion
129
129
  //#region src/playwright/markdown-table.d.ts
130
- interface TableSnapshotOptions {
130
+ interface MarkdownTableSnapshotOptions {
131
131
  /**
132
132
  * Show sort indicator for column headers
133
133
  *
@@ -138,7 +138,7 @@ interface TableSnapshotOptions {
138
138
  /**
139
139
  * Creates a Markdown table snapshot from a table or grid element
140
140
  */
141
- declare function markdownTableSnapshot(locator: Locator, options?: TableSnapshotOptions): Promise<string>;
141
+ declare function markdownTableSnapshot(locator: Locator, options?: MarkdownTableSnapshotOptions): Promise<string>;
142
142
  //#endregion
143
143
  //#region src/types/snapshot.d.ts
144
144
  type SnapshotByRole<TRole extends NodeRole> = NodeSnapshot & {
@@ -244,4 +244,4 @@ declare function excludeRole<TRole extends NodeRole>(role: TRole | Array<TRole>)
244
244
  */
245
245
  declare function getTextContent(snapshots: Array<NodeSnapshot>): string;
246
246
  //#endregion
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 MenuitemSnapshot, type NodeRole, type NodeSnapshot, type OptionSnapshot, type ProgressbarSnapshot, type RadiogroupSnapshot, type SeparatorSnapshot, type TabSnapshot, type TextSnapshot, excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, 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,7 +1,6 @@
1
1
  import { markdownTable } from "markdown-table";
2
2
  import path from "node:path";
3
3
  import { packageDirectory } from "package-directory";
4
-
5
4
  //#region src/utils/guards.ts
6
5
  function isTextSnapshot(snapshot) {
7
6
  return hasRole("text", snapshot);
@@ -14,7 +13,6 @@ function isEmpty(value) {
14
13
  if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
15
14
  return Object.keys(value).length === 0;
16
15
  }
17
-
18
16
  //#endregion
19
17
  //#region src/utils/predicates.ts
20
18
  /**
@@ -29,7 +27,6 @@ function includeRole(role) {
29
27
  function excludeRole(role) {
30
28
  return (snapshot) => !hasRole(role, snapshot);
31
29
  }
32
-
33
30
  //#endregion
34
31
  //#region src/utils/filter.ts
35
32
  function filter(options) {
@@ -64,22 +61,23 @@ function filterByRole(role, snapshots) {
64
61
  function hasChildren(snapshot) {
65
62
  return "children" in snapshot && snapshot.children.length > 0;
66
63
  }
67
-
68
64
  //#endregion
69
65
  //#region src/utils/text.ts
70
66
  /**
71
67
  * Aggregates and normalizes the text content of all provided snapshots
72
68
  */
73
69
  function getTextContent(snapshots) {
74
- return normalizeText(filter({
70
+ return mergeTexts(filter({
75
71
  snapshots,
76
72
  predicate: isTextSnapshot
77
- }).map((textNode) => textNode.name).join(" "));
73
+ }));
78
74
  }
79
75
  function normalizeText(text) {
80
76
  return text.replaceAll(/\s+/g, " ").trim();
81
77
  }
82
-
78
+ function mergeTexts(texts) {
79
+ return normalizeText(texts.map((textNode) => textNode.name).join(" "));
80
+ }
83
81
  //#endregion
84
82
  //#region src/playwright/proxy.ts
85
83
  var ElementSnapshotProxy = class {
@@ -107,7 +105,6 @@ var ElementSnapshotProxy = class {
107
105
  return packageDir;
108
106
  }
109
107
  };
110
-
111
108
  //#endregion
112
109
  //#region src/playwright/transformer.ts
113
110
  var ElementSnapshotTransformer = class {
@@ -206,7 +203,6 @@ var ElementSnapshotTransformer = class {
206
203
  return { [role]: content };
207
204
  }
208
205
  };
209
-
210
206
  //#endregion
211
207
  //#region src/playwright/snapshot.ts
212
208
  /**
@@ -232,7 +228,6 @@ async function snapshotElement(locator, options) {
232
228
  async function snapshotElementRaw(locator) {
233
229
  return await new ElementSnapshotProxy(locator.page()).snapshotElement(locator);
234
230
  }
235
-
236
231
  //#endregion
237
232
  //#region src/playwright/markdown-table.ts
238
233
  const tableRoles = ["table", "grid"];
@@ -283,6 +278,5 @@ function getCellTexts(row) {
283
278
  snapshots: row.children
284
279
  }).map((cell) => getTextContent(cell.children));
285
280
  }
286
-
287
281
  //#endregion
288
- export { excludeRole, filter, filterByRole, getTextContent, includeRole, markdownTableSnapshot, 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.14.1",
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,22 +47,22 @@
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.11.0",
51
- "@typescript/native-preview": "7.0.0-dev.20260302.1",
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.2",
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.20.3",
59
+ "tsdown": "0.21.1",
60
60
  "typescript-eslint": "8.56.1",
61
61
  "vitest": "4.0.18",
62
62
  "@cronn/playwright-file-snapshots": "1.0.1",
63
+ "@cronn/shared-configs": "0.0.0",
63
64
  "@cronn/test-utils": "0.0.0",
64
- "@cronn/vitest-file-snapshots": "1.0.1",
65
- "@cronn/shared-configs": "0.0.0"
65
+ "@cronn/vitest-file-snapshots": "1.0.1"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@playwright/test": "^1.55"