browser-debugger-cli 0.6.10 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/README.md +10 -0
  2. package/dist/commands/cdp.d.ts.map +1 -1
  3. package/dist/commands/cdp.js +72 -16
  4. package/dist/commands/cdp.js.map +1 -1
  5. package/dist/commands/cleanup.js +1 -1
  6. package/dist/commands/cleanup.js.map +1 -1
  7. package/dist/commands/console.d.ts +4 -28
  8. package/dist/commands/console.d.ts.map +1 -1
  9. package/dist/commands/console.js +81 -187
  10. package/dist/commands/console.js.map +1 -1
  11. package/dist/commands/details.d.ts.map +1 -1
  12. package/dist/commands/details.js +8 -0
  13. package/dist/commands/details.js.map +1 -1
  14. package/dist/commands/dom/DomElementResolver.d.ts +2 -2
  15. package/dist/commands/dom/DomElementResolver.d.ts.map +1 -1
  16. package/dist/commands/dom/DomElementResolver.js +34 -13
  17. package/dist/commands/dom/DomElementResolver.js.map +1 -1
  18. package/dist/commands/dom/__tests__/screenshotResize.unit.test.d.ts +2 -0
  19. package/dist/commands/dom/__tests__/screenshotResize.unit.test.d.ts.map +1 -0
  20. package/dist/commands/dom/__tests__/screenshotResize.unit.test.js +184 -0
  21. package/dist/commands/dom/__tests__/screenshotResize.unit.test.js.map +1 -0
  22. package/dist/commands/dom/a11y.d.ts.map +1 -1
  23. package/dist/commands/dom/a11y.js +14 -10
  24. package/dist/commands/dom/a11y.js.map +1 -1
  25. package/dist/commands/dom/evalHelpers.d.ts.map +1 -1
  26. package/dist/commands/dom/evalHelpers.js +10 -7
  27. package/dist/commands/dom/evalHelpers.js.map +1 -1
  28. package/dist/commands/dom/form.d.ts +14 -0
  29. package/dist/commands/dom/form.d.ts.map +1 -0
  30. package/dist/commands/dom/form.js +439 -0
  31. package/dist/commands/dom/form.js.map +1 -0
  32. package/dist/commands/dom/formDiscovery.d.ts +26 -0
  33. package/dist/commands/dom/formDiscovery.d.ts.map +1 -0
  34. package/dist/commands/dom/formDiscovery.js +476 -0
  35. package/dist/commands/dom/formDiscovery.js.map +1 -0
  36. package/dist/commands/dom/formFillHelpers.d.ts +2 -1
  37. package/dist/commands/dom/formFillHelpers.d.ts.map +1 -1
  38. package/dist/commands/dom/formFillHelpers.js +54 -24
  39. package/dist/commands/dom/formFillHelpers.js.map +1 -1
  40. package/dist/commands/dom/formInteraction.d.ts.map +1 -1
  41. package/dist/commands/dom/formInteraction.js +26 -10
  42. package/dist/commands/dom/formInteraction.js.map +1 -1
  43. package/dist/commands/dom/formSubmitHelpers.d.ts +2 -1
  44. package/dist/commands/dom/formSubmitHelpers.d.ts.map +1 -1
  45. package/dist/commands/dom/formSubmitHelpers.js.map +1 -1
  46. package/dist/commands/dom/formTypes.d.ts +75 -0
  47. package/dist/commands/dom/formTypes.d.ts.map +1 -0
  48. package/dist/commands/dom/formTypes.js +7 -0
  49. package/dist/commands/dom/formTypes.js.map +1 -0
  50. package/dist/commands/dom/helpers.d.ts +11 -4
  51. package/dist/commands/dom/helpers.d.ts.map +1 -1
  52. package/dist/commands/dom/helpers.js +324 -40
  53. package/dist/commands/dom/helpers.js.map +1 -1
  54. package/dist/commands/dom/index.d.ts.map +1 -1
  55. package/dist/commands/dom/index.js +17 -5
  56. package/dist/commands/dom/index.js.map +1 -1
  57. package/dist/commands/dom/reactEventHelpers.d.ts +4 -4
  58. package/dist/commands/dom/reactEventHelpers.d.ts.map +1 -1
  59. package/dist/commands/dom/reactEventHelpers.js +11 -11
  60. package/dist/commands/dom/screenshotResize.d.ts +81 -0
  61. package/dist/commands/dom/screenshotResize.d.ts.map +1 -0
  62. package/dist/commands/dom/screenshotResize.js +103 -0
  63. package/dist/commands/dom/screenshotResize.js.map +1 -0
  64. package/dist/commands/helpJson.d.ts +20 -0
  65. package/dist/commands/helpJson.d.ts.map +1 -1
  66. package/dist/commands/helpJson.js +21 -29
  67. package/dist/commands/helpJson.js.map +1 -1
  68. package/dist/commands/network/har.d.ts.map +1 -1
  69. package/dist/commands/network/har.js +3 -2
  70. package/dist/commands/network/har.js.map +1 -1
  71. package/dist/commands/network/list.d.ts +0 -8
  72. package/dist/commands/network/list.d.ts.map +1 -1
  73. package/dist/commands/network/list.js +131 -201
  74. package/dist/commands/network/list.js.map +1 -1
  75. package/dist/commands/network/shared.d.ts.map +1 -1
  76. package/dist/commands/network/shared.js +6 -0
  77. package/dist/commands/network/shared.js.map +1 -1
  78. package/dist/commands/optionBehaviors.d.ts +26 -0
  79. package/dist/commands/optionBehaviors.d.ts.map +1 -0
  80. package/dist/commands/optionBehaviors.js +195 -0
  81. package/dist/commands/optionBehaviors.js.map +1 -0
  82. package/dist/commands/peek.d.ts +2 -4
  83. package/dist/commands/peek.d.ts.map +1 -1
  84. package/dist/commands/peek.js +108 -92
  85. package/dist/commands/peek.js.map +1 -1
  86. package/dist/commands/shared/CommandRunner.js +1 -1
  87. package/dist/commands/shared/CommandRunner.js.map +1 -1
  88. package/dist/commands/shared/dataFetcher.d.ts +58 -0
  89. package/dist/commands/shared/dataFetcher.d.ts.map +1 -0
  90. package/dist/commands/shared/dataFetcher.js +98 -0
  91. package/dist/commands/shared/dataFetcher.js.map +1 -0
  92. package/dist/commands/shared/optionTypes.d.ts +8 -4
  93. package/dist/commands/shared/optionTypes.d.ts.map +1 -1
  94. package/dist/commands/shared/startHelpers.d.ts.map +1 -1
  95. package/dist/commands/shared/startHelpers.js +6 -5
  96. package/dist/commands/shared/startHelpers.js.map +1 -1
  97. package/dist/commands/shared/validation.d.ts +0 -45
  98. package/dist/commands/shared/validation.d.ts.map +1 -1
  99. package/dist/commands/shared/validation.js +65 -107
  100. package/dist/commands/shared/validation.js.map +1 -1
  101. package/dist/commands/status.d.ts.map +1 -1
  102. package/dist/commands/status.js +13 -4
  103. package/dist/commands/status.js.map +1 -1
  104. package/dist/commands/stop.d.ts.map +1 -1
  105. package/dist/commands/stop.js +15 -4
  106. package/dist/commands/stop.js.map +1 -1
  107. package/dist/commands/tail.d.ts +2 -7
  108. package/dist/commands/tail.d.ts.map +1 -1
  109. package/dist/commands/tail.js +61 -64
  110. package/dist/commands/tail.js.map +1 -1
  111. package/dist/connection/cdp.d.ts.map +1 -1
  112. package/dist/connection/cdp.js +5 -3
  113. package/dist/connection/cdp.js.map +1 -1
  114. package/dist/connection/diagnostics.js +1 -1
  115. package/dist/connection/diagnostics.js.map +1 -1
  116. package/dist/connection/errors.d.ts +0 -7
  117. package/dist/connection/errors.d.ts.map +1 -1
  118. package/dist/connection/errors.js +0 -7
  119. package/dist/connection/errors.js.map +1 -1
  120. package/dist/connection/launcher/binaryResolver.d.ts.map +1 -1
  121. package/dist/connection/launcher/binaryResolver.js +2 -1
  122. package/dist/connection/launcher/binaryResolver.js.map +1 -1
  123. package/dist/connection/launcher/preferencesLoader.d.ts.map +1 -1
  124. package/dist/connection/launcher/preferencesLoader.js +2 -1
  125. package/dist/connection/launcher/preferencesLoader.js.map +1 -1
  126. package/dist/connection/launcher.d.ts.map +1 -1
  127. package/dist/connection/launcher.js +2 -1
  128. package/dist/connection/launcher.js.map +1 -1
  129. package/dist/connection/pageReadiness.d.ts.map +1 -1
  130. package/dist/connection/pageReadiness.js +2 -9
  131. package/dist/connection/pageReadiness.js.map +1 -1
  132. package/dist/daemon/handlers/BaseHandler.d.ts.map +1 -1
  133. package/dist/daemon/handlers/BaseHandler.js +1 -1
  134. package/dist/daemon/handlers/BaseHandler.js.map +1 -1
  135. package/dist/daemon/handlers/QueryHandlers.d.ts.map +1 -1
  136. package/dist/daemon/handlers/QueryHandlers.js +1 -1
  137. package/dist/daemon/handlers/QueryHandlers.js.map +1 -1
  138. package/dist/daemon/handlers/SessionHandlers.d.ts.map +1 -1
  139. package/dist/daemon/handlers/SessionHandlers.js +1 -1
  140. package/dist/daemon/handlers/SessionHandlers.js.map +1 -1
  141. package/dist/daemon/ipcServer.js +1 -1
  142. package/dist/daemon/ipcServer.js.map +1 -1
  143. package/dist/daemon/launcher.d.ts.map +1 -1
  144. package/dist/daemon/launcher.js +3 -2
  145. package/dist/daemon/launcher.js.map +1 -1
  146. package/dist/daemon/lifecycle/signalHandlers.d.ts.map +1 -1
  147. package/dist/daemon/lifecycle/signalHandlers.js +1 -1
  148. package/dist/daemon/lifecycle/signalHandlers.js.map +1 -1
  149. package/dist/daemon/lifecycle/workerCleanup.d.ts.map +1 -1
  150. package/dist/daemon/lifecycle/workerCleanup.js +4 -3
  151. package/dist/daemon/lifecycle/workerCleanup.js.map +1 -1
  152. package/dist/daemon/lifecycle/workerConfig.d.ts.map +1 -1
  153. package/dist/daemon/lifecycle/workerConfig.js +1 -1
  154. package/dist/daemon/lifecycle/workerConfig.js.map +1 -1
  155. package/dist/daemon/lifecycle/workerIpc.d.ts.map +1 -1
  156. package/dist/daemon/lifecycle/workerIpc.js +1 -1
  157. package/dist/daemon/lifecycle/workerIpc.js.map +1 -1
  158. package/dist/daemon/server/JsonlParser.d.ts.map +1 -1
  159. package/dist/daemon/server/JsonlParser.js +1 -1
  160. package/dist/daemon/server/JsonlParser.js.map +1 -1
  161. package/dist/daemon/startSession.d.ts.map +1 -1
  162. package/dist/daemon/startSession.js +1 -1
  163. package/dist/daemon/startSession.js.map +1 -1
  164. package/dist/daemon/worker/plugins.js +3 -3
  165. package/dist/daemon/worker/plugins.js.map +1 -1
  166. package/dist/index.js +23 -7
  167. package/dist/index.js.map +1 -1
  168. package/dist/ipc/protocol/messages.d.ts +2 -2
  169. package/dist/ipc/protocol/messages.d.ts.map +1 -1
  170. package/dist/ipc/utils/errors.js +1 -1
  171. package/dist/ipc/utils/errors.js.map +1 -1
  172. package/dist/session/QueryCacheManager.d.ts.map +1 -1
  173. package/dist/session/QueryCacheManager.js +1 -1
  174. package/dist/session/QueryCacheManager.js.map +1 -1
  175. package/dist/session/chrome.js +1 -1
  176. package/dist/session/chrome.js.map +1 -1
  177. package/dist/session/cleanup.js +5 -5
  178. package/dist/session/cleanup.js.map +1 -1
  179. package/dist/session/lock.js +1 -1
  180. package/dist/session/lock.js.map +1 -1
  181. package/dist/session/metadata.d.ts.map +1 -1
  182. package/dist/session/metadata.js +1 -1
  183. package/dist/session/metadata.js.map +1 -1
  184. package/dist/session/pid.js +1 -1
  185. package/dist/session/pid.js.map +1 -1
  186. package/dist/telemetry/dom.d.ts.map +1 -1
  187. package/dist/telemetry/dom.js +1 -1
  188. package/dist/telemetry/dom.js.map +1 -1
  189. package/dist/telemetry/filterPresets.d.ts.map +1 -1
  190. package/dist/telemetry/filterPresets.js +5 -2
  191. package/dist/telemetry/filterPresets.js.map +1 -1
  192. package/dist/telemetry/network.d.ts.map +1 -1
  193. package/dist/telemetry/network.js +1 -1
  194. package/dist/telemetry/network.js.map +1 -1
  195. package/dist/types.d.ts +217 -1
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js +33 -1
  198. package/dist/types.js.map +1 -1
  199. package/dist/ui/OutputBuilder.d.ts +2 -44
  200. package/dist/ui/OutputBuilder.d.ts.map +1 -1
  201. package/dist/ui/OutputBuilder.js +71 -103
  202. package/dist/ui/OutputBuilder.js.map +1 -1
  203. package/dist/ui/errors/CommandError.d.ts +0 -2
  204. package/dist/ui/errors/CommandError.d.ts.map +1 -1
  205. package/dist/ui/errors/CommandError.js.map +1 -1
  206. package/dist/ui/formatters/dom.d.ts +8 -19
  207. package/dist/ui/formatters/dom.d.ts.map +1 -1
  208. package/dist/ui/formatters/dom.js +12 -68
  209. package/dist/ui/formatters/dom.js.map +1 -1
  210. package/dist/ui/formatters/form.d.ts +15 -0
  211. package/dist/ui/formatters/form.d.ts.map +1 -0
  212. package/dist/ui/formatters/form.js +314 -0
  213. package/dist/ui/formatters/form.js.map +1 -0
  214. package/dist/ui/logging/logger.d.ts +1 -1
  215. package/dist/ui/logging/logger.d.ts.map +1 -1
  216. package/dist/ui/logging/logger.js +1 -1
  217. package/dist/ui/logging/logger.js.map +1 -1
  218. package/dist/ui/messages/dom.d.ts +14 -0
  219. package/dist/ui/messages/dom.d.ts.map +1 -0
  220. package/dist/ui/messages/dom.js +28 -0
  221. package/dist/ui/messages/dom.js.map +1 -0
  222. package/dist/ui/messages/errors.d.ts +122 -10
  223. package/dist/ui/messages/errors.d.ts.map +1 -1
  224. package/dist/ui/messages/errors.js +280 -11
  225. package/dist/ui/messages/errors.js.map +1 -1
  226. package/dist/utils/__tests__/shellDetection.unit.test.d.ts +12 -0
  227. package/dist/utils/__tests__/shellDetection.unit.test.d.ts.map +1 -0
  228. package/dist/utils/__tests__/shellDetection.unit.test.js +220 -0
  229. package/dist/utils/__tests__/shellDetection.unit.test.js.map +1 -0
  230. package/dist/utils/async.d.ts +10 -0
  231. package/dist/utils/async.d.ts.map +1 -0
  232. package/dist/utils/async.js +12 -0
  233. package/dist/utils/async.js.map +1 -0
  234. package/dist/utils/atomicFile.js +1 -1
  235. package/dist/utils/atomicFile.js.map +1 -1
  236. package/dist/utils/decisionTrees.d.ts.map +1 -1
  237. package/dist/utils/decisionTrees.js +5 -0
  238. package/dist/utils/decisionTrees.js.map +1 -1
  239. package/dist/utils/errorMapping.d.ts.map +1 -1
  240. package/dist/utils/errorMapping.js +4 -1
  241. package/dist/utils/errorMapping.js.map +1 -1
  242. package/dist/utils/exitCodes.d.ts +18 -17
  243. package/dist/utils/exitCodes.d.ts.map +1 -1
  244. package/dist/utils/exitCodes.js +97 -17
  245. package/dist/utils/exitCodes.js.map +1 -1
  246. package/dist/utils/shellDetection.d.ts +34 -0
  247. package/dist/utils/shellDetection.d.ts.map +1 -0
  248. package/dist/utils/shellDetection.js +94 -0
  249. package/dist/utils/shellDetection.js.map +1 -0
  250. package/dist/utils/suggestions.d.ts +19 -0
  251. package/dist/utils/suggestions.d.ts.map +1 -0
  252. package/dist/utils/suggestions.js +34 -0
  253. package/dist/utils/suggestions.js.map +1 -0
  254. package/dist/utils/taskMappings.d.ts.map +1 -1
  255. package/dist/utils/taskMappings.js +40 -0
  256. package/dist/utils/taskMappings.js.map +1 -1
  257. package/dist/utils/url.d.ts.map +1 -1
  258. package/dist/utils/url.js +14 -0
  259. package/dist/utils/url.js.map +1 -1
  260. package/package.json +5 -5
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Form discovery command for semantic form inspection.
3
+ *
4
+ * Discovers forms on the page with semantic labels, current values,
5
+ * validation state, and suggested commands for agent consumption.
6
+ */
7
+ import type { Command } from 'commander';
8
+ /**
9
+ * Register form discovery command.
10
+ *
11
+ * @param domCommand - DOM command group
12
+ */
13
+ export declare function registerFormCommand(domCommand: Command): void;
14
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/commands/dom/form.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2gBzC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAU7D"}
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Form discovery command for semantic form inspection.
3
+ *
4
+ * Discovers forms on the page with semantic labels, current values,
5
+ * validation state, and suggested commands for agent consumption.
6
+ */
7
+ import { FORM_DISCOVERY_SCRIPT, isRawFormData } from '../../commands/dom/formDiscovery.js';
8
+ import { runCommand } from '../../commands/shared/CommandRunner.js';
9
+ import { jsonOption } from '../../commands/shared/commonOptions.js';
10
+ import { QueryCacheManager } from '../../session/QueryCacheManager.js';
11
+ import { CommandError } from '../../ui/errors/index.js';
12
+ import { formatFormDiscovery } from '../../ui/formatters/form.js';
13
+ import { createLogger } from '../../ui/logging/index.js';
14
+ import { noFormsFoundError, formInIframeError } from '../../ui/messages/errors.js';
15
+ import { EXIT_CODES } from '../../utils/exitCodes.js';
16
+ const log = createLogger('dom');
17
+ /**
18
+ * Execute form discovery in page context.
19
+ *
20
+ * @param cdp - CDP connection
21
+ * @returns Raw form data from page
22
+ */
23
+ async function executeFormDiscovery(cdp) {
24
+ const response = await cdp.send('Runtime.evaluate', {
25
+ expression: FORM_DISCOVERY_SCRIPT,
26
+ returnByValue: true,
27
+ });
28
+ const cdpResponse = response;
29
+ if (cdpResponse.exceptionDetails) {
30
+ throw new CommandError(`Form discovery failed: ${cdpResponse.exceptionDetails.text}`, { suggestion: 'Check if page has loaded completely' }, EXIT_CODES.SOFTWARE_ERROR);
31
+ }
32
+ const rawData = cdpResponse.result?.value;
33
+ if (!isRawFormData(rawData)) {
34
+ throw new CommandError('Unexpected form discovery response', { suggestion: 'This may be a bug - please report it' }, EXIT_CODES.SOFTWARE_ERROR);
35
+ }
36
+ return rawData;
37
+ }
38
+ /**
39
+ * Build validation state from raw field data.
40
+ *
41
+ * @param raw - Raw field data
42
+ * @returns Structured validation state
43
+ */
44
+ function buildValidation(raw) {
45
+ const hasNativeError = !raw.isValid && raw.validationMessage;
46
+ const hasAriaError = raw.ariaInvalid;
47
+ const hasSiblingError = !!raw.siblingErrorText;
48
+ const hasClassError = raw.hasErrorClass;
49
+ if (hasNativeError) {
50
+ return {
51
+ valid: false,
52
+ message: raw.validationMessage,
53
+ source: 'native',
54
+ confidence: 'high',
55
+ };
56
+ }
57
+ if (hasAriaError) {
58
+ return {
59
+ valid: false,
60
+ message: raw.siblingErrorText ?? 'Field is invalid',
61
+ source: 'aria',
62
+ confidence: 'high',
63
+ };
64
+ }
65
+ if (hasSiblingError) {
66
+ return {
67
+ valid: false,
68
+ message: raw.siblingErrorText,
69
+ source: 'sibling',
70
+ confidence: 'medium',
71
+ };
72
+ }
73
+ if (hasClassError) {
74
+ return {
75
+ valid: false,
76
+ message: 'Field has error styling',
77
+ source: 'heuristic',
78
+ confidence: 'low',
79
+ };
80
+ }
81
+ return {
82
+ valid: true,
83
+ confidence: 'high',
84
+ };
85
+ }
86
+ /**
87
+ * Build field state from raw value.
88
+ *
89
+ * @param raw - Raw field data
90
+ * @returns Field state
91
+ */
92
+ function buildFieldState(raw) {
93
+ const type = raw.type.toLowerCase();
94
+ if (type === 'checkbox' || type === 'radio' || type === 'switch') {
95
+ return raw.checked || raw.value === true ? 'checked' : 'unchecked';
96
+ }
97
+ if (Array.isArray(raw.value)) {
98
+ return raw.value.length > 0 ? 'filled' : 'empty';
99
+ }
100
+ if (typeof raw.value === 'string') {
101
+ return raw.value.length > 0 ? 'filled' : 'empty';
102
+ }
103
+ return 'empty';
104
+ }
105
+ /**
106
+ * Build masked value for password fields.
107
+ *
108
+ * @param raw - Raw field data
109
+ * @returns Masked value string
110
+ */
111
+ function buildMaskedValue(raw) {
112
+ if (raw.inputType === 'password' && typeof raw.value === 'string' && raw.value.length > 0) {
113
+ return '•'.repeat(Math.min(raw.value.length, 8));
114
+ }
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Build interaction warning for non-native fields.
119
+ *
120
+ * @param raw - Raw field data
121
+ * @returns Warning message or undefined
122
+ */
123
+ function buildInteractionWarning(raw) {
124
+ if (raw.native) {
125
+ return undefined;
126
+ }
127
+ const type = raw.type.toLowerCase();
128
+ if (type === 'contenteditable') {
129
+ return 'Custom contenteditable - use click + type instead of fill';
130
+ }
131
+ if (type === 'combobox' || type === 'listbox') {
132
+ return 'Custom dropdown - click to open, then select option';
133
+ }
134
+ if (type === 'textbox') {
135
+ return 'Custom textbox - fill may not trigger framework events';
136
+ }
137
+ return 'Custom component - standard fill may not work';
138
+ }
139
+ /**
140
+ * Build fill command for a field.
141
+ *
142
+ * @param index - Global element index
143
+ * @param type - Field type
144
+ * @returns Command string
145
+ */
146
+ function buildFieldCommand(index, type) {
147
+ const lowerType = type.toLowerCase();
148
+ if (lowerType === 'checkbox' || lowerType === 'radio' || lowerType === 'switch') {
149
+ return `bdg dom click ${index}`;
150
+ }
151
+ if (lowerType === 'file') {
152
+ return `bdg dom click ${index}`;
153
+ }
154
+ return `bdg dom fill ${index} "<value>"`;
155
+ }
156
+ /**
157
+ * Build selector-based command for a field.
158
+ *
159
+ * @param selector - CSS selector
160
+ * @param type - Field type
161
+ * @returns Command string
162
+ */
163
+ function buildSelectorCommand(selector, type) {
164
+ const lowerType = type.toLowerCase();
165
+ // Escape backslashes first, then double quotes (CodeQL js/incomplete-string-escaping)
166
+ const escaped = selector.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
167
+ if (lowerType === 'checkbox' || lowerType === 'radio' || lowerType === 'switch') {
168
+ return `bdg dom click "${escaped}"`;
169
+ }
170
+ if (lowerType === 'file') {
171
+ return `bdg dom click "${escaped}"`;
172
+ }
173
+ return `bdg dom fill "${escaped}" "<value>"`;
174
+ }
175
+ /**
176
+ * Build alternative command for non-native fields.
177
+ *
178
+ * @param index - Global element index
179
+ * @param raw - Raw field data
180
+ * @returns Alternative command or undefined
181
+ */
182
+ function buildAlternativeCommand(index, raw) {
183
+ if (raw.native) {
184
+ return undefined;
185
+ }
186
+ const type = raw.type.toLowerCase();
187
+ if (type === 'contenteditable' || type === 'textbox') {
188
+ return `bdg dom click ${index} && bdg dom pressKey ${index} "<value>"`;
189
+ }
190
+ return undefined;
191
+ }
192
+ /**
193
+ * Transform raw field to structured FormField.
194
+ *
195
+ * @param raw - Raw field data
196
+ * @returns Structured FormField
197
+ */
198
+ function transformField(raw) {
199
+ return {
200
+ index: raw.index,
201
+ formIndex: raw.formIndex,
202
+ selector: raw.selector,
203
+ type: raw.type,
204
+ inputType: raw.inputType,
205
+ label: raw.label,
206
+ name: raw.name,
207
+ placeholder: raw.placeholder,
208
+ required: raw.required,
209
+ disabled: raw.disabled,
210
+ readOnly: raw.readOnly,
211
+ hidden: raw.hidden,
212
+ native: raw.native,
213
+ interactionWarning: buildInteractionWarning(raw),
214
+ state: buildFieldState(raw),
215
+ value: raw.value,
216
+ maskedValue: buildMaskedValue(raw),
217
+ validation: buildValidation(raw),
218
+ options: raw.options,
219
+ command: buildFieldCommand(raw.index, raw.type),
220
+ selectorCommand: buildSelectorCommand(raw.selector, raw.type),
221
+ alternativeCommand: buildAlternativeCommand(raw.index, raw),
222
+ };
223
+ }
224
+ /**
225
+ * Transform raw button to structured FormButton.
226
+ *
227
+ * @param raw - Raw button data
228
+ * @returns Structured FormButton
229
+ */
230
+ function transformButton(raw) {
231
+ return {
232
+ index: raw.index,
233
+ selector: raw.selector,
234
+ label: raw.label,
235
+ type: raw.type,
236
+ primary: raw.isPrimary,
237
+ enabled: !raw.disabled,
238
+ disabledReason: raw.disabled ? 'Button is disabled' : undefined,
239
+ command: `bdg dom click ${raw.index}`,
240
+ };
241
+ }
242
+ /**
243
+ * Calculate form summary statistics.
244
+ *
245
+ * @param fields - Transformed form fields
246
+ * @param buttons - Transformed form buttons
247
+ * @returns Form summary
248
+ */
249
+ function calculateSummary(fields, buttons) {
250
+ const visibleFields = fields.filter((f) => !f.hidden);
251
+ const requiredFields = visibleFields.filter((f) => f.required);
252
+ const filledFields = visibleFields.filter((f) => f.state === 'filled' || f.state === 'checked');
253
+ const validFields = visibleFields.filter((f) => f.validation.valid);
254
+ const invalidFields = visibleFields.filter((f) => !f.validation.valid);
255
+ const emptyRequired = requiredFields.filter((f) => f.state === 'empty' || f.state === 'unchecked');
256
+ const blockers = [];
257
+ for (const field of emptyRequired) {
258
+ blockers.push({
259
+ index: field.index,
260
+ label: field.label,
261
+ reason: 'Required field is empty',
262
+ command: field.command,
263
+ });
264
+ }
265
+ for (const field of invalidFields) {
266
+ if (!emptyRequired.includes(field)) {
267
+ blockers.push({
268
+ index: field.index,
269
+ label: field.label,
270
+ reason: field.validation.message ?? 'Validation failed',
271
+ command: field.command,
272
+ });
273
+ }
274
+ }
275
+ const submitButton = buttons.find((b) => b.type === 'submit' && b.primary);
276
+ if (submitButton && !submitButton.enabled) {
277
+ blockers.push({
278
+ index: submitButton.index,
279
+ label: submitButton.label,
280
+ reason: submitButton.disabledReason ?? 'Submit button is disabled',
281
+ command: submitButton.command,
282
+ });
283
+ }
284
+ return {
285
+ totalFields: visibleFields.length,
286
+ filledFields: filledFields.length,
287
+ emptyFields: visibleFields.length - filledFields.length,
288
+ validFields: validFields.length,
289
+ invalidFields: invalidFields.length,
290
+ requiredTotal: requiredFields.length,
291
+ requiredFilled: requiredFields.length - emptyRequired.length,
292
+ requiredRemaining: emptyRequired.length,
293
+ readyToSubmit: blockers.length === 0,
294
+ blockers,
295
+ };
296
+ }
297
+ /**
298
+ * Transform raw form to structured DiscoveredForm.
299
+ *
300
+ * @param raw - Raw form data
301
+ * @returns Structured DiscoveredForm
302
+ */
303
+ function transformForm(raw) {
304
+ const fields = raw.fields.map(transformField);
305
+ const buttons = raw.buttons.map(transformButton);
306
+ return {
307
+ index: raw.index,
308
+ name: raw.name,
309
+ action: raw.action,
310
+ method: raw.method,
311
+ step: raw.step ?? undefined,
312
+ relevanceScore: raw.relevanceScore,
313
+ fields,
314
+ buttons,
315
+ summary: calculateSummary(fields, buttons),
316
+ };
317
+ }
318
+ /**
319
+ * Cache form elements for index-based access.
320
+ *
321
+ * @param forms - Discovered forms
322
+ */
323
+ async function cacheFormElements(forms) {
324
+ const cacheManager = QueryCacheManager.getInstance();
325
+ const allElements = [];
326
+ for (const form of forms) {
327
+ for (const field of form.fields) {
328
+ allElements.push({
329
+ index: field.index,
330
+ nodeId: 0,
331
+ selector: field.selector,
332
+ });
333
+ }
334
+ for (const button of form.buttons) {
335
+ allElements.push({
336
+ index: button.index,
337
+ nodeId: 0,
338
+ selector: button.selector,
339
+ });
340
+ }
341
+ }
342
+ const navigationId = await cacheManager.getCurrentNavigationId();
343
+ await cacheManager.set({
344
+ selector: 'form:auto-discovered',
345
+ count: allElements.length,
346
+ nodes: allElements.map((el) => ({
347
+ index: el.index,
348
+ nodeId: el.nodeId,
349
+ tag: 'input',
350
+ preview: el.selector,
351
+ })),
352
+ ...(navigationId !== null && { navigationId }),
353
+ });
354
+ log.debug(`Cached ${allElements.length} form elements`);
355
+ }
356
+ /**
357
+ * Execute CDP connection lifecycle for form discovery.
358
+ *
359
+ * @param fn - Function to execute with CDP connection
360
+ * @returns Result from function
361
+ */
362
+ async function withCDPConnection(fn) {
363
+ const { CDPConnection } = await import('../../connection/cdp.js');
364
+ const { validateActiveSession, getValidatedSessionMetadata, verifyTargetExists } = await import('../../commands/dom/evalHelpers.js');
365
+ validateActiveSession();
366
+ const metadata = getValidatedSessionMetadata();
367
+ const port = 9222;
368
+ await verifyTargetExists(metadata, port);
369
+ const cdp = new CDPConnection();
370
+ if (!metadata.webSocketDebuggerUrl) {
371
+ throw new CommandError('Session metadata missing webSocketDebuggerUrl', { suggestion: 'Start a new session with: bdg <url>' }, EXIT_CODES.SESSION_FILE_ERROR);
372
+ }
373
+ await cdp.connect(metadata.webSocketDebuggerUrl);
374
+ try {
375
+ return await fn(cdp);
376
+ }
377
+ finally {
378
+ cdp.close();
379
+ }
380
+ }
381
+ /**
382
+ * Handle form discovery command.
383
+ *
384
+ * @param options - Command options
385
+ */
386
+ async function handleFormCommand(options) {
387
+ await runCommand(async () => {
388
+ return await withCDPConnection(async (cdp) => {
389
+ const rawData = await executeFormDiscovery(cdp);
390
+ if (rawData.forms.length === 0) {
391
+ const err = noFormsFoundError();
392
+ return {
393
+ success: false,
394
+ error: err.message,
395
+ exitCode: EXIT_CODES.NO_FORMS_FOUND,
396
+ errorContext: { suggestion: err.suggestion },
397
+ };
398
+ }
399
+ const iframeForm = rawData.forms.find((f) => f.inIframe);
400
+ if (iframeForm && rawData.forms.length === 1) {
401
+ const err = formInIframeError(iframeForm.iframeUrl ?? 'unknown', iframeForm.crossOrigin ?? false);
402
+ return {
403
+ success: false,
404
+ error: err.message,
405
+ exitCode: EXIT_CODES.FORM_IN_IFRAME,
406
+ errorContext: { suggestion: err.suggestion },
407
+ };
408
+ }
409
+ const allForms = rawData.forms.map(transformForm);
410
+ const forms = options.all ? allForms : [allForms[0]];
411
+ // Cache ALL forms so global indices work with bdg dom fill/click
412
+ await cacheFormElements(allForms);
413
+ const result = {
414
+ formCount: rawData.forms.length,
415
+ selectedForm: 0,
416
+ forms,
417
+ brief: options.brief,
418
+ };
419
+ return { success: true, data: result };
420
+ });
421
+ }, options, formatFormDiscovery);
422
+ }
423
+ /**
424
+ * Register form discovery command.
425
+ *
426
+ * @param domCommand - DOM command group
427
+ */
428
+ export function registerFormCommand(domCommand) {
429
+ domCommand
430
+ .command('form')
431
+ .description('Discover forms with semantic labels, values, and validation state')
432
+ .option('--all', 'Show all forms expanded')
433
+ .option('--brief', 'Quick scan: field names, types, and required status only')
434
+ .addOption(jsonOption())
435
+ .action(async (options) => {
436
+ await handleFormCommand(options);
437
+ });
438
+ }
439
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../../src/commands/dom/form.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Form discovery script executed in page context.
3
+ *
4
+ * Discovers forms, extracts semantic labels, detects state and validation,
5
+ * and returns structured data for agent consumption.
6
+ */
7
+ import type { RawFormData } from '../../commands/dom/formTypes.js';
8
+ /**
9
+ * Page-context script for form discovery.
10
+ *
11
+ * This script runs in the browser via Runtime.evaluate and discovers:
12
+ * - Native form elements and inputs
13
+ * - Custom components with ARIA roles
14
+ * - Labels via priority chain (label[for], aria-label, placeholder, etc.)
15
+ * - Current values and validation state
16
+ * - Form relevance scoring for multi-form pages
17
+ */
18
+ export declare const FORM_DISCOVERY_SCRIPT = "\n(function() {\n const result = { forms: [] };\n\n function generateSelector(element) {\n if (element.id) {\n return '#' + CSS.escape(element.id);\n }\n if (element.name) {\n const tag = element.tagName.toLowerCase();\n return tag + '[name=\"' + CSS.escape(element.name) + '\"]';\n }\n const tag = element.tagName.toLowerCase();\n const parent = element.parentElement;\n if (!parent) return tag;\n const siblings = Array.from(parent.children).filter(c => c.tagName === element.tagName);\n if (siblings.length === 1) {\n const parentSelector = generateSelector(parent);\n return parentSelector + ' > ' + tag;\n }\n const index = siblings.indexOf(element);\n const parentSelector = generateSelector(parent);\n return parentSelector + ' > ' + tag + ':nth-of-type(' + (index + 1) + ')';\n }\n\n function cleanLabelText(text) {\n if (!text) return text;\n const patterns = [\n /Previous\\s*arrow/gi,\n /Next\\s*arrow/gi,\n /\\barrow\\b/gi,\n /\\bchevron\\b/gi,\n /\u2190|\u2192|\u2191|\u2193|\u25B2|\u25BC|\u25C0|\u25B6/g,\n /\\u25C0|\\u25B6|\\u25B2|\\u25BC/g,\n /Previous|Next|Back|Forward/gi,\n /^\\s*[<>]\\s*/,\n /\\s*[<>]\\s*$/,\n ];\n let cleaned = text;\n for (const pattern of patterns) {\n cleaned = cleaned.replace(pattern, '');\n }\n cleaned = cleaned.replace(/\\s{2,}/g, ' ').trim();\n return cleaned || text.trim();\n }\n\n function extractLabel(element) {\n if (element.id) {\n const labelFor = document.querySelector('label[for=\"' + CSS.escape(element.id) + '\"]');\n if (labelFor) return cleanLabelText(labelFor.textContent);\n }\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel) return cleanLabelText(ariaLabel);\n const ariaLabelledBy = element.getAttribute('aria-labelledby');\n if (ariaLabelledBy) {\n const labelEl = document.getElementById(ariaLabelledBy);\n if (labelEl) return cleanLabelText(labelEl.textContent);\n }\n const wrappingLabel = element.closest('label');\n if (wrappingLabel) {\n const clone = wrappingLabel.cloneNode(true);\n const inputs = clone.querySelectorAll('input, select, textarea, button');\n inputs.forEach(i => i.remove());\n const text = clone.textContent.trim();\n if (text) return cleanLabelText(text);\n }\n if (element.placeholder) return cleanLabelText(element.placeholder);\n const title = element.getAttribute('title');\n if (title) return cleanLabelText(title);\n if (element.name) {\n return element.name\n .replace(/[_-]/g, ' ')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/^./, s => s.toUpperCase());\n }\n return 'Unlabeled field';\n }\n\n function extractButtonLabel(element) {\n const ariaLabel = element.getAttribute('aria-label');\n if (ariaLabel) return ariaLabel.trim();\n if (element.value && element.type !== 'submit') return element.value;\n const text = element.textContent.trim();\n if (text) return text;\n if (element.value) return element.value;\n const title = element.getAttribute('title');\n if (title) return title;\n return 'Button';\n }\n\n function getFieldValue(element) {\n const tag = element.tagName.toLowerCase();\n const type = element.type?.toLowerCase() || 'text';\n if (tag === 'select') {\n if (element.multiple) {\n return Array.from(element.selectedOptions).map(o => o.value);\n }\n return element.value;\n }\n if (type === 'checkbox' || type === 'radio') {\n return element.checked;\n }\n if (element.getAttribute('role') === 'checkbox' || element.getAttribute('role') === 'switch') {\n return element.getAttribute('aria-checked') === 'true';\n }\n if (element.isContentEditable) {\n return element.textContent || '';\n }\n return element.value || '';\n }\n\n function getFieldState(element, value) {\n const type = element.type?.toLowerCase() || 'text';\n if (type === 'checkbox' || element.getAttribute('role') === 'checkbox' || element.getAttribute('role') === 'switch') {\n return value ? 'checked' : 'unchecked';\n }\n if (type === 'radio') {\n return value ? 'checked' : 'unchecked';\n }\n if (Array.isArray(value)) {\n return value.length > 0 ? 'filled' : 'empty';\n }\n if (typeof value === 'string') {\n return value.length > 0 ? 'filled' : 'empty';\n }\n return 'empty';\n }\n\n function getFieldType(element) {\n const tag = element.tagName.toLowerCase();\n const role = element.getAttribute('role');\n if (role === 'textbox') return 'textbox';\n if (role === 'checkbox') return 'checkbox';\n if (role === 'radio') return 'radio';\n if (role === 'combobox') return 'combobox';\n if (role === 'listbox') return 'listbox';\n if (role === 'switch') return 'switch';\n if (element.isContentEditable) return 'contenteditable';\n if (tag === 'textarea') return 'textarea';\n if (tag === 'select') return 'select';\n if (tag === 'input') {\n const type = element.type?.toLowerCase() || 'text';\n return type;\n }\n return 'unknown';\n }\n\n function isNativeInput(element) {\n const tag = element.tagName.toLowerCase();\n return tag === 'input' || tag === 'textarea' || tag === 'select';\n }\n\n function getSelectOptions(element) {\n if (element.tagName.toLowerCase() !== 'select') return undefined;\n return Array.from(element.options).map(opt => ({\n value: opt.value,\n label: opt.textContent.trim(),\n selected: opt.selected\n }));\n }\n\n function findSiblingError(element) {\n const next = element.nextElementSibling;\n if (next) {\n const isError = next.classList.contains('error') ||\n next.classList.contains('invalid') ||\n next.classList.contains('field-error') ||\n next.classList.contains('error-message') ||\n next.getAttribute('role') === 'alert';\n if (isError) return next.textContent.trim();\n }\n const parent = element.parentElement;\n if (parent) {\n const errorEl = parent.querySelector('.error-message, .field-error, [role=\"alert\"]');\n if (errorEl && errorEl !== element) return errorEl.textContent.trim();\n }\n return undefined;\n }\n\n function calculateRelevance(formEl, fields, buttons) {\n let score = 0;\n const rect = formEl.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n const viewportWidth = window.innerWidth;\n const centerX = rect.left + rect.width / 2;\n const centerY = rect.top + rect.height / 2;\n if (centerX > viewportWidth * 0.2 && centerX < viewportWidth * 0.8) score += 10;\n if (centerY > 0 && centerY < viewportHeight) score += 5;\n const isInMain = formEl.closest('main, [role=\"main\"], article, .main-content, #main');\n if (isInMain) score += 15;\n const isInHeader = formEl.closest('header, [role=\"banner\"], nav, [role=\"navigation\"]');\n if (isInHeader) score -= 10;\n const isInAside = formEl.closest('aside, [role=\"complementary\"], footer, [role=\"contentinfo\"]');\n if (isInAside) score -= 5;\n score += Math.min(fields.length * 3, 30);\n const hasSubmit = buttons.some(b => b.type === 'submit' || b.isPrimary);\n if (hasSubmit) score += 10;\n const style = window.getComputedStyle(formEl);\n if (style.display === 'none' || style.visibility === 'hidden') score -= 100;\n return score;\n }\n\n function detectFormStep(formEl) {\n const stepIndicators = formEl.querySelectorAll('[aria-current=\"step\"], .step.active, .wizard-step.current');\n if (stepIndicators.length === 0) {\n const pageSteps = document.querySelectorAll('.step, .wizard-step, [role=\"tablist\"] [role=\"tab\"]');\n if (pageSteps.length > 1) {\n const activeIndex = Array.from(pageSteps).findIndex(s =>\n s.classList.contains('active') ||\n s.classList.contains('current') ||\n s.getAttribute('aria-selected') === 'true'\n );\n if (activeIndex >= 0) {\n return { current: activeIndex + 1, total: pageSteps.length };\n }\n }\n }\n const ariaStep = formEl.querySelector('[aria-current=\"step\"]');\n if (ariaStep) {\n const allSteps = formEl.querySelectorAll('[role=\"listitem\"], .step');\n const currentIdx = Array.from(allSteps).indexOf(ariaStep);\n if (currentIdx >= 0) {\n return { current: currentIdx + 1, total: allSteps.length };\n }\n }\n return null;\n }\n\n function findNearbyHeading(formEl) {\n let sibling = formEl.previousElementSibling;\n let distance = 0;\n while (sibling && distance < 3) {\n if (sibling.matches('h1, h2, h3, h4, h5, h6, [role=\"heading\"]')) {\n return sibling.textContent.trim();\n }\n const heading = sibling.querySelector('h1, h2, h3, h4, h5, h6, [role=\"heading\"]');\n if (heading) return heading.textContent.trim();\n sibling = sibling.previousElementSibling;\n distance++;\n }\n const parent = formEl.parentElement;\n if (parent) {\n const heading = parent.querySelector(':scope > h1, :scope > h2, :scope > h3');\n if (heading && heading.compareDocumentPosition(formEl) & Node.DOCUMENT_POSITION_FOLLOWING) {\n return heading.textContent.trim();\n }\n }\n return null;\n }\n\n function matchesWord(text, word) {\n const pattern = new RegExp('\\\\b' + word + '\\\\b', 'i');\n return pattern.test(text);\n }\n\n function inferFormType(formEl) {\n const inputs = formEl.querySelectorAll('input, textarea, select');\n const types = Array.from(inputs).map(i => i.type?.toLowerCase() || i.tagName.toLowerCase());\n const names = Array.from(inputs).map(i => (i.name || '').toLowerCase());\n const allText = (Array.from(inputs).map(i => i.name + ' ' + i.placeholder + ' ' + i.id).join(' ')).toLowerCase();\n if (types.includes('password')) {\n if (types.filter(t => t === 'password').length >= 2) return 'Change Password';\n if (matchesWord(allText, 'register') || matchesWord(allText, 'signup') || matchesWord(allText, 'create')) return 'Registration';\n return 'Login';\n }\n const hasSearchRole = formEl.closest('[role=\"search\"]') || formEl.getAttribute('role') === 'search';\n const hasSearchInput = formEl.querySelector('[type=\"search\"], [aria-label*=\"search\" i]');\n if (hasSearchRole || hasSearchInput) return 'Search';\n const interactiveTypes = types.filter(t => !['hidden', 'submit', 'button', 'reset', 'image'].includes(t));\n if (interactiveTypes.length <= 2 && matchesWord(allText, 'search')) return 'Search';\n if (matchesWord(allText, 'address') || matchesWord(allText, 'street') || matchesWord(allText, 'postcode') ||\n matchesWord(allText, 'zipcode') || matchesWord(allText, 'city') || matchesWord(allText, 'county')) {\n return 'Address';\n }\n if (matchesWord(allText, 'email') && (matchesWord(allText, 'message') || matchesWord(allText, 'subject'))) {\n return 'Contact';\n }\n if (matchesWord(allText, 'card') || matchesWord(allText, 'cvv') || matchesWord(allText, 'expiry')) {\n return 'Payment';\n }\n if (names.some(n => matchesWord(n, 'subscribe') || matchesWord(n, 'newsletter'))) {\n return 'Newsletter';\n }\n return null;\n }\n\n function extractFormName(formEl) {\n const ariaLabel = formEl.getAttribute('aria-label');\n if (ariaLabel) return ariaLabel;\n const ariaLabelledBy = formEl.getAttribute('aria-labelledby');\n if (ariaLabelledBy) {\n const labelEl = document.getElementById(ariaLabelledBy);\n if (labelEl) return labelEl.textContent.trim();\n }\n const headings = formEl.querySelectorAll('h1, h2, h3, h4, h5, h6, [role=\"heading\"]');\n for (const h of headings) {\n if (!h.closest('[role=\"dialog\"], [role=\"alertdialog\"], [aria-modal=\"true\"]')) {\n return h.textContent.trim();\n }\n }\n const legend = formEl.querySelector('legend');\n if (legend) return legend.textContent.trim();\n const title = formEl.getAttribute('title');\n if (title) return title;\n const name = formEl.getAttribute('name');\n if (name) {\n return name\n .replace(/[_-]/g, ' ')\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/^./, s => s.toUpperCase());\n }\n const nearbyHeading = findNearbyHeading(formEl);\n if (nearbyHeading) return nearbyHeading;\n const inferredType = inferFormType(formEl);\n if (inferredType) return inferredType;\n return null;\n }\n\n function discoverFields(container, formIndex, startIndex) {\n const fields = [];\n const nativeInputs = container.querySelectorAll(\n 'input:not([type=\"hidden\"]):not([type=\"submit\"]):not([type=\"button\"]):not([type=\"reset\"]):not([type=\"image\"]), ' +\n 'textarea, ' +\n 'select'\n );\n const customInputs = container.querySelectorAll(\n '[role=\"textbox\"], ' +\n '[role=\"checkbox\"]:not(input), ' +\n '[role=\"radio\"]:not(input), ' +\n '[role=\"combobox\"], ' +\n '[role=\"listbox\"], ' +\n '[role=\"switch\"], ' +\n '[contenteditable=\"true\"]'\n );\n const allInputs = new Set([...nativeInputs, ...customInputs]);\n let idx = startIndex;\n for (const el of allInputs) {\n const style = window.getComputedStyle(el);\n const isHidden = style.display === 'none' || style.visibility === 'hidden' || el.type === 'hidden';\n const value = getFieldValue(el);\n fields.push({\n index: idx,\n formIndex: formIndex,\n selector: generateSelector(el),\n type: getFieldType(el),\n inputType: el.type?.toLowerCase(),\n label: extractLabel(el),\n name: el.name || null,\n placeholder: el.placeholder || undefined,\n required: el.required || el.getAttribute('aria-required') === 'true',\n disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',\n readOnly: el.readOnly || false,\n hidden: isHidden,\n native: isNativeInput(el),\n value: value,\n checked: el.checked,\n validationMessage: el.validationMessage || undefined,\n isValid: el.checkValidity ? el.checkValidity() : true,\n ariaInvalid: el.getAttribute('aria-invalid') === 'true',\n hasErrorClass: el.classList.contains('error') || el.classList.contains('invalid'),\n siblingErrorText: findSiblingError(el),\n options: getSelectOptions(el)\n });\n idx++;\n }\n return fields;\n }\n\n function discoverButtons(container, startIndex) {\n const buttons = [];\n const buttonEls = container.querySelectorAll(\n 'button, ' +\n 'input[type=\"submit\"], ' +\n 'input[type=\"button\"], ' +\n 'input[type=\"reset\"], ' +\n '[role=\"button\"]'\n );\n let idx = startIndex;\n for (const el of buttonEls) {\n const style = window.getComputedStyle(el);\n if (style.display === 'none' || style.visibility === 'hidden') continue;\n const type = el.type?.toLowerCase() || 'button';\n const btnType = type === 'submit' ? 'submit' : type === 'reset' ? 'reset' : 'button';\n const isPrimary = btnType === 'submit' ||\n el.classList.contains('primary') ||\n el.classList.contains('btn-primary') ||\n el.classList.contains('submit');\n buttons.push({\n index: idx,\n selector: generateSelector(el),\n label: extractButtonLabel(el),\n type: btnType,\n disabled: el.disabled || el.getAttribute('aria-disabled') === 'true',\n isPrimary: isPrimary\n });\n idx++;\n }\n return buttons;\n }\n\n const forms = document.querySelectorAll('form');\n let globalIndex = 0;\n\n if (forms.length === 0) {\n const bodyFields = discoverFields(document.body, 0, 0);\n const bodyButtons = discoverButtons(document.body, bodyFields.length);\n if (bodyFields.length > 0) {\n result.forms.push({\n index: 0,\n name: document.title || 'Page',\n action: null,\n method: 'GET',\n step: null,\n relevanceScore: bodyFields.length * 3,\n inIframe: false,\n fields: bodyFields,\n buttons: bodyButtons\n });\n }\n } else {\n for (let i = 0; i < forms.length; i++) {\n const formEl = forms[i];\n const fields = discoverFields(formEl, i, globalIndex);\n globalIndex += fields.length;\n const buttons = discoverButtons(formEl, globalIndex);\n globalIndex += buttons.length;\n const inIframe = formEl.ownerDocument !== document;\n result.forms.push({\n index: i,\n name: extractFormName(formEl),\n action: formEl.action || null,\n method: (formEl.method || 'GET').toUpperCase(),\n step: detectFormStep(formEl),\n relevanceScore: calculateRelevance(formEl, fields, buttons),\n inIframe: inIframe,\n fields: fields,\n buttons: buttons\n });\n }\n }\n\n result.forms.sort((a, b) => b.relevanceScore - a.relevanceScore);\n\n return result;\n})()\n";
19
+ /**
20
+ * Type guard for raw form data.
21
+ *
22
+ * @param value - Unknown value to check
23
+ * @returns True if value matches RawFormData structure
24
+ */
25
+ export declare function isRawFormData(value: unknown): value is RawFormData;
26
+ //# sourceMappingURL=formDiscovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formDiscovery.d.ts","sourceRoot":"","sources":["../../../src/commands/dom/formDiscovery.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE/D;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,s9hBA6bjC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAMlE"}