@futdevpro/dynamo-e2e 1.18.25 → 1.18.29

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +149 -139
  3. package/build/_modules/contracts/_collections/test-case-sets/password-field-test-cases.const.d.ts +4 -0
  4. package/build/_modules/contracts/_collections/test-case-sets/password-field-test-cases.const.d.ts.map +1 -1
  5. package/build/_modules/contracts/_collections/test-case-sets/password-field-test-cases.const.js +39 -3
  6. package/build/_modules/contracts/_collections/test-case-sets/password-field-test-cases.const.js.map +1 -1
  7. package/build/_modules/contracts/_enums/field-test-case-type.enum.d.ts +5 -0
  8. package/build/_modules/contracts/_enums/field-test-case-type.enum.d.ts.map +1 -1
  9. package/build/_modules/contracts/_enums/field-test-case-type.enum.js +5 -0
  10. package/build/_modules/contracts/_enums/field-test-case-type.enum.js.map +1 -1
  11. package/build/_modules/contracts/_models/interfaces/form-test-suite-settings.interface.d.ts +9 -1
  12. package/build/_modules/contracts/_models/interfaces/form-test-suite-settings.interface.d.ts.map +1 -1
  13. package/build/_modules/generators/_collections/field-emission.util.d.ts +11 -0
  14. package/build/_modules/generators/_collections/field-emission.util.d.ts.map +1 -1
  15. package/build/_modules/generators/_collections/field-emission.util.js +43 -6
  16. package/build/_modules/generators/_collections/field-emission.util.js.map +1 -1
  17. package/build/_modules/generators/_collections/form-level-emission.util.d.ts.map +1 -1
  18. package/build/_modules/generators/_collections/form-level-emission.util.js +5 -6
  19. package/build/_modules/generators/_collections/form-level-emission.util.js.map +1 -1
  20. package/build/_modules/generators/_collections/json-schema.util.d.ts.map +1 -1
  21. package/build/_modules/generators/_collections/json-schema.util.js +2 -0
  22. package/build/_modules/generators/_collections/json-schema.util.js.map +1 -1
  23. package/build/_modules/generators/_collections/test-case-filter.util.d.ts +6 -0
  24. package/build/_modules/generators/_collections/test-case-filter.util.d.ts.map +1 -1
  25. package/build/_modules/generators/_collections/test-case-filter.util.js +36 -0
  26. package/build/_modules/generators/_collections/test-case-filter.util.js.map +1 -1
  27. package/build/_modules/generators/_models/form-settings-shape.interface.d.ts +13 -0
  28. package/build/_modules/generators/_models/form-settings-shape.interface.d.ts.map +1 -1
  29. package/build/_modules/generators/form-suite.generator.d.ts.map +1 -1
  30. package/build/_modules/generators/form-suite.generator.js +4 -1
  31. package/build/_modules/generators/form-suite.generator.js.map +1 -1
  32. package/build/_modules/runtime/_collections/notification-detector-page-script.const.js +136 -136
  33. package/build/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.d.ts +6 -0
  34. package/build/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.d.ts.map +1 -0
  35. package/build/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.js +208 -0
  36. package/build/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.js.map +1 -0
  37. package/build/_modules/ui-check/_collections/detectors/dead-control.detector.d.ts +29 -0
  38. package/build/_modules/ui-check/_collections/detectors/dead-control.detector.d.ts.map +1 -0
  39. package/build/_modules/ui-check/_collections/detectors/dead-control.detector.js +101 -0
  40. package/build/_modules/ui-check/_collections/detectors/dead-control.detector.js.map +1 -0
  41. package/build/_modules/ui-check/_collections/detectors/nested-scroll.detector.d.ts +25 -0
  42. package/build/_modules/ui-check/_collections/detectors/nested-scroll.detector.d.ts.map +1 -0
  43. package/build/_modules/ui-check/_collections/detectors/nested-scroll.detector.js +47 -0
  44. package/build/_modules/ui-check/_collections/detectors/nested-scroll.detector.js.map +1 -0
  45. package/build/_modules/ui-check/_collections/detectors/non-toggling-control.detector.d.ts +33 -0
  46. package/build/_modules/ui-check/_collections/detectors/non-toggling-control.detector.d.ts.map +1 -0
  47. package/build/_modules/ui-check/_collections/detectors/non-toggling-control.detector.js +119 -0
  48. package/build/_modules/ui-check/_collections/detectors/non-toggling-control.detector.js.map +1 -0
  49. package/build/_modules/ui-check/_collections/ui-check-default-detectors.const.d.ts +20 -0
  50. package/build/_modules/ui-check/_collections/ui-check-default-detectors.const.d.ts.map +1 -0
  51. package/build/_modules/ui-check/_collections/ui-check-default-detectors.const.js +30 -0
  52. package/build/_modules/ui-check/_collections/ui-check-default-detectors.const.js.map +1 -0
  53. package/build/_modules/ui-check/_collections/ui-check-guard.util.d.ts +60 -0
  54. package/build/_modules/ui-check/_collections/ui-check-guard.util.d.ts.map +1 -0
  55. package/build/_modules/ui-check/_collections/ui-check-guard.util.js +144 -0
  56. package/build/_modules/ui-check/_collections/ui-check-guard.util.js.map +1 -0
  57. package/build/_modules/ui-check/_collections/ui-check-page.util.d.ts +61 -0
  58. package/build/_modules/ui-check/_collections/ui-check-page.util.d.ts.map +1 -0
  59. package/build/_modules/ui-check/_collections/ui-check-page.util.js +152 -0
  60. package/build/_modules/ui-check/_collections/ui-check-page.util.js.map +1 -0
  61. package/build/_modules/ui-check/_collections/ui-check-runner.util.d.ts +41 -0
  62. package/build/_modules/ui-check/_collections/ui-check-runner.util.d.ts.map +1 -0
  63. package/build/_modules/ui-check/_collections/ui-check-runner.util.js +128 -0
  64. package/build/_modules/ui-check/_collections/ui-check-runner.util.js.map +1 -0
  65. package/build/_modules/ui-check/_models/ui-check-context.interface.d.ts +39 -0
  66. package/build/_modules/ui-check/_models/ui-check-context.interface.d.ts.map +1 -0
  67. package/build/_modules/ui-check/_models/ui-check-context.interface.js +3 -0
  68. package/build/_modules/ui-check/_models/ui-check-context.interface.js.map +1 -0
  69. package/build/_modules/ui-check/_models/ui-check-detector.interface.d.ts +32 -0
  70. package/build/_modules/ui-check/_models/ui-check-detector.interface.d.ts.map +1 -0
  71. package/build/_modules/ui-check/_models/ui-check-detector.interface.js +3 -0
  72. package/build/_modules/ui-check/_models/ui-check-detector.interface.js.map +1 -0
  73. package/build/_modules/ui-check/_models/ui-check-finding.interface.d.ts +30 -0
  74. package/build/_modules/ui-check/_models/ui-check-finding.interface.d.ts.map +1 -0
  75. package/build/_modules/ui-check/_models/ui-check-finding.interface.js +3 -0
  76. package/build/_modules/ui-check/_models/ui-check-finding.interface.js.map +1 -0
  77. package/build/_modules/ui-check/_models/ui-check-report.interface.d.ts +38 -0
  78. package/build/_modules/ui-check/_models/ui-check-report.interface.d.ts.map +1 -0
  79. package/build/_modules/ui-check/_models/ui-check-report.interface.js +3 -0
  80. package/build/_modules/ui-check/_models/ui-check-report.interface.js.map +1 -0
  81. package/build/_modules/ui-check/index.d.ts +27 -0
  82. package/build/_modules/ui-check/index.d.ts.map +1 -0
  83. package/build/_modules/ui-check/index.js +43 -0
  84. package/build/_modules/ui-check/index.js.map +1 -0
  85. package/build/_modules/vqa/generators/review-bundle.generator.d.ts +16 -0
  86. package/build/_modules/vqa/generators/review-bundle.generator.d.ts.map +1 -1
  87. package/build/_modules/vqa/generators/review-bundle.generator.js +11 -0
  88. package/build/_modules/vqa/generators/review-bundle.generator.js.map +1 -1
  89. package/build/index.d.ts +1 -0
  90. package/build/index.d.ts.map +1 -1
  91. package/build/index.js +2 -1
  92. package/build/index.js.map +1 -1
  93. package/package.json +10 -1
@@ -22,115 +22,115 @@ exports.DYE2E_NOTIFICATION_BINDING_NAME = '__dye2e_notification_binding__';
22
22
  * → error · `bg-orange-50` / `yellow-snackbar` / `warning` → warning · `bg-green-50` / `green-snackbar` /
23
23
  * `check_circle` → success · `bg-blue-50` / `blue-snackbar` → info.
24
24
  */
25
- exports.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT = `
26
- (function () {
27
- var w = window;
28
- if (w.__dye2e_notifications__) { return; }
29
- var list = [];
30
- w.__dye2e_notifications__ = list;
31
- var bindingName = '${exports.DYE2E_NOTIFICATION_BINDING_NAME}';
32
- var perf = w.performance;
33
- var origin = String(Math.round(perf && perf.timeOrigin ? perf.timeOrigin : Date.now()));
34
- var counter = 0;
35
- var recentKeys = {};
36
- var recorded = {};
37
- var checks = [
38
- ['dynx-card', 'dynamo-notification-card'],
39
- ['notif-area', 'dynamo-notifications-area'],
40
- ['snackbar', 'mat-snack-bar-container, .mat-mdc-snack-bar-container, simple-snack-bar, ' +
41
- '.red-snackbar, .yellow-snackbar, .green-snackbar, .blue-snackbar'],
42
- ['role-alert', '[role="alert"], [role="alertdialog"]']
43
- ];
44
-
45
- function nowMs() { return perf ? perf.now() : 0; }
46
- function squash(text) { return (text || '').replace(/\\s+/g, ' ').trim(); }
47
- function textOf(el, selector) {
48
- try {
49
- var node = el.querySelector(selector);
50
- return node ? squash(node.textContent) : '';
51
- } catch (e) { return ''; }
52
- }
53
- function classify(el) {
54
- var html = el.outerHTML || '';
55
- if (/bg-red-50|red-snackbar|>\\s*error\\s*</.test(html)) { return 'error'; }
56
- if (/bg-orange-50|yellow-snackbar|>\\s*warning\\s*</.test(html)) { return 'warning'; }
57
- if (/bg-green-50|green-snackbar|>\\s*check_circle\\s*</.test(html)) { return 'success'; }
58
- if (/bg-blue-50|blue-snackbar/.test(html)) { return 'info'; }
59
- return 'unknown';
60
- }
61
- function read(el) {
62
- var structured = textOf(el, '.text-slate-700') || textOf(el, '.mat-mdc-snack-bar-label') ||
63
- textOf(el, '[matsnackbarlabel]');
64
- return {
65
- type: classify(el),
66
- structured: !!structured,
67
- title: (structured || squash(el.textContent)).slice(0, 400),
68
- message: textOf(el, '.text-slate-500').slice(0, 400),
69
- html: squash(el.outerHTML).slice(0, 500)
70
- };
71
- }
72
- function emit(entry) {
73
- try {
74
- if (typeof w[bindingName] === 'function') { w[bindingName](entry); }
75
- } catch (e) { /* the page must never break because of the detector */ }
76
- }
77
- function enrich(entry, state, el) {
78
- if (!el.isConnected) { return; }
79
- var fresh = read(el);
80
- var changed = false;
81
- if (entry.type === 'unknown' && fresh.type !== 'unknown') { entry.type = fresh.type; changed = true; }
82
- if (fresh.title && fresh.title !== entry.title && (!entry.title || !state.structured)) {
83
- entry.title = fresh.title; state.structured = fresh.structured; changed = true;
84
- }
85
- if (!entry.message && fresh.message) { entry.message = fresh.message; changed = true; }
86
- if (changed) { entry.html = fresh.html; emit(entry); }
87
- }
88
- function record(kind, el) {
89
- var seen = recorded[kind] || (recorded[kind] = new WeakSet());
90
- if (seen.has(el)) { return; }
91
- seen.add(el);
92
- var data = read(el);
93
- var t = nowMs();
94
- var key = kind + '|' + data.type + '|' + data.title;
95
- if (data.title && recentKeys[key] !== undefined && t - recentKeys[key] < 1500) { return; }
96
- recentKeys[key] = t;
97
- counter += 1;
98
- var entry = {
99
- id: origin + '-' + counter,
100
- kind: kind,
101
- type: data.type,
102
- title: data.title,
103
- message: data.message,
104
- url: location.href,
105
- at: Date.now(),
106
- sinceDocumentStartMs: Math.round(t),
107
- html: data.html
108
- };
109
- var state = { structured: data.structured };
110
- list.push(entry);
111
- emit(entry);
112
- setTimeout(function () { enrich(entry, state, el); }, 50);
113
- setTimeout(function () { enrich(entry, state, el); }, 300);
114
- }
115
- function scan(node) {
116
- if (!node || node.nodeType !== 1) { return; }
117
- for (var i = 0; i < checks.length; i++) {
118
- try {
119
- if (node.matches(checks[i][1])) { record(checks[i][0], node); }
120
- var found = node.querySelectorAll(checks[i][1]);
121
- for (var j = 0; j < found.length; j++) { record(checks[i][0], found[j]); }
122
- } catch (e) { /* one bad selector must not stop the other checks */ }
123
- }
124
- }
125
-
126
- new MutationObserver(function (mutations) {
127
- for (var i = 0; i < mutations.length; i++) {
128
- var added = mutations[i].addedNodes;
129
- for (var j = 0; j < added.length; j++) { scan(added[j]); }
130
- }
131
- }).observe(document, { childList: true, subtree: true });
132
- scan(document.documentElement);
133
- })();
25
+ exports.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT = `
26
+ (function () {
27
+ var w = window;
28
+ if (w.__dye2e_notifications__) { return; }
29
+ var list = [];
30
+ w.__dye2e_notifications__ = list;
31
+ var bindingName = '${exports.DYE2E_NOTIFICATION_BINDING_NAME}';
32
+ var perf = w.performance;
33
+ var origin = String(Math.round(perf && perf.timeOrigin ? perf.timeOrigin : Date.now()));
34
+ var counter = 0;
35
+ var recentKeys = {};
36
+ var recorded = {};
37
+ var checks = [
38
+ ['dynx-card', 'dynamo-notification-card'],
39
+ ['notif-area', 'dynamo-notifications-area'],
40
+ ['snackbar', 'mat-snack-bar-container, .mat-mdc-snack-bar-container, simple-snack-bar, ' +
41
+ '.red-snackbar, .yellow-snackbar, .green-snackbar, .blue-snackbar'],
42
+ ['role-alert', '[role="alert"], [role="alertdialog"]']
43
+ ];
44
+
45
+ function nowMs() { return perf ? perf.now() : 0; }
46
+ function squash(text) { return (text || '').replace(/\\s+/g, ' ').trim(); }
47
+ function textOf(el, selector) {
48
+ try {
49
+ var node = el.querySelector(selector);
50
+ return node ? squash(node.textContent) : '';
51
+ } catch (e) { return ''; }
52
+ }
53
+ function classify(el) {
54
+ var html = el.outerHTML || '';
55
+ if (/bg-red-50|red-snackbar|>\\s*error\\s*</.test(html)) { return 'error'; }
56
+ if (/bg-orange-50|yellow-snackbar|>\\s*warning\\s*</.test(html)) { return 'warning'; }
57
+ if (/bg-green-50|green-snackbar|>\\s*check_circle\\s*</.test(html)) { return 'success'; }
58
+ if (/bg-blue-50|blue-snackbar/.test(html)) { return 'info'; }
59
+ return 'unknown';
60
+ }
61
+ function read(el) {
62
+ var structured = textOf(el, '.text-slate-700') || textOf(el, '.mat-mdc-snack-bar-label') ||
63
+ textOf(el, '[matsnackbarlabel]');
64
+ return {
65
+ type: classify(el),
66
+ structured: !!structured,
67
+ title: (structured || squash(el.textContent)).slice(0, 400),
68
+ message: textOf(el, '.text-slate-500').slice(0, 400),
69
+ html: squash(el.outerHTML).slice(0, 500)
70
+ };
71
+ }
72
+ function emit(entry) {
73
+ try {
74
+ if (typeof w[bindingName] === 'function') { w[bindingName](entry); }
75
+ } catch (e) { /* the page must never break because of the detector */ }
76
+ }
77
+ function enrich(entry, state, el) {
78
+ if (!el.isConnected) { return; }
79
+ var fresh = read(el);
80
+ var changed = false;
81
+ if (entry.type === 'unknown' && fresh.type !== 'unknown') { entry.type = fresh.type; changed = true; }
82
+ if (fresh.title && fresh.title !== entry.title && (!entry.title || !state.structured)) {
83
+ entry.title = fresh.title; state.structured = fresh.structured; changed = true;
84
+ }
85
+ if (!entry.message && fresh.message) { entry.message = fresh.message; changed = true; }
86
+ if (changed) { entry.html = fresh.html; emit(entry); }
87
+ }
88
+ function record(kind, el) {
89
+ var seen = recorded[kind] || (recorded[kind] = new WeakSet());
90
+ if (seen.has(el)) { return; }
91
+ seen.add(el);
92
+ var data = read(el);
93
+ var t = nowMs();
94
+ var key = kind + '|' + data.type + '|' + data.title;
95
+ if (data.title && recentKeys[key] !== undefined && t - recentKeys[key] < 1500) { return; }
96
+ recentKeys[key] = t;
97
+ counter += 1;
98
+ var entry = {
99
+ id: origin + '-' + counter,
100
+ kind: kind,
101
+ type: data.type,
102
+ title: data.title,
103
+ message: data.message,
104
+ url: location.href,
105
+ at: Date.now(),
106
+ sinceDocumentStartMs: Math.round(t),
107
+ html: data.html
108
+ };
109
+ var state = { structured: data.structured };
110
+ list.push(entry);
111
+ emit(entry);
112
+ setTimeout(function () { enrich(entry, state, el); }, 50);
113
+ setTimeout(function () { enrich(entry, state, el); }, 300);
114
+ }
115
+ function scan(node) {
116
+ if (!node || node.nodeType !== 1) { return; }
117
+ for (var i = 0; i < checks.length; i++) {
118
+ try {
119
+ if (node.matches(checks[i][1])) { record(checks[i][0], node); }
120
+ var found = node.querySelectorAll(checks[i][1]);
121
+ for (var j = 0; j < found.length; j++) { record(checks[i][0], found[j]); }
122
+ } catch (e) { /* one bad selector must not stop the other checks */ }
123
+ }
124
+ }
125
+
126
+ new MutationObserver(function (mutations) {
127
+ for (var i = 0; i < mutations.length; i++) {
128
+ var added = mutations[i].addedNodes;
129
+ for (var j = 0; j < added.length; j++) { scan(added[j]); }
130
+ }
131
+ }).observe(document, { childList: true, subtree: true });
132
+ scan(document.documentElement);
133
+ })();
134
134
  `;
135
135
  /**
136
136
  * Page-side expression: the visible error banners / inline error texts at this moment (not toasts — e.g. a login
@@ -138,32 +138,32 @@ exports.DYE2E_NOTIFICATION_DETECTOR_PAGE_SCRIPT = `
138
138
  * already counted banner is part of it, not a new one (measured: a `role=alert` box with its `.error-text` and icon
139
139
  * was counted three times). PURE JavaScript.
140
140
  */
141
- exports.DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION = `
142
- (function () {
143
- var selectors = ['.error-text', '[class*="error-text"]', 'mat-error', '.mat-mdc-form-field-error',
144
- '.text-red-200', '.text-red-500', '.text-red-600', '.text-red-700', '[role="alert"]'];
145
- var out = [];
146
- var seen = {};
147
- var picked = [];
148
- var found = document.querySelectorAll(selectors.join(','));
149
- for (var i = 0; i < found.length; i++) {
150
- var el = found[i];
151
- var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
152
- if (!text || text.length > 300 || !(el instanceof HTMLElement)) { continue; }
153
- var rect = el.getBoundingClientRect();
154
- var style = getComputedStyle(el);
155
- var visible = el.offsetParent !== null && rect.width > 0 && rect.height > 0 &&
156
- style.visibility !== 'hidden' && style.display !== 'none';
157
- var key = el.tagName + '|' + text;
158
- if (!visible || seen[key]) { continue; }
159
- var inside = false;
160
- for (var j = 0; j < picked.length; j++) { if (picked[j].contains(el)) { inside = true; break; } }
161
- if (inside) { continue; }
162
- seen[key] = true;
163
- picked.push(el);
164
- out.push({ tag: el.tagName.toLowerCase(), cls: String(el.className || '').slice(0, 120), text: text.slice(0, 260) });
165
- }
166
- return out;
167
- })()
141
+ exports.DYE2E_VISIBLE_ERRORS_PAGE_EXPRESSION = `
142
+ (function () {
143
+ var selectors = ['.error-text', '[class*="error-text"]', 'mat-error', '.mat-mdc-form-field-error',
144
+ '.text-red-200', '.text-red-500', '.text-red-600', '.text-red-700', '[role="alert"]'];
145
+ var out = [];
146
+ var seen = {};
147
+ var picked = [];
148
+ var found = document.querySelectorAll(selectors.join(','));
149
+ for (var i = 0; i < found.length; i++) {
150
+ var el = found[i];
151
+ var text = (el.textContent || '').replace(/\\s+/g, ' ').trim();
152
+ if (!text || text.length > 300 || !(el instanceof HTMLElement)) { continue; }
153
+ var rect = el.getBoundingClientRect();
154
+ var style = getComputedStyle(el);
155
+ var visible = el.offsetParent !== null && rect.width > 0 && rect.height > 0 &&
156
+ style.visibility !== 'hidden' && style.display !== 'none';
157
+ var key = el.tagName + '|' + text;
158
+ if (!visible || seen[key]) { continue; }
159
+ var inside = false;
160
+ for (var j = 0; j < picked.length; j++) { if (picked[j].contains(el)) { inside = true; break; } }
161
+ if (inside) { continue; }
162
+ seen[key] = true;
163
+ picked.push(el);
164
+ out.push({ tag: el.tagName.toLowerCase(), cls: String(el.className || '').slice(0, 120), text: text.slice(0, 260) });
165
+ }
166
+ return out;
167
+ })()
168
168
  `;
169
169
  //# sourceMappingURL=notification-detector-page-script.const.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Browser-side scripts of the UI detectors (BFR-FLEET-012). Each is the SOURCE of one function expression, called as
3
+ * `(${script})(${JSON.stringify(arg)})` through `page.evaluate` — DOM APIs only, no Node imports, JSON in and out.
4
+ */
5
+ export declare const DyE2E_UICheckPageScripts_Const: Readonly<Record<string, string>>;
6
+ //# sourceMappingURL=ui-check-page-scripts.const.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-check-page-scripts.const.d.ts","sourceRoot":"","sources":["../../../../../src/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6M3E,CAAC"}
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyE2E_UICheckPageScripts_Const = void 0;
4
+ /**
5
+ * Browser-side scripts of the UI detectors (BFR-FLEET-012). Each is the SOURCE of one function expression, called as
6
+ * `(${script})(${JSON.stringify(arg)})` through `page.evaluate` — DOM APIs only, no Node imports, JSON in and out.
7
+ */
8
+ exports.DyE2E_UICheckPageScripts_Const = {
9
+ /**
10
+ * Lists the visible, enabled controls of one kind. `press`: buttons and button-like elements (not form submits, not
11
+ * checkbox / switch internals); `toggle`: checkboxes and switches. Each comes with a unique CSS path.
12
+ */
13
+ listControls: `(arg) => {
14
+ const cssPath = (el) => {
15
+ const parts = [];
16
+ let node = el;
17
+ while (node && node.nodeType === 1 && node !== document.documentElement) {
18
+ if (node.id && /^[A-Za-z][\\w-]*$/.test(node.id) && document.querySelectorAll('#' + node.id).length === 1) {
19
+ parts.unshift('#' + node.id);
20
+ break;
21
+ }
22
+ const tag = node.tagName.toLowerCase();
23
+ const parent = node.parentElement;
24
+ const same = parent ? Array.from(parent.children).filter((c) => c.tagName === node.tagName) : [];
25
+ parts.unshift(same.length > 1 ? tag + ':nth-of-type(' + (same.indexOf(node) + 1) + ')' : tag);
26
+ node = parent;
27
+ }
28
+ return parts.join(' > ');
29
+ };
30
+ const visible = (el) => {
31
+ const r = el.getBoundingClientRect();
32
+ const cs = getComputedStyle(el);
33
+ return r.width > 0 && r.height > 0 && cs.visibility !== 'hidden' && cs.display !== 'none'
34
+ && (typeof el.checkVisibility !== 'function' || el.checkVisibility());
35
+ };
36
+ const disabled = (el) => el.disabled === true || el.getAttribute('aria-disabled') === 'true'
37
+ || !!el.closest('[inert], fieldset[disabled]');
38
+ const selector = arg.kind === 'toggle'
39
+ ? 'input[type="checkbox"], [role="checkbox"], [role="switch"]'
40
+ : 'button, [role="button"], a:not([href]), a[href="#"], a[href=""], [role="menuitem"], [role="tab"]';
41
+ // a modal dialog open (a visible dialog + a backdrop, or aria-modal) ⇒ only ITS controls are reachable
42
+ const dialogs = Array.from(document.querySelectorAll(
43
+ '[role="dialog"], [role="alertdialog"], mat-dialog-container, .mat-mdc-dialog-container, dialog[open]'))
44
+ .filter((d) => visible(d));
45
+ const backdrop = Array.from(document.querySelectorAll('.cdk-overlay-backdrop-showing, .modal-backdrop'))
46
+ .some((b) => visible(b));
47
+ const modal = dialogs.length && (backdrop || dialogs.some((d) => d.getAttribute('aria-modal') === 'true'))
48
+ ? dialogs[dialogs.length - 1] : null;
49
+ const out = [];
50
+ for (const el of Array.from((modal || document).querySelectorAll(selector))) {
51
+ if (arg.kind === 'press') {
52
+ if (el.closest('[role="checkbox"], [role="switch"], [role="radio"], label') && el.tagName !== 'BUTTON') continue;
53
+ if (el.tagName === 'BUTTON' && (el.type || 'submit') === 'submit' && el.form) continue;
54
+ if (el.closest('input, select, textarea')) continue;
55
+ }
56
+ if (arg.kind === 'toggle' && el.tagName !== 'INPUT' && el.querySelector('input[type="checkbox"]')) continue;
57
+ const probe = el.tagName === 'INPUT' && !visible(el) ? (el.closest('label, [class*="checkbox"]') || el) : el;
58
+ if (!visible(probe) || disabled(el)) continue;
59
+ const labelEl = el.tagName === 'INPUT'
60
+ ? (el.labels && el.labels[0]) || el.closest('label, [class*="checkbox"], [class*="toggle"]') : el;
61
+ out.push({
62
+ selector: cssPath(el),
63
+ tag: el.tagName.toLowerCase(),
64
+ text: ((labelEl && labelEl.textContent) || '').replace(/\\s+/g, ' ').trim().slice(0, 120),
65
+ testId: el.getAttribute('data-testid') || el.getAttribute('data-test-id') || null,
66
+ ariaLabel: el.getAttribute('aria-label'),
67
+ title: el.getAttribute('title') || '',
68
+ destructive: !!el.closest('[data-e2e-destructive]'),
69
+ });
70
+ if (out.length >= arg.max) break;
71
+ }
72
+ return out;
73
+ }`,
74
+ /** Starts counting MEANINGFUL DOM mutations (ripple, tooltip, focus-class and live-announcer noise ignored). */
75
+ startMutationWatch: `() => {
76
+ const noise = '.mat-ripple-element, .mat-ripple, .mdc-button__ripple, .mat-mdc-button-ripple, '
77
+ + '.mat-mdc-button-persistent-ripple, .mat-focus-indicator, mat-tooltip-component, .mat-mdc-tooltip, '
78
+ + '.mdc-tooltip, .mat-mdc-tooltip-panel, .cdk-describedby-message-container, .cdk-live-announcer-element, '
79
+ + '.cdk-visually-hidden, [role="tooltip"]';
80
+ const isNoise = (node) => {
81
+ const el = node && node.nodeType === 1 ? node : node && node.parentElement;
82
+ if (!el) return true;
83
+ // <head> changes are invisible (e.g. a style injected on the first ripple) — only the <title> counts
84
+ if (el.closest('head') && !el.closest('title')) return true;
85
+ if (el.closest(noise)) return true;
86
+ // a wrapper that only carries noise (e.g. the overlay pane of a tooltip)
87
+ const inner = Array.from(el.querySelectorAll(noise));
88
+ return inner.length > 0 && (el.textContent || '').trim()
89
+ === inner.map((n) => (n.textContent || '').trim()).join('').trim();
90
+ };
91
+ const noiseClass = /focus|ripple|hover|active|mouse|keyboard|touch|program|animat|transition/i;
92
+ const noiseAttr = /^(mat-ripple-loader|ng-reflect-|aria-describedby$|tabindex$|data-mat-|cdk-focus)/i;
93
+ const state = { count: 0, samples: [] };
94
+ const note = (text) => { state.count++; if (state.samples.length < 5) state.samples.push(text); };
95
+ const observer = new MutationObserver((records) => {
96
+ for (const r of records) {
97
+ if (r.type === 'childList') {
98
+ const nodes = Array.from(r.addedNodes).concat(Array.from(r.removedNodes))
99
+ .filter((n) => n.nodeType === 1 || (n.nodeType === 3 && (n.textContent || '').trim()));
100
+ if (nodes.length && !isNoise(r.target) && nodes.some((n) => !isNoise(n))) {
101
+ note('childList ' + (r.target.tagName || '').toLowerCase());
102
+ }
103
+ } else if (r.type === 'characterData') {
104
+ if (!isNoise(r.target)) note('text ' + (r.target.textContent || '').trim().slice(0, 40));
105
+ } else if (r.type === 'attributes') {
106
+ // the framework's own bookkeeping attributes (lazy ripple loader, Angular reflect, focus/describedby)
107
+ if (isNoise(r.target) || noiseAttr.test(r.attributeName || '')) continue;
108
+ if (r.attributeName === 'class') {
109
+ const before = new Set((r.oldValue || '').split(/\\s+/).filter(Boolean));
110
+ const after = new Set((r.target.getAttribute('class') || '').split(/\\s+/).filter(Boolean));
111
+ const changed = [...before].filter((c) => !after.has(c)).concat([...after].filter((c) => !before.has(c)));
112
+ if (changed.every((c) => noiseClass.test(c))) continue;
113
+ }
114
+ note('attr ' + r.attributeName + ' ' + (r.target.tagName || '').toLowerCase());
115
+ }
116
+ }
117
+ });
118
+ observer.observe(document.documentElement, { subtree: true, childList: true, characterData: true,
119
+ attributes: true, attributeOldValue: true });
120
+ // effects with no DOM trace: clipboard copy, storage write, a message to another window
121
+ const restore = [];
122
+ const hook = (owner, name, label) => {
123
+ if (!owner || typeof owner[name] !== 'function') return;
124
+ const original = owner[name];
125
+ owner[name] = function () { note(label); return original.apply(this, arguments); };
126
+ restore.push(() => { owner[name] = original; });
127
+ };
128
+ if (navigator.clipboard) {
129
+ hook(navigator.clipboard, 'writeText', 'clipboard write');
130
+ hook(navigator.clipboard, 'write', 'clipboard write');
131
+ }
132
+ hook(document, 'execCommand', 'execCommand');
133
+ hook(Storage.prototype, 'setItem', 'storage write');
134
+ hook(Storage.prototype, 'removeItem', 'storage write');
135
+ hook(window, 'postMessage', 'postMessage');
136
+ if (window.parent && window.parent !== window) {
137
+ try { hook(window.parent, 'postMessage', 'postMessage to parent'); } catch (e) { /* cross-origin parent */ }
138
+ }
139
+ window.__dye2eUiMut = { state: state, observer: observer, restore: restore };
140
+ return true;
141
+ }`,
142
+ /** Stops the mutation watch and returns `{ count, samples }`. */
143
+ readMutationWatch: `() => {
144
+ const w = window.__dye2eUiMut;
145
+ if (!w) return { count: -1, samples: [ 'the watch was lost (the page navigated)' ] };
146
+ w.observer.disconnect();
147
+ w.restore.forEach((undo) => undo());
148
+ delete window.__dye2eUiMut;
149
+ return w.state;
150
+ }`,
151
+ /** The state of a checkbox / switch: `on`, `off` or `missing`. */
152
+ toggleState: `(selector) => {
153
+ const el = document.querySelector(selector);
154
+ if (!el) return 'missing';
155
+ if (el.tagName === 'INPUT') return el.checked ? 'on' : 'off';
156
+ return el.getAttribute('aria-checked') === 'true' ? 'on' : 'off';
157
+ }`,
158
+ /** The visible modal dialogs, with the texts of their buttons. */
159
+ openDialogs: `() => {
160
+ const dialogs = Array.from(document.querySelectorAll(
161
+ '[role="dialog"], [role="alertdialog"], mat-dialog-container, .mat-mdc-dialog-container, dialog[open]'))
162
+ .filter((d) => { const r = d.getBoundingClientRect(); return r.width > 0 && r.height > 0; });
163
+ return dialogs.map((d) => Array.from(d.querySelectorAll('button, [role="button"], a'))
164
+ .map((b) => (b.textContent || '').replace(/\\s+/g, ' ').trim()).filter(Boolean));
165
+ }`,
166
+ /**
167
+ * The two-level scroll measurement: does the DOCUMENT scroll, and is there an inner scroll container that holds the
168
+ * main content (≥ the given share of the viewport height and width)? Small inner scrollers (lists, textareas,
169
+ * drop-downs) are not counted.
170
+ */
171
+ nestedScroll: `(arg) => {
172
+ const root = document.scrollingElement || document.documentElement;
173
+ const vh = window.innerHeight;
174
+ const vw = window.innerWidth;
175
+ const documentScrolls = root.scrollHeight > vh + 1
176
+ && getComputedStyle(document.body).overflowY !== 'hidden'
177
+ && getComputedStyle(document.documentElement).overflowY !== 'hidden';
178
+ const cssPath = (el) => {
179
+ const parts = [];
180
+ let node = el;
181
+ while (node && node.nodeType === 1 && node !== document.documentElement) {
182
+ if (node.id && /^[A-Za-z][\\w-]*$/.test(node.id)) { parts.unshift('#' + node.id); break; }
183
+ const tag = node.tagName.toLowerCase();
184
+ const parent = node.parentElement;
185
+ const same = parent ? Array.from(parent.children).filter((c) => c.tagName === node.tagName) : [];
186
+ parts.unshift(same.length > 1 ? tag + ':nth-of-type(' + (same.indexOf(node) + 1) + ')' : tag);
187
+ node = parent;
188
+ }
189
+ return parts.join(' > ');
190
+ };
191
+ const inner = [];
192
+ for (const el of Array.from(document.querySelectorAll('body *'))) {
193
+ if (/^(TEXTAREA|SELECT|INPUT)$/.test(el.tagName) || el.closest('[role="listbox"], [role="menu"]')) continue;
194
+ const cs = getComputedStyle(el);
195
+ if (!/(auto|scroll|overlay)/.test(cs.overflowY) || el.scrollHeight <= el.clientHeight + 1) continue;
196
+ const r = el.getBoundingClientRect();
197
+ const visibleHeight = Math.min(r.bottom, vh) - Math.max(r.top, 0);
198
+ if (visibleHeight < vh * arg.minHeightShare || r.width < vw * arg.minWidthShare) continue;
199
+ inner.push({ selector: cssPath(el), tag: el.tagName.toLowerCase(),
200
+ className: (typeof el.className === 'string' ? el.className : '').slice(0, 80),
201
+ scrollHeight: el.scrollHeight, clientHeight: el.clientHeight, visibleHeight: Math.round(visibleHeight) });
202
+ }
203
+ return { documentScrolls: documentScrolls, documentScrollHeight: root.scrollHeight, viewportHeight: vh,
204
+ viewportWidth: vw, inner: inner.filter((a) => !inner.some((b) => b !== a && b.selector.startsWith(a.selector
205
+ + ' > '))) };
206
+ }`,
207
+ };
208
+ //# sourceMappingURL=ui-check-page-scripts.const.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-check-page-scripts.const.js","sourceRoot":"","sources":["../../../../../src/_modules/ui-check/_collections/constants/ui-check-page-scripts.const.ts"],"names":[],"mappings":";;;AAAA;;;GAGG;AACU,QAAA,8BAA8B,GAAqC;IAE9E;;;OAGG;IACH,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4DZ;IAEF,gHAAgH;IAChH,kBAAkB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkElB;IAEF,iEAAiE;IACjE,iBAAiB,EAAE;;;;;;;IAOjB;IAEF,kEAAkE;IAClE,WAAW,EAAE;;;;;IAKX;IAEF,kEAAkE;IAClE,WAAW,EAAE;;;;;;IAMX;IAEF;;;;OAIG;IACH,YAAY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmCZ;CACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { Page } from '@playwright/test';
2
+ import { DyE2E_UICheckContext_Interface } from '../../_models/ui-check-context.interface';
3
+ import { DyE2E_UICheckDetector_Interface, DyE2E_UICheckOrigin_Interface } from '../../_models/ui-check-detector.interface';
4
+ import { DyE2E_UICheckFinding_Interface } from '../../_models/ui-check-finding.interface';
5
+ /**
6
+ * A control that does nothing: every visible, enabled button-like control is pressed on a FRESH page (reloaded before
7
+ * each press, so the presses cannot hide each other), and the effect is measured — URL, meaningful DOM change, request,
8
+ * popup, download, native dialog. Nothing ⇒ a finding. A press that fails (the control is covered) is a finding too,
9
+ * and so is a console error the press caused.
10
+ *
11
+ * Safety (`DyE2E_UICheckGuard_Util`): test host + anonymous / throwaway account only; payment, e-mail, delete, logout,
12
+ * publish controls are never pressed; form submits are left to the form suites; mutating requests are aborted.
13
+ *
14
+ * Origin: `BL-QA-FDPUI-001` — the registration PAGE showed a close "X" that did nothing (owner, 2026-10-20).
15
+ */
16
+ export declare class DyE2E_DeadControl_Detector implements DyE2E_UICheckDetector_Interface {
17
+ readonly id: string;
18
+ readonly errorClass: string;
19
+ readonly description: string;
20
+ readonly origin: DyE2E_UICheckOrigin_Interface;
21
+ readonly interactive: boolean;
22
+ notes: string[];
23
+ run(page: Page, context: DyE2E_UICheckContext_Interface): Promise<DyE2E_UICheckFinding_Interface[]>;
24
+ /** The same control on the reloaded page (by tag, test id, label and text). */
25
+ private findAgain;
26
+ /** The findings of one press. */
27
+ private judge;
28
+ }
29
+ //# sourceMappingURL=dead-control.detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-control.detector.d.ts","sourceRoot":"","sources":["../../../../../src/_modules/ui-check/_collections/detectors/dead-control.detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,8BAA8B,EAAE,MAAM,0CAA0C,CAAC;AAC1F,OAAO,EAAE,+BAA+B,EAAE,6BAA6B,EAAE,MAAM,2CAA2C,CAAC;AAC3H,OAAO,EAAE,8BAA8B,EAAE,MAAM,0CAA0C,CAAC;AAQ1F;;;;;;;;;;GAUG;AACH,qBAAa,0BAA2B,YAAW,+BAA+B;IAEhF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAkB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAA4B;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAmE;IAC/F,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAE5C;IACF,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAQ;IACrC,KAAK,EAAE,MAAM,EAAE,CAAM;IAEf,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,8BAA8B,EAAE,CAAC;IA4CzG,+EAA+E;YACjE,SAAS;IAevB,iCAAiC;IACjC,OAAO,CAAC,KAAK;CAiCd"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DyE2E_DeadControl_Detector = void 0;
4
+ const ui_check_guard_util_1 = require("../ui-check-guard.util");
5
+ const ui_check_page_util_1 = require("../ui-check-page.util");
6
+ /**
7
+ * A control that does nothing: every visible, enabled button-like control is pressed on a FRESH page (reloaded before
8
+ * each press, so the presses cannot hide each other), and the effect is measured — URL, meaningful DOM change, request,
9
+ * popup, download, native dialog. Nothing ⇒ a finding. A press that fails (the control is covered) is a finding too,
10
+ * and so is a console error the press caused.
11
+ *
12
+ * Safety (`DyE2E_UICheckGuard_Util`): test host + anonymous / throwaway account only; payment, e-mail, delete, logout,
13
+ * publish controls are never pressed; form submits are left to the form suites; mutating requests are aborted.
14
+ *
15
+ * Origin: `BL-QA-FDPUI-001` — the registration PAGE showed a close "X" that did nothing (owner, 2026-10-20).
16
+ */
17
+ class DyE2E_DeadControl_Detector {
18
+ id = 'dead-control';
19
+ errorClass = 'control-without-effect';
20
+ description = 'A visible, enabled control that changes nothing when pressed.';
21
+ origin = {
22
+ code: 'BL-QA-FDPUI-001', foundBy: 'owner (+ QA-1 round 126)', date: '2026-10-20',
23
+ };
24
+ interactive = true;
25
+ notes = [];
26
+ async run(page, context) {
27
+ const guard = await ui_check_guard_util_1.DyE2E_UICheckGuard_Util.installNetworkGuard(page, context);
28
+ const findings = [];
29
+ /** Console errors the page logs without any press (background noise, e.g. a failing socket). */
30
+ const background = new Set();
31
+ this.notes = [];
32
+ try {
33
+ await ui_check_page_util_1.DyE2E_UICheckPage_Util.reload(page, context, background);
34
+ const listed = await ui_check_page_util_1.DyE2E_UICheckPage_Util.listControls(page, context, 'press');
35
+ if (listed.denied.length) {
36
+ this.notes.push(`${listed.denied.length} control(s) not pressed (deny list): ` + listed.denied
37
+ .map((c) => `"${c.text || c.ariaLabel || c.testId}"`).join(', '));
38
+ }
39
+ for (const control of listed.controls) {
40
+ await ui_check_page_util_1.DyE2E_UICheckPage_Util.reload(page, context, background);
41
+ // an earlier press may have changed the page for good (a stored choice) ⇒ find the control again by identity
42
+ const current = await this.findAgain(page, context, control);
43
+ if (!current) {
44
+ this.notes.push(`"${control.text || control.ariaLabel || control.testId}" (${control.selector}) was not ` +
45
+ 'found again after the reload — not measured.');
46
+ continue;
47
+ }
48
+ const effect = await ui_check_page_util_1.DyE2E_UICheckPage_Util.press(page, current.selector, guard, context, background);
49
+ if (context.trace) {
50
+ this.notes.push(`trace "${current.text || current.ariaLabel || current.testId}" ${current.selector}: ` +
51
+ `url ${effect.urlAfter === effect.urlBefore ? 'same' : '→ ' + effect.urlAfter}, mutations ` +
52
+ `${effect.mutations} [${effect.mutationSamples.join('; ')}], requests ${effect.requests}, popups ` +
53
+ `${effect.popups}, dialogs ${effect.nativeDialogs}, error ${effect.pressError ?? '-'}`);
54
+ }
55
+ findings.push(...this.judge(current, effect, context, listed.denied.length));
56
+ }
57
+ }
58
+ finally {
59
+ await guard.remove();
60
+ }
61
+ return findings;
62
+ }
63
+ /** The same control on the reloaded page (by tag, test id, label and text). */
64
+ async findAgain(page, context, control) {
65
+ const listed = await ui_check_page_util_1.DyE2E_UICheckPage_Util.listControls(page, context, 'press');
66
+ const wanted = ui_check_page_util_1.DyE2E_UICheckPage_Util.signature(control);
67
+ return listed.controls.find((c) => c.selector === control.selector
68
+ && ui_check_page_util_1.DyE2E_UICheckPage_Util.signature(c) === wanted)
69
+ ?? listed.controls.find((c) => ui_check_page_util_1.DyE2E_UICheckPage_Util.signature(c) === wanted);
70
+ }
71
+ /** The findings of one press. */
72
+ judge(control, effect, context, deniedCount) {
73
+ const label = control.text || control.ariaLabel || control.testId || control.selector;
74
+ const base = {
75
+ detector: this.id,
76
+ url: context.url,
77
+ target: ui_check_page_util_1.DyE2E_UICheckPage_Util.toTarget(control),
78
+ details: { ...effect, deniedControlsOnPage: deniedCount },
79
+ };
80
+ const findings = [];
81
+ if (effect.pressError) {
82
+ findings.push({ ...base, errorClass: 'control-not-pressable',
83
+ measured: `"${label}" is visible and enabled, but pressing it failed: ${effect.pressError}`,
84
+ expected: 'A visible, enabled control can be pressed (nothing covers it).' });
85
+ }
86
+ else if (!effect.hadEffect) {
87
+ findings.push({ ...base, errorClass: this.errorClass,
88
+ measured: `Pressing "${label}" changed nothing: same URL, no DOM change, no request, no popup, no dialog.`,
89
+ expected: 'A control does something visible when pressed — or it is not shown / shown disabled.' });
90
+ }
91
+ if (effect.consoleErrors.length) {
92
+ findings.push({ ...base, errorClass: 'console-error-on-press',
93
+ measured: `Pressing "${label}" logged ${effect.consoleErrors.length} console error(s): ` +
94
+ effect.consoleErrors.slice(0, 2).join(' | '),
95
+ expected: 'A press logs no error.' });
96
+ }
97
+ return findings;
98
+ }
99
+ }
100
+ exports.DyE2E_DeadControl_Detector = DyE2E_DeadControl_Detector;
101
+ //# sourceMappingURL=dead-control.detector.js.map