@cronn/element-snapshot 0.16.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.
@@ -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/browser/container.ts
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
- main: "main",
252
- nav: "navigation",
253
- form: "form",
254
- header: "banner",
255
- section: "region",
272
+ a: "link",
256
273
  article: "article",
257
274
  aside: "complementary",
258
- search: "search",
275
+ button: "button",
276
+ dd: "definition",
277
+ dl: "descriptionlist",
278
+ dt: "term",
279
+ fieldset: "group",
259
280
  footer: "contentinfo",
260
- p: "paragraph",
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
- ul: "list",
268
- ol: "list",
269
- li: "listitem",
270
- a: "link",
271
- button: "button",
288
+ header: "banner",
289
+ hr: "separator",
290
+ img: "img",
272
291
  input: resolveInputRole,
273
- textarea: "textbox",
274
- select: "combobox",
292
+ label: "label",
293
+ legend: "label",
294
+ li: "listitem",
295
+ main: "main",
296
+ nav: "navigation",
297
+ ol: "list",
275
298
  option: "option",
276
- dl: "descriptionlist",
277
- dt: "term",
278
- dd: "definition",
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
- td: "cell",
286
- fieldset: "group",
287
- progress: "progressbar",
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
- region: hasAccessibleName,
296
- term: isWithinDescriptionList,
316
+ cell: isWithinTableRow,
317
+ columnheader: isWithinTableRowOrGridRow,
297
318
  definition: isWithinDescriptionList,
319
+ gridcell: isWithinGridRow,
298
320
  listitem: isWithinList,
299
321
  menuitem: isWithinMenu,
300
- rowgroup: isWithinTableOrGrid,
322
+ region: hasAccessibleName,
301
323
  row: isWithinTableOrGrid,
302
- columnheader: isWithinTableRowOrGridRow,
324
+ rowgroup: isWithinTableOrGrid,
303
325
  rowheader: isWithinTableRowOrGridRow,
304
- cell: isWithinTableRow,
305
- gridcell: isWithinGridRow,
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
- const value = resolveInputValue(element);
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, value === void 0)
427
- },
428
- children: []
429
- };
446
+ ...snapshotCommonInputAttributes(element)
447
+ }
448
+ });
430
449
  }
431
450
  function snapshotTextareaElement(element) {
432
- const value = resolveInputValue(element);
433
- return {
451
+ return elementSnapshot({
434
452
  role: "textbox",
435
453
  name: resolveInputLabel(element),
436
454
  attributes: {
437
- value,
438
- ...snapshotCommonInputAttributes(element, value === void 0)
439
- },
440
- children: []
441
- };
455
+ value: resolveInputValue(element),
456
+ ...snapshotCommonInputAttributes(element)
457
+ }
458
+ });
442
459
  }
443
- function snapshotCommonInputAttributes(element, isEmpty) {
460
+ function snapshotCommonInputAttributes(element) {
444
461
  return {
445
462
  ...discribableAttributes(element),
446
- placeholder: isEmpty ? resolvePlaceholder(element) : void 0,
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, value === void 0)
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
- return resolveOptions(element).map(snapshotOption).filter((childSnapshot) => childSnapshot !== null);
520
+ const optionsContainer = resolveOptionsContainer(element);
521
+ if (optionsContainer === null) return [];
522
+ return filterByRole("option", snapshotChildren(optionsContainer));
505
523
  }
506
- function resolveOptions(element) {
507
- if (element instanceof HTMLSelectElement) return Array.from(element.options);
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
- const optionSelector = selectorList("option", roleSelector("option"));
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: { selected: element instanceof HTMLOptionElement ? booleanAttribute(element.selected) : booleanAttribute(element.ariaSelected) },
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 snapshotRadiogroup(element) {
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
- attributes: {},
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/browser/table.ts
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
- function snapshotColumnheader(element) {
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
- option: snapshotOption,
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
- menuitem: snapshotMenuitem,
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
- "template"
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/browser/state.d.ts
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/browser/button.d.ts
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/browser/description.d.ts
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/browser/group.d.ts
34
+ //#region src/types/elements/group.d.ts
66
35
  interface GroupSnapshot extends GenericElementSnapshot<"group"> {}
67
- interface RadiogroupSnapshot extends GenericElementSnapshot<"radiogroup", RadiogroupAttributes> {}
36
+ interface RadioGroupSnapshot extends GenericElementSnapshot<"radiogroup", RadiogroupAttributes> {}
68
37
  interface RadiogroupAttributes extends DiscribableAttributes {}
69
38
  //#endregion
70
- //#region src/browser/heading.d.ts
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/browser/image.d.ts
45
+ //#region src/types/elements/image.d.ts
77
46
  interface ImageSnapshot extends GenericElementSnapshot<"img"> {}
78
47
  //#endregion
79
- //#region src/browser/link.d.ts
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/browser/list.d.ts
86
- interface MenuitemSnapshot extends GenericElementSnapshot<"menuitem", MenuitemAttributes> {}
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/browser/progressbar.d.ts
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/browser/separator.d.ts
83
+ //#region src/types/elements/separator.d.ts
96
84
  interface SeparatorSnapshot extends GenericElementSnapshot<"separator"> {}
97
85
  //#endregion
98
- //#region src/browser/tab.d.ts
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/browser/table.d.ts
103
- interface ColumnheaderSnapshot extends GenericElementSnapshot<"columnheader", ColumnheaderAttributes> {}
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/browser/text.d.ts
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/browser/types.d.ts
117
- type NodeRole = ElementRole | "text";
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 ElementSnapshot = ContainerSnapshot | HeadingSnapshot | LinkSnapshot | ButtonSnapshot | InputSnapshot | ComboboxSnapshot | OptionSnapshot | DialogSnapshot | TabSnapshot | MenuitemSnapshot | ColumnheaderSnapshot | GroupSnapshot | RadiogroupSnapshot | ProgressbarSnapshot | ImageSnapshot | SeparatorSnapshot;
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 SetValues<TSet> = TSet extends Set<infer TValue> ? TValue : never;
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;
@@ -247,4 +243,4 @@ declare function excludeRole<TRole extends NodeRole>(role: TRole | Array<TRole>)
247
243
  */
248
244
  declare function getTextContent(snapshots: Array<NodeSnapshot>): string;
249
245
  //#endregion
250
- 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, rawSnapshot, semanticSnapshot };
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 { markdownTable } from "markdown-table";
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
@@ -162,21 +162,21 @@ var SemanticSnapshotTransformer = 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 sparseAttributes = this.removeUndefinedProperties(snapshot.attributes);
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: sparseAttributes,
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 SemanticSnapshotTransformer = class {
192
192
  hasOnlyChildren(snapshot) {
193
193
  return isEmpty(snapshot.name) && isEmpty(snapshot.attributes) && !isEmpty(snapshot.children);
194
194
  }
195
- removeUndefinedProperties(attributes) {
196
- const sparseAttributes = {};
195
+ filterAttributes(attributes, exclude) {
196
+ const filteredAttributes = {};
197
197
  Object.entries(attributes).forEach(([key, value]) => {
198
- if (value !== void 0) sparseAttributes[key] = value;
198
+ if (value === void 0 && !exclude.includes(key)) return;
199
+ filteredAttributes[key] = value;
199
200
  });
200
- return sparseAttributes;
201
+ return filteredAttributes;
201
202
  }
202
203
  transformedSnapshot(role, content) {
203
204
  return { [role]: content };
@@ -248,8 +249,8 @@ const sortIndicators = {
248
249
  * Creates a Markdown table snapshot from a table or grid element
249
250
  */
250
251
  async function markdownTableSnapshot(locator, options = {}) {
251
- const { columnHeaders, rows } = parseTable(await rawSnapshot(locator), options);
252
- return markdownTable([columnHeaders, ...rows]);
252
+ const parsedTable = parseTable(await rawSnapshot(locator), options);
253
+ return new MarkdownTableSerializer().serialize(parsedTable);
253
254
  }
254
255
  function parseTable(snapshot, options) {
255
256
  const { showSortIndicator = true } = options;
@@ -265,7 +266,7 @@ function parseTable(snapshot, options) {
265
266
  const columnHeaders = filterByRole("columnheader", headerRow.children);
266
267
  if (columnHeaders.length === 0) throw new Error("No column headers found");
267
268
  return {
268
- columnHeaders: columnHeaders.map((columnHeader) => getColumnHeaderText(columnHeader, showSortIndicator)),
269
+ columns: columnHeaders.map((columnHeader) => getColumnHeaderText(columnHeader, showSortIndicator)),
269
270
  rows: bodyRows.map(getCellTexts)
270
271
  };
271
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cronn/element-snapshot",
3
- "version": "0.16.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"
@@ -40,29 +40,30 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "markdown-table": "3.0.4",
44
- "package-directory": "8.2.0"
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.58.2",
48
+ "@playwright/test": "1.59.1",
49
49
  "@trivago/prettier-plugin-sort-imports": "6.0.2",
50
- "@types/node": "24.12.0",
51
- "@typescript/native-preview": "7.0.0-dev.20260323.1",
52
- "@vitest/coverage-v8": "4.1.0",
53
- "eslint": "10.1.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
- "tsdown": "0.21.4",
60
- "typescript-eslint": "8.57.1",
61
- "vitest": "4.1.0",
62
- "@cronn/playwright-file-snapshots": "1.0.2",
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.2"
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
  }