@detiq/crawler 1.1.13 → 1.1.15

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 (112) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/accessibility-tree-crawler.js +156 -0
  3. package/dist/accessibility-tree-crawler.js.map +7 -0
  4. package/dist/advanced-ui-probes.js +310 -0
  5. package/dist/advanced-ui-probes.js.map +7 -0
  6. package/dist/content-chunker.js +2 -2
  7. package/dist/content-chunker.js.map +2 -2
  8. package/dist/crawl-session-manager.js +145 -0
  9. package/dist/crawl-session-manager.js.map +7 -0
  10. package/dist/crawler.js +1142 -263
  11. package/dist/crawler.js.map +2 -2
  12. package/dist/crud-interaction.js +264 -0
  13. package/dist/crud-interaction.js.map +7 -0
  14. package/dist/dropdown-enumerator.js +230 -0
  15. package/dist/dropdown-enumerator.js.map +7 -0
  16. package/dist/filter-pagination-crawler.js +299 -0
  17. package/dist/filter-pagination-crawler.js.map +7 -0
  18. package/dist/frontier.js +5 -3
  19. package/dist/frontier.js.map +2 -2
  20. package/dist/gap-pass.js +47 -14
  21. package/dist/gap-pass.js.map +2 -2
  22. package/dist/http-engine.js +28 -15
  23. package/dist/http-engine.js.map +2 -2
  24. package/dist/index.js +15 -1
  25. package/dist/index.js.map +2 -2
  26. package/dist/job-queue.js +12 -5
  27. package/dist/job-queue.js.map +2 -2
  28. package/dist/locator-extractor.js +23 -0
  29. package/dist/locator-extractor.js.map +2 -2
  30. package/dist/multi-context-handler.js +182 -0
  31. package/dist/multi-context-handler.js.map +7 -0
  32. package/dist/nested-window-crawler.js +172 -0
  33. package/dist/nested-window-crawler.js.map +7 -0
  34. package/dist/network-action-correlator.js +209 -0
  35. package/dist/network-action-correlator.js.map +7 -0
  36. package/dist/performance-metrics-collector.js +155 -0
  37. package/dist/performance-metrics-collector.js.map +7 -0
  38. package/dist/performance-metrics.js.map +2 -2
  39. package/dist/policies/crawl-safety.js +5 -1
  40. package/dist/policies/crawl-safety.js.map +2 -2
  41. package/dist/profiles/domain-profile.js +21 -1
  42. package/dist/profiles/domain-profile.js.map +2 -2
  43. package/dist/rate-limiter.js +2 -2
  44. package/dist/rate-limiter.js.map +2 -2
  45. package/dist/screen-reader.js +1 -0
  46. package/dist/screen-reader.js.map +2 -2
  47. package/dist/service-worker-interceptor.js +136 -0
  48. package/dist/service-worker-interceptor.js.map +7 -0
  49. package/dist/session-capture.js +4 -2
  50. package/dist/session-capture.js.map +2 -2
  51. package/dist/shadow-dom-crawler.js +132 -0
  52. package/dist/shadow-dom-crawler.js.map +7 -0
  53. package/dist/sitemap.js +16 -10
  54. package/dist/sitemap.js.map +2 -2
  55. package/dist/state-graph.js +193 -0
  56. package/dist/state-graph.js.map +7 -0
  57. package/dist/stealth/human-behavior.js +36 -3
  58. package/dist/stealth/human-behavior.js.map +2 -2
  59. package/dist/url-pattern-completer.js +48 -14
  60. package/dist/url-pattern-completer.js.map +2 -2
  61. package/dist/visual-diff-verdict.js +3 -1
  62. package/dist/visual-diff-verdict.js.map +2 -2
  63. package/package.json +2 -1
  64. package/src/accessibility-tree-crawler.ts +246 -0
  65. package/src/advanced-ui-probes.ts +470 -0
  66. package/src/crawl-session-manager.ts +231 -0
  67. package/src/crawler.ts +1071 -142
  68. package/src/crud-interaction.ts +386 -0
  69. package/src/dropdown-enumerator.ts +312 -0
  70. package/src/filter-pagination-crawler.ts +500 -0
  71. package/src/gap-pass.ts +58 -16
  72. package/src/http-engine.ts +29 -15
  73. package/src/index.ts +14 -1
  74. package/src/job-queue.ts +3 -1
  75. package/src/locator-extractor.ts +45 -0
  76. package/src/multi-context-handler.ts +296 -0
  77. package/src/nested-window-crawler.ts +310 -0
  78. package/src/network-action-correlator.ts +320 -0
  79. package/src/performance-metrics-collector.ts +243 -0
  80. package/src/performance-metrics.ts +1 -1
  81. package/src/screen-reader.ts +4 -1
  82. package/src/service-worker-interceptor.ts +185 -0
  83. package/src/session-capture.ts +3 -0
  84. package/src/shadow-dom-crawler.ts +198 -0
  85. package/src/sitemap.ts +17 -10
  86. package/src/state-graph.ts +280 -0
  87. package/src/stealth/human-behavior.ts +38 -4
  88. package/src/url-pattern-completer.ts +61 -15
  89. package/tsconfig.json +1 -1
  90. package/types/accessibility-tree-crawler.d.ts +44 -0
  91. package/types/advanced-ui-probes.d.ts +47 -0
  92. package/types/crawl-session-manager.d.ts +44 -0
  93. package/types/crawler.d.ts +3 -1
  94. package/types/crud-interaction.d.ts +31 -0
  95. package/types/dropdown-enumerator.d.ts +20 -0
  96. package/types/filter-pagination-crawler.d.ts +52 -0
  97. package/types/gap-pass.d.ts +1 -0
  98. package/types/index.d.ts +14 -1
  99. package/types/job-queue.d.ts +18 -9
  100. package/types/locator-extractor.d.ts +7 -0
  101. package/types/multi-context-handler.d.ts +55 -0
  102. package/types/nested-window-crawler.d.ts +41 -0
  103. package/types/network-action-correlator.d.ts +47 -0
  104. package/types/performance-metrics-collector.d.ts +50 -0
  105. package/types/performance-metrics.d.ts +1 -2
  106. package/types/screen-reader.d.ts +1 -2
  107. package/types/service-worker-interceptor.d.ts +36 -0
  108. package/types/session-capture.d.ts +1 -1
  109. package/types/shadow-dom-crawler.d.ts +35 -0
  110. package/types/sitemap.d.ts +2 -2
  111. package/types/state-graph.d.ts +59 -0
  112. package/types/stealth/human-behavior.d.ts +1 -1
@@ -0,0 +1,264 @@
1
+ function injectToastObserver(page) {
2
+ page.evaluate(() => {
3
+ if (window.__toastObserver) return;
4
+ window.__capturedToasts = [];
5
+ const TOAST_CLASSES = ["toast", "snackbar", "notification"];
6
+ function matchesToast(el) {
7
+ const role = el.getAttribute("role") ?? "";
8
+ if (role === "alert" || role === "status") return true;
9
+ const cls = (el.getAttribute("class") ?? "").toLowerCase();
10
+ return TOAST_CLASSES.some((t) => cls.includes(t));
11
+ }
12
+ function captureNode(node) {
13
+ if (!(node instanceof Element)) return;
14
+ if (matchesToast(node)) {
15
+ const text = (node.innerText ?? "").replace(/\s+/g, " ").trim();
16
+ const role = node.getAttribute("role") ?? "alert";
17
+ if (text) window.__capturedToasts.push({ text, role });
18
+ }
19
+ node.querySelectorAll('[role="alert"],[role="status"],[class*="toast"],[class*="Toast"],[class*="snackbar"],[class*="notification"]').forEach((child) => {
20
+ const text = (child.innerText ?? "").replace(/\s+/g, " ").trim();
21
+ const role = child.getAttribute("role") ?? "alert";
22
+ if (text) window.__capturedToasts.push({ text, role });
23
+ });
24
+ }
25
+ const observer = new MutationObserver((mutations) => {
26
+ for (const m of mutations) {
27
+ m.addedNodes.forEach(captureNode);
28
+ }
29
+ });
30
+ observer.observe(document.body, { childList: true, subtree: true });
31
+ window.__toastObserver = observer;
32
+ }).catch(() => void 0);
33
+ }
34
+ async function collectCapturedToasts(page) {
35
+ try {
36
+ return await page.evaluate(() => {
37
+ const toasts = window.__capturedToasts ?? [];
38
+ window.__capturedToasts = [];
39
+ return toasts;
40
+ });
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+ async function findCrudButtons(page) {
46
+ const editLocator = page.locator(
47
+ 'button[aria-label*="edit" i], button[aria-label*="Edit"], button:has-text("Edit")'
48
+ );
49
+ const deleteLocator = page.locator(
50
+ 'button[aria-label*="delete" i], button[aria-label*="Delete"], button:has-text("Delete")'
51
+ );
52
+ const createLocator = page.locator(
53
+ 'button:has-text("New"), button:has-text("Create"), button:has-text("Add")'
54
+ );
55
+ const [editCount, deleteCount, createCount] = await Promise.all([
56
+ editLocator.count().catch(() => 0),
57
+ deleteLocator.count().catch(() => 0),
58
+ createLocator.count().catch(() => 0)
59
+ ]);
60
+ const editBtns = [];
61
+ for (let i = 0; i < Math.min(editCount, 3); i++) {
62
+ editBtns.push(editLocator.nth(i));
63
+ }
64
+ const deleteBtns = [];
65
+ for (let i = 0; i < Math.min(deleteCount, 3); i++) {
66
+ deleteBtns.push(deleteLocator.nth(i));
67
+ }
68
+ const createBtn = createCount > 0 ? createLocator.first() : null;
69
+ return { editBtns, deleteBtns, createBtn };
70
+ }
71
+ async function detectEditFlowType(page, editButton) {
72
+ try {
73
+ const urlBefore = page.url();
74
+ await editButton.click({ timeout: 2e3 });
75
+ await page.waitForTimeout(600);
76
+ const urlAfter = page.url();
77
+ if (urlAfter !== urlBefore) {
78
+ await page.goBack().catch(() => void 0);
79
+ await page.waitForTimeout(300);
80
+ return "navigation";
81
+ }
82
+ const dialogVisible = await page.locator('[role="dialog"]').first().isVisible().catch(() => false);
83
+ if (dialogVisible) {
84
+ await page.keyboard.press("Escape");
85
+ await page.waitForTimeout(300);
86
+ return "modal";
87
+ }
88
+ const inlineInputCount = await page.locator("input:visible, textarea:visible").count().catch(() => 0);
89
+ if (inlineInputCount > 0) {
90
+ await page.keyboard.press("Escape");
91
+ await page.waitForTimeout(300);
92
+ return "inline";
93
+ }
94
+ await page.keyboard.press("Escape");
95
+ await page.waitForTimeout(300);
96
+ return "unknown";
97
+ } catch {
98
+ try {
99
+ await page.keyboard.press("Escape");
100
+ } catch {
101
+ }
102
+ return "unknown";
103
+ }
104
+ }
105
+ async function captureEditModal(page) {
106
+ try {
107
+ const dialog = page.locator('[role="dialog"]').first();
108
+ const fields = dialog.locator("input, textarea, select");
109
+ const count = Math.min(await fields.count().catch(() => 0), 20);
110
+ const results = [];
111
+ for (let i = 0; i < count; i++) {
112
+ try {
113
+ const field = fields.nth(i);
114
+ const tagName = await field.evaluate((el) => el.tagName.toLowerCase()).catch(() => "input");
115
+ const inputType = await field.getAttribute("type").catch(() => null) ?? tagName;
116
+ const name = await field.getAttribute("name").catch(() => null) ?? await field.getAttribute("id").catch(() => null) ?? await field.getAttribute("aria-label").catch(() => null) ?? `field_${i}`;
117
+ const prefilledValue = await field.inputValue().catch(() => void 0);
118
+ results.push({
119
+ name,
120
+ type: inputType,
121
+ prefilledValue: prefilledValue || void 0
122
+ });
123
+ } catch {
124
+ }
125
+ }
126
+ return results;
127
+ } catch {
128
+ return [];
129
+ }
130
+ }
131
+ async function captureDeleteConfirmation(page, deleteBtn) {
132
+ try {
133
+ await deleteBtn.click({ timeout: 2e3 });
134
+ const confirmDialog = page.locator('[role="alertdialog"], [role="dialog"]').first();
135
+ try {
136
+ await confirmDialog.waitFor({ state: "visible", timeout: 2e3 });
137
+ } catch {
138
+ await page.keyboard.press("Escape");
139
+ return null;
140
+ }
141
+ const dialogText = await confirmDialog.evaluate(
142
+ (el) => el.innerText?.replace(/\s+/g, " ").trim()
143
+ ).catch(() => "");
144
+ const isDeleteDialog = /delete|remove|are you sure|confirm/i.test(dialogText);
145
+ if (!isDeleteDialog) {
146
+ await page.keyboard.press("Escape");
147
+ return null;
148
+ }
149
+ const buttons = confirmDialog.locator("button");
150
+ const btnCount = await buttons.count().catch(() => 0);
151
+ const btnLabels = [];
152
+ for (let i = 0; i < btnCount; i++) {
153
+ try {
154
+ const label = await buttons.nth(i).innerText();
155
+ btnLabels.push(label.trim());
156
+ } catch {
157
+ }
158
+ }
159
+ const confirmLabel = btnLabels.find((l) => /delete|remove|yes|confirm/i.test(l)) ?? btnLabels[0] ?? "Confirm";
160
+ const cancelLabel = btnLabels.find((l) => /cancel|no|keep|dismiss/i.test(l)) ?? btnLabels[btnLabels.length - 1] ?? "Cancel";
161
+ await page.keyboard.press("Escape");
162
+ await page.waitForTimeout(300);
163
+ return {
164
+ message: dialogText,
165
+ confirmLabel,
166
+ cancelLabel
167
+ };
168
+ } catch {
169
+ try {
170
+ await page.keyboard.press("Escape");
171
+ } catch {
172
+ }
173
+ return null;
174
+ }
175
+ }
176
+ async function detectRowCount(page) {
177
+ try {
178
+ const tableRows = await page.locator("table tbody tr").count().catch(() => 0);
179
+ if (tableRows > 0) return tableRows;
180
+ const listItems = await page.locator('[role="row"]').count().catch(() => 0);
181
+ if (listItems > 0) return listItems;
182
+ const dataItems = await page.locator('[data-testid*="row"], [class*="row" i], [class*="item" i]').count().catch(() => 0);
183
+ return dataItems;
184
+ } catch {
185
+ return 0;
186
+ }
187
+ }
188
+ async function getCreateTriggerLabel(createBtn) {
189
+ if (!createBtn) return void 0;
190
+ try {
191
+ const label = await createBtn.innerText();
192
+ return label.trim() || void 0;
193
+ } catch {
194
+ return void 0;
195
+ }
196
+ }
197
+ async function probeCrudFlows(page, entityName = "Entity") {
198
+ const toastsObserved = [];
199
+ injectToastObserver(page);
200
+ const { editBtns, deleteBtns, createBtn } = await findCrudButtons(page);
201
+ const rowCount = await detectRowCount(page);
202
+ const createTrigger = await getCreateTriggerLabel(createBtn);
203
+ let editFlowType = "unknown";
204
+ let editFields = [];
205
+ if (editBtns.length > 0) {
206
+ try {
207
+ editFlowType = await detectEditFlowType(page, editBtns[0]);
208
+ if (editFlowType === "modal") {
209
+ try {
210
+ await editBtns[0].click({ timeout: 2e3 });
211
+ await page.waitForTimeout(400);
212
+ const dialogVisible = await page.locator('[role="dialog"]').first().isVisible().catch(() => false);
213
+ if (dialogVisible) {
214
+ editFields = await captureEditModal(page);
215
+ const editToasts = await collectCapturedToasts(page);
216
+ for (const t of editToasts) {
217
+ toastsObserved.push({ action: "edit_open", ...t });
218
+ }
219
+ }
220
+ await page.keyboard.press("Escape");
221
+ await page.waitForTimeout(300);
222
+ } catch {
223
+ }
224
+ }
225
+ } catch {
226
+ }
227
+ }
228
+ let deleteConfirmation;
229
+ if (deleteBtns.length > 0) {
230
+ try {
231
+ const result = await captureDeleteConfirmation(page, deleteBtns[0]);
232
+ if (result) {
233
+ deleteConfirmation = result;
234
+ const deleteToasts = await collectCapturedToasts(page);
235
+ for (const t of deleteToasts) {
236
+ toastsObserved.push({ action: "delete_cancel", ...t });
237
+ }
238
+ }
239
+ } catch {
240
+ }
241
+ }
242
+ const remainingToasts = await collectCapturedToasts(page);
243
+ for (const t of remainingToasts) {
244
+ toastsObserved.push({ action: "ambient", ...t });
245
+ }
246
+ const flow = {
247
+ entityName,
248
+ createTrigger,
249
+ editFlowType,
250
+ editFields,
251
+ deleteConfirmation,
252
+ rowCount,
253
+ hasDeleteButton: deleteBtns.length > 0,
254
+ hasEditButton: editBtns.length > 0
255
+ };
256
+ return {
257
+ flows: [flow],
258
+ toastsObserved
259
+ };
260
+ }
261
+ export {
262
+ probeCrudFlows
263
+ };
264
+ //# sourceMappingURL=crud-interaction.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/crud-interaction.ts"],
4
+ "sourcesContent": ["import type { Page, Locator } from 'playwright';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type EditFlowType = 'inline' | 'modal' | 'navigation' | 'unknown';\n\nexport interface CrudFlowCapture {\n entityName: string;\n createTrigger?: string;\n editFlowType: EditFlowType;\n editFields: Array<{ name: string; type: string; prefilledValue?: string }>;\n deleteConfirmation?: { message: string; confirmLabel: string; cancelLabel: string };\n createSuccessToast?: string;\n editSuccessToast?: string;\n rowCount: number;\n hasDeleteButton: boolean;\n hasEditButton: boolean;\n}\n\nexport interface CrudProbeResult {\n flows: CrudFlowCapture[];\n toastsObserved: Array<{ action: string; text: string; role: string }>;\n}\n\n// \u2500\u2500 Internal: Toast observer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction injectToastObserver(page: Page): void {\n page.evaluate(() => {\n if ((window as any).__toastObserver) return;\n\n (window as any).__capturedToasts = [] as Array<{ text: string; role: string }>;\n\n const TOAST_CLASSES = ['toast', 'snackbar', 'notification'];\n\n function matchesToast(el: Element): boolean {\n const role = el.getAttribute('role') ?? '';\n if (role === 'alert' || role === 'status') return true;\n const cls = (el.getAttribute('class') ?? '').toLowerCase();\n return TOAST_CLASSES.some((t) => cls.includes(t));\n }\n\n function captureNode(node: Node): void {\n if (!(node instanceof Element)) return;\n if (matchesToast(node)) {\n const text = ((node as HTMLElement).innerText ?? '').replace(/\\s+/g, ' ').trim();\n const role = node.getAttribute('role') ?? 'alert';\n if (text) (window as any).__capturedToasts.push({ text, role });\n }\n node.querySelectorAll('[role=\"alert\"],[role=\"status\"],[class*=\"toast\"],[class*=\"Toast\"],[class*=\"snackbar\"],[class*=\"notification\"]').forEach((child) => {\n const text = ((child as HTMLElement).innerText ?? '').replace(/\\s+/g, ' ').trim();\n const role = child.getAttribute('role') ?? 'alert';\n if (text) (window as any).__capturedToasts.push({ text, role });\n });\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const m of mutations) {\n m.addedNodes.forEach(captureNode);\n }\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n (window as any).__toastObserver = observer;\n }).catch(() => undefined);\n}\n\nasync function collectCapturedToasts(page: Page): Promise<Array<{ text: string; role: string }>> {\n try {\n return await page.evaluate(() => {\n const toasts: Array<{ text: string; role: string }> = (window as any).__capturedToasts ?? [];\n (window as any).__capturedToasts = [];\n return toasts;\n });\n } catch {\n return [];\n }\n}\n\n// \u2500\u2500 Internal: Button discovery \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface CrudButtons {\n editBtns: Locator[];\n deleteBtns: Locator[];\n createBtn: Locator | null;\n}\n\nasync function findCrudButtons(page: Page): Promise<CrudButtons> {\n const editLocator = page.locator(\n 'button[aria-label*=\"edit\" i], button[aria-label*=\"Edit\"], button:has-text(\"Edit\")'\n );\n const deleteLocator = page.locator(\n 'button[aria-label*=\"delete\" i], button[aria-label*=\"Delete\"], button:has-text(\"Delete\")'\n );\n const createLocator = page.locator(\n 'button:has-text(\"New\"), button:has-text(\"Create\"), button:has-text(\"Add\")'\n );\n\n const [editCount, deleteCount, createCount] = await Promise.all([\n editLocator.count().catch(() => 0),\n deleteLocator.count().catch(() => 0),\n createLocator.count().catch(() => 0),\n ]);\n\n const editBtns: Locator[] = [];\n for (let i = 0; i < Math.min(editCount, 3); i++) {\n editBtns.push(editLocator.nth(i));\n }\n\n const deleteBtns: Locator[] = [];\n for (let i = 0; i < Math.min(deleteCount, 3); i++) {\n deleteBtns.push(deleteLocator.nth(i));\n }\n\n const createBtn = createCount > 0 ? createLocator.first() : null;\n\n return { editBtns, deleteBtns, createBtn };\n}\n\n// \u2500\u2500 Internal: Edit flow detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function detectEditFlowType(page: Page, editButton: Locator): Promise<EditFlowType> {\n try {\n const urlBefore = page.url();\n await editButton.click({ timeout: 2000 });\n await page.waitForTimeout(600);\n\n const urlAfter = page.url();\n if (urlAfter !== urlBefore) {\n await page.goBack().catch(() => undefined);\n await page.waitForTimeout(300);\n return 'navigation';\n }\n\n const dialogVisible = await page.locator('[role=\"dialog\"]').first().isVisible().catch(() => false);\n if (dialogVisible) {\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n return 'modal';\n }\n\n const inlineInputCount = await page.locator('input:visible, textarea:visible').count().catch(() => 0);\n if (inlineInputCount > 0) {\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n return 'inline';\n }\n\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n return 'unknown';\n } catch {\n try {\n await page.keyboard.press('Escape');\n } catch { /* ignore */ }\n return 'unknown';\n }\n}\n\n// \u2500\u2500 Internal: Modal field capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function captureEditModal(\n page: Page\n): Promise<Array<{ name: string; type: string; prefilledValue?: string }>> {\n try {\n const dialog = page.locator('[role=\"dialog\"]').first();\n const fields = dialog.locator('input, textarea, select');\n const count = Math.min(await fields.count().catch(() => 0), 20);\n\n const results: Array<{ name: string; type: string; prefilledValue?: string }> = [];\n\n for (let i = 0; i < count; i++) {\n try {\n const field = fields.nth(i);\n const tagName = await field.evaluate((el) => el.tagName.toLowerCase()).catch(() => 'input');\n const inputType = await field.getAttribute('type').catch(() => null) ?? tagName;\n const name =\n (await field.getAttribute('name').catch(() => null)) ??\n (await field.getAttribute('id').catch(() => null)) ??\n (await field.getAttribute('aria-label').catch(() => null)) ??\n `field_${i}`;\n const prefilledValue = await field.inputValue().catch(() => undefined);\n\n results.push({\n name,\n type: inputType,\n prefilledValue: prefilledValue || undefined,\n });\n } catch { /* skip individual field errors */ }\n }\n\n return results;\n } catch {\n return [];\n }\n}\n\n// \u2500\u2500 Internal: Delete confirmation capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function captureDeleteConfirmation(\n page: Page,\n deleteBtn: Locator\n): Promise<{ message: string; confirmLabel: string; cancelLabel: string } | null> {\n try {\n await deleteBtn.click({ timeout: 2000 });\n\n const confirmDialog = page.locator('[role=\"alertdialog\"], [role=\"dialog\"]').first();\n try {\n await confirmDialog.waitFor({ state: 'visible', timeout: 2000 });\n } catch {\n await page.keyboard.press('Escape');\n return null;\n }\n\n const dialogText = await confirmDialog.evaluate((el) =>\n (el as HTMLElement).innerText?.replace(/\\s+/g, ' ').trim()\n ).catch(() => '');\n\n const isDeleteDialog =\n /delete|remove|are you sure|confirm/i.test(dialogText);\n\n if (!isDeleteDialog) {\n await page.keyboard.press('Escape');\n return null;\n }\n\n // Capture button labels inside the dialog\n const buttons = confirmDialog.locator('button');\n const btnCount = await buttons.count().catch(() => 0);\n const btnLabels: string[] = [];\n for (let i = 0; i < btnCount; i++) {\n try {\n const label = await buttons.nth(i).innerText();\n btnLabels.push(label.trim());\n } catch { /* skip */ }\n }\n\n // Heuristic: confirm = first destructive-looking label, cancel = cancel/no label\n const confirmLabel =\n btnLabels.find((l) => /delete|remove|yes|confirm/i.test(l)) ??\n btnLabels[0] ??\n 'Confirm';\n const cancelLabel =\n btnLabels.find((l) => /cancel|no|keep|dismiss/i.test(l)) ??\n btnLabels[btnLabels.length - 1] ??\n 'Cancel';\n\n // CRITICAL: Always cancel \u2014 never actually delete data\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n\n return {\n message: dialogText,\n confirmLabel,\n cancelLabel,\n };\n } catch {\n try {\n await page.keyboard.press('Escape');\n } catch { /* ignore */ }\n return null;\n }\n}\n\n// \u2500\u2500 Internal: Row count \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function detectRowCount(page: Page): Promise<number> {\n try {\n // Try common table row patterns\n const tableRows = await page.locator('table tbody tr').count().catch(() => 0);\n if (tableRows > 0) return tableRows;\n\n const listItems = await page.locator('[role=\"row\"]').count().catch(() => 0);\n if (listItems > 0) return listItems;\n\n const dataItems = await page\n .locator('[data-testid*=\"row\"], [class*=\"row\" i], [class*=\"item\" i]')\n .count()\n .catch(() => 0);\n return dataItems;\n } catch {\n return 0;\n }\n}\n\n// \u2500\u2500 Internal: Create trigger label \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function getCreateTriggerLabel(createBtn: Locator | null): Promise<string | undefined> {\n if (!createBtn) return undefined;\n try {\n const label = await createBtn.innerText();\n return label.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n// \u2500\u2500 Public: probeCrudFlows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function probeCrudFlows(\n page: Page,\n entityName = 'Entity'\n): Promise<CrudProbeResult> {\n const toastsObserved: Array<{ action: string; text: string; role: string }> = [];\n\n // Step 1: Inject toast observer\n injectToastObserver(page);\n\n // Step 2: Find CRUD buttons\n const { editBtns, deleteBtns, createBtn } = await findCrudButtons(page);\n\n // Step 3: Row count\n const rowCount = await detectRowCount(page);\n\n // Step 4: Create trigger label\n const createTrigger = await getCreateTriggerLabel(createBtn);\n\n // Step 5: Probe edit flow (use first edit button)\n let editFlowType: EditFlowType = 'unknown';\n let editFields: Array<{ name: string; type: string; prefilledValue?: string }> = [];\n\n if (editBtns.length > 0) {\n try {\n editFlowType = await detectEditFlowType(page, editBtns[0]);\n\n if (editFlowType === 'modal') {\n // Re-open to capture fields\n try {\n await editBtns[0].click({ timeout: 2000 });\n await page.waitForTimeout(400);\n const dialogVisible = await page\n .locator('[role=\"dialog\"]')\n .first()\n .isVisible()\n .catch(() => false);\n if (dialogVisible) {\n editFields = await captureEditModal(page);\n const editToasts = await collectCapturedToasts(page);\n for (const t of editToasts) {\n toastsObserved.push({ action: 'edit_open', ...t });\n }\n }\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n } catch { /* non-fatal */ }\n }\n } catch { /* non-fatal */ }\n }\n\n // Step 6: Probe delete confirmation (NEVER actually delete)\n let deleteConfirmation: CrudFlowCapture['deleteConfirmation'];\n\n if (deleteBtns.length > 0) {\n try {\n const result = await captureDeleteConfirmation(page, deleteBtns[0]);\n if (result) {\n deleteConfirmation = result;\n const deleteToasts = await collectCapturedToasts(page);\n for (const t of deleteToasts) {\n toastsObserved.push({ action: 'delete_cancel', ...t });\n }\n }\n } catch { /* non-fatal */ }\n }\n\n // Step 7: Collect any remaining toasts\n const remainingToasts = await collectCapturedToasts(page);\n for (const t of remainingToasts) {\n toastsObserved.push({ action: 'ambient', ...t });\n }\n\n const flow: CrudFlowCapture = {\n entityName,\n createTrigger,\n editFlowType,\n editFields,\n deleteConfirmation,\n rowCount,\n hasDeleteButton: deleteBtns.length > 0,\n hasEditButton: editBtns.length > 0,\n };\n\n return {\n flows: [flow],\n toastsObserved,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,230 @@
1
+ async function enumerateNativeSelects(page) {
2
+ const selects = await page.$$("select:not([multiple])");
3
+ const limited = selects.slice(0, 50);
4
+ const results = [];
5
+ for (const handle of limited) {
6
+ try {
7
+ const data = await handle.evaluate((el) => {
8
+ const name = el.name || el.id || "";
9
+ const ariaLabel = el.getAttribute("aria-label") || "";
10
+ let labelText = ariaLabel;
11
+ if (!labelText && el.id) {
12
+ const lbl = document.querySelector(`label[for="${el.id}"]`);
13
+ if (lbl) labelText = lbl.textContent?.trim() || "";
14
+ }
15
+ if (!labelText && name) labelText = name;
16
+ const options = Array.from(el.options).map((o) => ({
17
+ value: o.value,
18
+ label: o.text.trim(),
19
+ disabled: o.disabled || void 0
20
+ }));
21
+ const selectedOption = el.options[el.selectedIndex];
22
+ const defaultValue = selectedOption ? selectedOption.value : void 0;
23
+ const locatorHint = name ? `select[name="${name}"]` : el.id ? `select#${el.id}` : "select";
24
+ return { label: labelText, locatorHint, options, defaultValue };
25
+ });
26
+ results.push({
27
+ kind: "native-select",
28
+ label: data.label,
29
+ locatorHint: data.locatorHint,
30
+ options: data.options,
31
+ defaultValue: data.defaultValue
32
+ });
33
+ } catch {
34
+ }
35
+ }
36
+ return results;
37
+ }
38
+ async function enumerateCustomDropdowns(page) {
39
+ const selectors = [
40
+ '[role="combobox"]',
41
+ '[aria-haspopup="listbox"]:not([role="combobox"])',
42
+ "[data-radix-select-trigger]"
43
+ ];
44
+ const seen = /* @__PURE__ */ new Set();
45
+ const handles = [];
46
+ for (const sel of selectors) {
47
+ const els = await page.$$(sel);
48
+ for (const el of els) {
49
+ const hint = await el.evaluate((e) => {
50
+ const id = e.id ? `#${e.id}` : "";
51
+ const aria = e.getAttribute("aria-label") || "";
52
+ return id || aria || e.tagName.toLowerCase();
53
+ }).catch(() => "");
54
+ if (!seen.has(hint)) {
55
+ seen.add(hint);
56
+ handles.push({ handle: el, hint });
57
+ }
58
+ }
59
+ }
60
+ const limited = handles.slice(0, 20);
61
+ const results = [];
62
+ for (const { handle, hint } of limited) {
63
+ try {
64
+ const ariaLabel = await handle.evaluate((e) => e.getAttribute("aria-label") || e.textContent?.trim() || "").catch(() => "");
65
+ await handle.click();
66
+ await page.waitForSelector('[role="listbox"]', { timeout: 2e3 });
67
+ const options = await page.$$eval(
68
+ '[role="listbox"] [role="option"]',
69
+ (els) => els.map((el) => ({
70
+ value: el.getAttribute("data-value") || el.getAttribute("value") || el.textContent?.trim() || "",
71
+ label: el.textContent?.trim() || "",
72
+ disabled: el.getAttribute("aria-disabled") === "true" || void 0
73
+ }))
74
+ );
75
+ await page.keyboard.press("Escape");
76
+ await page.waitForTimeout(300);
77
+ results.push({
78
+ kind: "combobox",
79
+ label: ariaLabel,
80
+ locatorHint: hint,
81
+ options
82
+ });
83
+ } catch {
84
+ try {
85
+ await page.keyboard.press("Escape");
86
+ await page.waitForTimeout(300);
87
+ } catch {
88
+ }
89
+ }
90
+ }
91
+ return results;
92
+ }
93
+ async function enumerateMultiSelects(page) {
94
+ const results = [];
95
+ const nativeMulti = await page.$$("select[multiple]");
96
+ for (const handle of nativeMulti) {
97
+ try {
98
+ const data = await handle.evaluate((el) => {
99
+ const name = el.name || el.id || "";
100
+ const ariaLabel = el.getAttribute("aria-label") || "";
101
+ let labelText = ariaLabel;
102
+ if (!labelText && el.id) {
103
+ const lbl = document.querySelector(`label[for="${el.id}"]`);
104
+ if (lbl) labelText = lbl.textContent?.trim() || "";
105
+ }
106
+ if (!labelText && name) labelText = name;
107
+ const options = Array.from(el.options).map((o) => ({
108
+ value: o.value,
109
+ label: o.text.trim(),
110
+ disabled: o.disabled || void 0
111
+ }));
112
+ const locatorHint = name ? `select[multiple][name="${name}"]` : el.id ? `select[multiple]#${el.id}` : "select[multiple]";
113
+ return { label: labelText, locatorHint, options };
114
+ });
115
+ results.push({
116
+ kind: "multiselect",
117
+ label: data.label,
118
+ locatorHint: data.locatorHint,
119
+ options: data.options
120
+ });
121
+ } catch {
122
+ }
123
+ }
124
+ const ariaMulti = await page.$$('[role="listbox"][aria-multiselectable="true"]');
125
+ for (const handle of ariaMulti) {
126
+ try {
127
+ const data = await handle.evaluate((el) => {
128
+ const ariaLabel = el.getAttribute("aria-label") || el.id || "";
129
+ const locatorHint = el.id ? `[role="listbox"]#${el.id}` : '[role="listbox"][aria-multiselectable="true"]';
130
+ const optionEls = el.querySelectorAll('[role="option"]');
131
+ const options = Array.from(optionEls).map((o) => ({
132
+ value: o.getAttribute("data-value") || o.getAttribute("value") || o.textContent?.trim() || "",
133
+ label: o.textContent?.trim() || "",
134
+ disabled: o.getAttribute("aria-disabled") === "true" || void 0
135
+ }));
136
+ return { label: ariaLabel, locatorHint, options };
137
+ });
138
+ results.push({
139
+ kind: "multiselect",
140
+ label: data.label,
141
+ locatorHint: data.locatorHint,
142
+ options: data.options
143
+ });
144
+ } catch {
145
+ }
146
+ }
147
+ return results;
148
+ }
149
+ async function detectCascading(page, dropdowns) {
150
+ const cascadeMap = {};
151
+ const nativeParents = dropdowns.filter(
152
+ (d) => d.kind === "native-select" && d.options.length <= 8
153
+ );
154
+ for (const parent of nativeParents) {
155
+ const parentName = parent.locatorHint.match(/name="([^"]+)"/)?.[1];
156
+ if (!parentName) continue;
157
+ const otherNatives = dropdowns.filter(
158
+ (d) => d.kind === "native-select" && d.locatorHint !== parent.locatorHint
159
+ );
160
+ if (otherNatives.length === 0) continue;
161
+ const baselineCounts = await page.$$eval(
162
+ "select:not([multiple])",
163
+ (els) => els.map((el) => ({ name: el.name || el.id, count: el.options.length }))
164
+ ).catch(() => []);
165
+ let foundChildren = [];
166
+ for (let i = 0; i < parent.options.length; i++) {
167
+ try {
168
+ await page.selectOption(`select[name="${parentName}"]`, { index: i });
169
+ await page.waitForTimeout(500);
170
+ const newCounts = await page.$$eval(
171
+ "select:not([multiple])",
172
+ (els) => els.map((el) => ({ name: el.name || el.id, count: el.options.length }))
173
+ ).catch(() => []);
174
+ for (const curr of newCounts) {
175
+ const base = baselineCounts.find((b) => b.name === curr.name);
176
+ if (base && base.count !== curr.count && curr.name !== parentName) {
177
+ const childHint = `select[name="${curr.name}"]`;
178
+ const childDef = dropdowns.find((d) => d.locatorHint === childHint);
179
+ if (childDef && !foundChildren.includes(childHint)) {
180
+ foundChildren.push(childHint);
181
+ }
182
+ }
183
+ }
184
+ } catch {
185
+ }
186
+ }
187
+ try {
188
+ await page.selectOption(`select[name="${parentName}"]`, { index: 0 });
189
+ } catch {
190
+ }
191
+ if (foundChildren.length > 0) {
192
+ cascadeMap[parent.locatorHint] = foundChildren;
193
+ }
194
+ }
195
+ return cascadeMap;
196
+ }
197
+ async function enumerateDropdowns(page) {
198
+ try {
199
+ const [native, custom, multi] = await Promise.all([
200
+ enumerateNativeSelects(page).catch(() => []),
201
+ enumerateCustomDropdowns(page).catch(() => []),
202
+ enumerateMultiSelects(page).catch(() => [])
203
+ ]);
204
+ const seen = /* @__PURE__ */ new Set();
205
+ const dropdowns = [];
206
+ for (const d of [...native, ...custom, ...multi]) {
207
+ if (!seen.has(d.locatorHint)) {
208
+ seen.add(d.locatorHint);
209
+ dropdowns.push(d);
210
+ }
211
+ }
212
+ const conditionalRevealMap = await detectCascading(page, dropdowns).catch(
213
+ () => ({})
214
+ );
215
+ for (const [parent, children] of Object.entries(conditionalRevealMap)) {
216
+ for (const childHint of children) {
217
+ const child = dropdowns.find((d) => d.locatorHint === childHint);
218
+ if (child) child.cascadesFrom = parent;
219
+ }
220
+ }
221
+ const totalOptionsFound = dropdowns.reduce((sum, d) => sum + d.options.length, 0);
222
+ return { dropdowns, conditionalRevealMap, totalOptionsFound };
223
+ } catch {
224
+ return { dropdowns: [], conditionalRevealMap: {}, totalOptionsFound: 0 };
225
+ }
226
+ }
227
+ export {
228
+ enumerateDropdowns
229
+ };
230
+ //# sourceMappingURL=dropdown-enumerator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/dropdown-enumerator.ts"],
4
+ "sourcesContent": ["import type { Page } from 'playwright';\n\nexport interface DropdownOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\nexport interface DropdownDef {\n kind: 'native-select' | 'combobox' | 'listbox' | 'multiselect';\n label: string;\n locatorHint: string;\n options: DropdownOption[];\n defaultValue?: string;\n cascadesFrom?: string;\n}\n\nexport interface DropdownEnumerationResult {\n dropdowns: DropdownDef[];\n conditionalRevealMap: Record<string, string[]>;\n totalOptionsFound: number;\n}\n\nasync function enumerateNativeSelects(page: Page): Promise<DropdownDef[]> {\n const selects = await page.$$('select:not([multiple])');\n const limited = selects.slice(0, 50);\n const results: DropdownDef[] = [];\n\n for (const handle of limited) {\n try {\n const data = await handle.evaluate((el: HTMLSelectElement) => {\n const name = el.name || el.id || '';\n const ariaLabel = el.getAttribute('aria-label') || '';\n let labelText = ariaLabel;\n if (!labelText && el.id) {\n const lbl = document.querySelector(`label[for=\"${el.id}\"]`);\n if (lbl) labelText = lbl.textContent?.trim() || '';\n }\n if (!labelText && name) labelText = name;\n const options = Array.from(el.options).map((o) => ({\n value: o.value,\n label: o.text.trim(),\n disabled: o.disabled || undefined,\n }));\n const selectedOption = el.options[el.selectedIndex];\n const defaultValue = selectedOption ? selectedOption.value : undefined;\n const locatorHint = name\n ? `select[name=\"${name}\"]`\n : el.id\n ? `select#${el.id}`\n : 'select';\n return { label: labelText, locatorHint, options, defaultValue };\n });\n\n results.push({\n kind: 'native-select',\n label: data.label,\n locatorHint: data.locatorHint,\n options: data.options,\n defaultValue: data.defaultValue,\n });\n } catch {\n // skip unresponsive handle\n }\n }\n\n return results;\n}\n\nasync function enumerateCustomDropdowns(page: Page): Promise<DropdownDef[]> {\n const selectors = [\n '[role=\"combobox\"]',\n '[aria-haspopup=\"listbox\"]:not([role=\"combobox\"])',\n '[data-radix-select-trigger]',\n ];\n\n const seen = new Set<string>();\n const handles: { handle: Awaited<ReturnType<Page['$$']>>[0]; hint: string }[] = [];\n\n for (const sel of selectors) {\n const els = await page.$$(sel);\n for (const el of els) {\n const hint = await el\n .evaluate((e: Element) => {\n const id = e.id ? `#${e.id}` : '';\n const aria = e.getAttribute('aria-label') || '';\n return id || aria || e.tagName.toLowerCase();\n })\n .catch(() => '');\n if (!seen.has(hint)) {\n seen.add(hint);\n handles.push({ handle: el, hint });\n }\n }\n }\n\n const limited = handles.slice(0, 20);\n const results: DropdownDef[] = [];\n\n for (const { handle, hint } of limited) {\n try {\n const ariaLabel = await handle\n .evaluate((e: Element) => e.getAttribute('aria-label') || e.textContent?.trim() || '')\n .catch(() => '');\n\n await handle.click();\n await page.waitForSelector('[role=\"listbox\"]', { timeout: 2000 });\n\n const options = await page.$$eval('[role=\"listbox\"] [role=\"option\"]', (els) =>\n els.map((el) => ({\n value: el.getAttribute('data-value') || el.getAttribute('value') || el.textContent?.trim() || '',\n label: el.textContent?.trim() || '',\n disabled: el.getAttribute('aria-disabled') === 'true' || undefined,\n }))\n );\n\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n\n results.push({\n kind: 'combobox',\n label: ariaLabel,\n locatorHint: hint,\n options,\n });\n } catch {\n try {\n await page.keyboard.press('Escape');\n await page.waitForTimeout(300);\n } catch {\n // ignore cleanup failure\n }\n }\n }\n\n return results;\n}\n\nasync function enumerateMultiSelects(page: Page): Promise<DropdownDef[]> {\n const results: DropdownDef[] = [];\n\n const nativeMulti = await page.$$('select[multiple]');\n for (const handle of nativeMulti) {\n try {\n const data = await handle.evaluate((el: HTMLSelectElement) => {\n const name = el.name || el.id || '';\n const ariaLabel = el.getAttribute('aria-label') || '';\n let labelText = ariaLabel;\n if (!labelText && el.id) {\n const lbl = document.querySelector(`label[for=\"${el.id}\"]`);\n if (lbl) labelText = lbl.textContent?.trim() || '';\n }\n if (!labelText && name) labelText = name;\n const options = Array.from(el.options).map((o) => ({\n value: o.value,\n label: o.text.trim(),\n disabled: o.disabled || undefined,\n }));\n const locatorHint = name\n ? `select[multiple][name=\"${name}\"]`\n : el.id\n ? `select[multiple]#${el.id}`\n : 'select[multiple]';\n return { label: labelText, locatorHint, options };\n });\n\n results.push({\n kind: 'multiselect',\n label: data.label,\n locatorHint: data.locatorHint,\n options: data.options,\n });\n } catch {\n // skip\n }\n }\n\n const ariaMulti = await page.$$('[role=\"listbox\"][aria-multiselectable=\"true\"]');\n for (const handle of ariaMulti) {\n try {\n const data = await handle.evaluate((el: Element) => {\n const ariaLabel = el.getAttribute('aria-label') || el.id || '';\n const locatorHint = el.id\n ? `[role=\"listbox\"]#${el.id}`\n : '[role=\"listbox\"][aria-multiselectable=\"true\"]';\n const optionEls = el.querySelectorAll('[role=\"option\"]');\n const options = Array.from(optionEls).map((o) => ({\n value: o.getAttribute('data-value') || o.getAttribute('value') || o.textContent?.trim() || '',\n label: o.textContent?.trim() || '',\n disabled: o.getAttribute('aria-disabled') === 'true' || undefined,\n }));\n return { label: ariaLabel, locatorHint, options };\n });\n\n results.push({\n kind: 'multiselect',\n label: data.label,\n locatorHint: data.locatorHint,\n options: data.options,\n });\n } catch {\n // skip\n }\n }\n\n return results;\n}\n\nasync function detectCascading(\n page: Page,\n dropdowns: DropdownDef[]\n): Promise<Record<string, string[]>> {\n const cascadeMap: Record<string, string[]> = {};\n\n const nativeParents = dropdowns.filter(\n (d) => d.kind === 'native-select' && d.options.length <= 8\n );\n\n for (const parent of nativeParents) {\n const parentName = parent.locatorHint.match(/name=\"([^\"]+)\"/)?.[1];\n if (!parentName) continue;\n\n const otherNatives = dropdowns.filter(\n (d) => d.kind === 'native-select' && d.locatorHint !== parent.locatorHint\n );\n if (otherNatives.length === 0) continue;\n\n const baselineCounts = await page\n .$$eval('select:not([multiple])', (els) =>\n (els as HTMLSelectElement[]).map((el) => ({ name: el.name || el.id, count: el.options.length }))\n )\n .catch(() => [] as { name: string; count: number }[]);\n\n let foundChildren: string[] = [];\n\n for (let i = 0; i < parent.options.length; i++) {\n try {\n await page.selectOption(`select[name=\"${parentName}\"]`, { index: i });\n await page.waitForTimeout(500);\n\n const newCounts = await page\n .$$eval('select:not([multiple])', (els) =>\n (els as HTMLSelectElement[]).map((el) => ({ name: el.name || el.id, count: el.options.length }))\n )\n .catch(() => [] as { name: string; count: number }[]);\n\n for (const curr of newCounts) {\n const base = baselineCounts.find((b) => b.name === curr.name);\n if (base && base.count !== curr.count && curr.name !== parentName) {\n const childHint = `select[name=\"${curr.name}\"]`;\n const childDef = dropdowns.find((d) => d.locatorHint === childHint);\n if (childDef && !foundChildren.includes(childHint)) {\n foundChildren.push(childHint);\n }\n }\n }\n } catch {\n // skip option that fails\n }\n }\n\n // reset to index 0\n try {\n await page.selectOption(`select[name=\"${parentName}\"]`, { index: 0 });\n } catch {\n // ignore reset failure\n }\n\n if (foundChildren.length > 0) {\n cascadeMap[parent.locatorHint] = foundChildren;\n }\n }\n\n return cascadeMap;\n}\n\nexport async function enumerateDropdowns(page: Page): Promise<DropdownEnumerationResult> {\n try {\n const [native, custom, multi] = await Promise.all([\n enumerateNativeSelects(page).catch(() => [] as DropdownDef[]),\n enumerateCustomDropdowns(page).catch(() => [] as DropdownDef[]),\n enumerateMultiSelects(page).catch(() => [] as DropdownDef[]),\n ]);\n\n const seen = new Set<string>();\n const dropdowns: DropdownDef[] = [];\n\n for (const d of [...native, ...custom, ...multi]) {\n if (!seen.has(d.locatorHint)) {\n seen.add(d.locatorHint);\n dropdowns.push(d);\n }\n }\n\n const conditionalRevealMap = await detectCascading(page, dropdowns).catch(\n () => ({} as Record<string, string[]>)\n );\n\n for (const [parent, children] of Object.entries(conditionalRevealMap)) {\n for (const childHint of children) {\n const child = dropdowns.find((d) => d.locatorHint === childHint);\n if (child) child.cascadesFrom = parent;\n }\n }\n\n const totalOptionsFound = dropdowns.reduce((sum, d) => sum + d.options.length, 0);\n\n return { dropdowns, conditionalRevealMap, totalOptionsFound };\n } catch {\n return { dropdowns: [], conditionalRevealMap: {}, totalOptionsFound: 0 };\n }\n}\n"],
5
+ "mappings": "AAuBA,eAAe,uBAAuB,MAAoC;AACxE,QAAM,UAAU,MAAM,KAAK,GAAG,wBAAwB;AACtD,QAAM,UAAU,QAAQ,MAAM,GAAG,EAAE;AACnC,QAAM,UAAyB,CAAC;AAEhC,aAAW,UAAU,SAAS;AAC5B,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,SAAS,CAAC,OAA0B;AAC5D,cAAM,OAAO,GAAG,QAAQ,GAAG,MAAM;AACjC,cAAM,YAAY,GAAG,aAAa,YAAY,KAAK;AACnD,YAAI,YAAY;AAChB,YAAI,CAAC,aAAa,GAAG,IAAI;AACvB,gBAAM,MAAM,SAAS,cAAc,cAAc,GAAG,EAAE,IAAI;AAC1D,cAAI,IAAK,aAAY,IAAI,aAAa,KAAK,KAAK;AAAA,QAClD;AACA,YAAI,CAAC,aAAa,KAAM,aAAY;AACpC,cAAM,UAAU,MAAM,KAAK,GAAG,OAAO,EAAE,IAAI,CAAC,OAAO;AAAA,UACjD,OAAO,EAAE;AAAA,UACT,OAAO,EAAE,KAAK,KAAK;AAAA,UACnB,UAAU,EAAE,YAAY;AAAA,QAC1B,EAAE;AACF,cAAM,iBAAiB,GAAG,QAAQ,GAAG,aAAa;AAClD,cAAM,eAAe,iBAAiB,eAAe,QAAQ;AAC7D,cAAM,cAAc,OAChB,gBAAgB,IAAI,OACpB,GAAG,KACH,UAAU,GAAG,EAAE,KACf;AACJ,eAAO,EAAE,OAAO,WAAW,aAAa,SAAS,aAAa;AAAA,MAChE,CAAC;AAED,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,SAAS,KAAK;AAAA,QACd,cAAc,KAAK;AAAA,MACrB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,yBAAyB,MAAoC;AAC1E,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAA0E,CAAC;AAEjF,aAAW,OAAO,WAAW;AAC3B,UAAM,MAAM,MAAM,KAAK,GAAG,GAAG;AAC7B,eAAW,MAAM,KAAK;AACpB,YAAM,OAAO,MAAM,GAChB,SAAS,CAAC,MAAe;AACxB,cAAM,KAAK,EAAE,KAAK,IAAI,EAAE,EAAE,KAAK;AAC/B,cAAM,OAAO,EAAE,aAAa,YAAY,KAAK;AAC7C,eAAO,MAAM,QAAQ,EAAE,QAAQ,YAAY;AAAA,MAC7C,CAAC,EACA,MAAM,MAAM,EAAE;AACjB,UAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,aAAK,IAAI,IAAI;AACb,gBAAQ,KAAK,EAAE,QAAQ,IAAI,KAAK,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,QAAQ,MAAM,GAAG,EAAE;AACnC,QAAM,UAAyB,CAAC;AAEhC,aAAW,EAAE,QAAQ,KAAK,KAAK,SAAS;AACtC,QAAI;AACF,YAAM,YAAY,MAAM,OACrB,SAAS,CAAC,MAAe,EAAE,aAAa,YAAY,KAAK,EAAE,aAAa,KAAK,KAAK,EAAE,EACpF,MAAM,MAAM,EAAE;AAEjB,YAAM,OAAO,MAAM;AACnB,YAAM,KAAK,gBAAgB,oBAAoB,EAAE,SAAS,IAAK,CAAC;AAEhE,YAAM,UAAU,MAAM,KAAK;AAAA,QAAO;AAAA,QAAoC,CAAC,QACrE,IAAI,IAAI,CAAC,QAAQ;AAAA,UACf,OAAO,GAAG,aAAa,YAAY,KAAK,GAAG,aAAa,OAAO,KAAK,GAAG,aAAa,KAAK,KAAK;AAAA,UAC9F,OAAO,GAAG,aAAa,KAAK,KAAK;AAAA,UACjC,UAAU,GAAG,aAAa,eAAe,MAAM,UAAU;AAAA,QAC3D,EAAE;AAAA,MACJ;AAEA,YAAM,KAAK,SAAS,MAAM,QAAQ;AAClC,YAAM,KAAK,eAAe,GAAG;AAE7B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,QACb;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AACN,UAAI;AACF,cAAM,KAAK,SAAS,MAAM,QAAQ;AAClC,cAAM,KAAK,eAAe,GAAG;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,sBAAsB,MAAoC;AACvE,QAAM,UAAyB,CAAC;AAEhC,QAAM,cAAc,MAAM,KAAK,GAAG,kBAAkB;AACpD,aAAW,UAAU,aAAa;AAChC,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,SAAS,CAAC,OAA0B;AAC5D,cAAM,OAAO,GAAG,QAAQ,GAAG,MAAM;AACjC,cAAM,YAAY,GAAG,aAAa,YAAY,KAAK;AACnD,YAAI,YAAY;AAChB,YAAI,CAAC,aAAa,GAAG,IAAI;AACvB,gBAAM,MAAM,SAAS,cAAc,cAAc,GAAG,EAAE,IAAI;AAC1D,cAAI,IAAK,aAAY,IAAI,aAAa,KAAK,KAAK;AAAA,QAClD;AACA,YAAI,CAAC,aAAa,KAAM,aAAY;AACpC,cAAM,UAAU,MAAM,KAAK,GAAG,OAAO,EAAE,IAAI,CAAC,OAAO;AAAA,UACjD,OAAO,EAAE;AAAA,UACT,OAAO,EAAE,KAAK,KAAK;AAAA,UACnB,UAAU,EAAE,YAAY;AAAA,QAC1B,EAAE;AACF,cAAM,cAAc,OAChB,0BAA0B,IAAI,OAC9B,GAAG,KACH,oBAAoB,GAAG,EAAE,KACzB;AACJ,eAAO,EAAE,OAAO,WAAW,aAAa,QAAQ;AAAA,MAClD,CAAC;AAED,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,SAAS,KAAK;AAAA,MAChB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,KAAK,GAAG,+CAA+C;AAC/E,aAAW,UAAU,WAAW;AAC9B,QAAI;AACF,YAAM,OAAO,MAAM,OAAO,SAAS,CAAC,OAAgB;AAClD,cAAM,YAAY,GAAG,aAAa,YAAY,KAAK,GAAG,MAAM;AAC5D,cAAM,cAAc,GAAG,KACnB,oBAAoB,GAAG,EAAE,KACzB;AACJ,cAAM,YAAY,GAAG,iBAAiB,iBAAiB;AACvD,cAAM,UAAU,MAAM,KAAK,SAAS,EAAE,IAAI,CAAC,OAAO;AAAA,UAChD,OAAO,EAAE,aAAa,YAAY,KAAK,EAAE,aAAa,OAAO,KAAK,EAAE,aAAa,KAAK,KAAK;AAAA,UAC3F,OAAO,EAAE,aAAa,KAAK,KAAK;AAAA,UAChC,UAAU,EAAE,aAAa,eAAe,MAAM,UAAU;AAAA,QAC1D,EAAE;AACF,eAAO,EAAE,OAAO,WAAW,aAAa,QAAQ;AAAA,MAClD,CAAC;AAED,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,aAAa,KAAK;AAAA,QAClB,SAAS,KAAK;AAAA,MAChB,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAe,gBACb,MACA,WACmC;AACnC,QAAM,aAAuC,CAAC;AAE9C,QAAM,gBAAgB,UAAU;AAAA,IAC9B,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,QAAQ,UAAU;AAAA,EAC3D;AAEA,aAAW,UAAU,eAAe;AAClC,UAAM,aAAa,OAAO,YAAY,MAAM,gBAAgB,IAAI,CAAC;AACjE,QAAI,CAAC,WAAY;AAEjB,UAAM,eAAe,UAAU;AAAA,MAC7B,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,gBAAgB,OAAO;AAAA,IAChE;AACA,QAAI,aAAa,WAAW,EAAG;AAE/B,UAAM,iBAAiB,MAAM,KAC1B;AAAA,MAAO;AAAA,MAA0B,CAAC,QAChC,IAA4B,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,OAAO,GAAG,QAAQ,OAAO,EAAE;AAAA,IACjG,EACC,MAAM,MAAM,CAAC,CAAsC;AAEtD,QAAI,gBAA0B,CAAC;AAE/B,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,UAAI;AACF,cAAM,KAAK,aAAa,gBAAgB,UAAU,MAAM,EAAE,OAAO,EAAE,CAAC;AACpE,cAAM,KAAK,eAAe,GAAG;AAE7B,cAAM,YAAY,MAAM,KACrB;AAAA,UAAO;AAAA,UAA0B,CAAC,QAChC,IAA4B,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,OAAO,GAAG,QAAQ,OAAO,EAAE;AAAA,QACjG,EACC,MAAM,MAAM,CAAC,CAAsC;AAEtD,mBAAW,QAAQ,WAAW;AAC5B,gBAAM,OAAO,eAAe,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI;AAC5D,cAAI,QAAQ,KAAK,UAAU,KAAK,SAAS,KAAK,SAAS,YAAY;AACjE,kBAAM,YAAY,gBAAgB,KAAK,IAAI;AAC3C,kBAAM,WAAW,UAAU,KAAK,CAAC,MAAM,EAAE,gBAAgB,SAAS;AAClE,gBAAI,YAAY,CAAC,cAAc,SAAS,SAAS,GAAG;AAClD,4BAAc,KAAK,SAAS;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACF,YAAM,KAAK,aAAa,gBAAgB,UAAU,MAAM,EAAE,OAAO,EAAE,CAAC;AAAA,IACtE,QAAQ;AAAA,IAER;AAEA,QAAI,cAAc,SAAS,GAAG;AAC5B,iBAAW,OAAO,WAAW,IAAI;AAAA,IACnC;AAAA,EACF;AAEA,SAAO;AACT;AAEA,eAAsB,mBAAmB,MAAgD;AACvF,MAAI;AACF,UAAM,CAAC,QAAQ,QAAQ,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAChD,uBAAuB,IAAI,EAAE,MAAM,MAAM,CAAC,CAAkB;AAAA,MAC5D,yBAAyB,IAAI,EAAE,MAAM,MAAM,CAAC,CAAkB;AAAA,MAC9D,sBAAsB,IAAI,EAAE,MAAM,MAAM,CAAC,CAAkB;AAAA,IAC7D,CAAC;AAED,UAAM,OAAO,oBAAI,IAAY;AAC7B,UAAM,YAA2B,CAAC;AAElC,eAAW,KAAK,CAAC,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG;AAChD,UAAI,CAAC,KAAK,IAAI,EAAE,WAAW,GAAG;AAC5B,aAAK,IAAI,EAAE,WAAW;AACtB,kBAAU,KAAK,CAAC;AAAA,MAClB;AAAA,IACF;AAEA,UAAM,uBAAuB,MAAM,gBAAgB,MAAM,SAAS,EAAE;AAAA,MAClE,OAAO,CAAC;AAAA,IACV;AAEA,eAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO,QAAQ,oBAAoB,GAAG;AACrE,iBAAW,aAAa,UAAU;AAChC,cAAM,QAAQ,UAAU,KAAK,CAAC,MAAM,EAAE,gBAAgB,SAAS;AAC/D,YAAI,MAAO,OAAM,eAAe;AAAA,MAClC;AAAA,IACF;AAEA,UAAM,oBAAoB,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,QAAQ,CAAC;AAEhF,WAAO,EAAE,WAAW,sBAAsB,kBAAkB;AAAA,EAC9D,QAAQ;AACN,WAAO,EAAE,WAAW,CAAC,GAAG,sBAAsB,CAAC,GAAG,mBAAmB,EAAE;AAAA,EACzE;AACF;",
6
+ "names": []
7
+ }