browser-debugger-cli 0.6.11 → 0.7.1
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.
- package/README.md +2 -0
- package/dist/commands/cdp.d.ts.map +1 -1
- package/dist/commands/cdp.js +90 -15
- package/dist/commands/cdp.js.map +1 -1
- package/dist/commands/dom/DomElementResolver.d.ts.map +1 -1
- package/dist/commands/dom/DomElementResolver.js +28 -7
- package/dist/commands/dom/DomElementResolver.js.map +1 -1
- package/dist/commands/dom/a11y.d.ts.map +1 -1
- package/dist/commands/dom/a11y.js +5 -1
- package/dist/commands/dom/a11y.js.map +1 -1
- package/dist/commands/dom/evalHelpers.js +1 -1
- package/dist/commands/dom/evalHelpers.js.map +1 -1
- package/dist/commands/dom/form.d.ts +14 -0
- package/dist/commands/dom/form.d.ts.map +1 -0
- package/dist/commands/dom/form.js +441 -0
- package/dist/commands/dom/form.js.map +1 -0
- package/dist/commands/dom/formDiscovery.d.ts +26 -0
- package/dist/commands/dom/formDiscovery.d.ts.map +1 -0
- package/dist/commands/dom/formDiscovery.js +476 -0
- package/dist/commands/dom/formDiscovery.js.map +1 -0
- package/dist/commands/dom/formFillHelpers.d.ts +72 -0
- package/dist/commands/dom/formFillHelpers.d.ts.map +1 -1
- package/dist/commands/dom/formFillHelpers.js +241 -5
- package/dist/commands/dom/formFillHelpers.js.map +1 -1
- package/dist/commands/dom/formInteraction.d.ts.map +1 -1
- package/dist/commands/dom/formInteraction.js +119 -3
- package/dist/commands/dom/formInteraction.js.map +1 -1
- package/dist/commands/dom/formTypes.d.ts +75 -0
- package/dist/commands/dom/formTypes.d.ts.map +1 -0
- package/dist/commands/dom/formTypes.js +7 -0
- package/dist/commands/dom/formTypes.js.map +1 -0
- package/dist/commands/dom/helpers.d.ts +1 -1
- package/dist/commands/dom/helpers.js +1 -1
- package/dist/commands/dom/index.d.ts.map +1 -1
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/index.js.map +1 -1
- package/dist/commands/dom/screenshotResize.d.ts +1 -1
- package/dist/commands/dom/screenshotResize.js +1 -1
- package/dist/commands/optionBehaviors.d.ts.map +1 -1
- package/dist/commands/optionBehaviors.js +37 -0
- package/dist/commands/optionBehaviors.js.map +1 -1
- package/dist/commands/shared/optionTypes.d.ts +22 -0
- package/dist/commands/shared/optionTypes.d.ts.map +1 -1
- package/dist/commands/shared/startHelpers.d.ts.map +1 -1
- package/dist/commands/shared/startHelpers.js +1 -0
- package/dist/commands/shared/startHelpers.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +25 -6
- package/dist/commands/start.js.map +1 -1
- package/dist/connection/__tests__/flagsBuilder.unit.test.d.ts +9 -0
- package/dist/connection/__tests__/flagsBuilder.unit.test.d.ts.map +1 -0
- package/dist/connection/__tests__/flagsBuilder.unit.test.js +94 -0
- package/dist/connection/__tests__/flagsBuilder.unit.test.js.map +1 -0
- package/dist/connection/cdp.js.map +1 -1
- package/dist/connection/launcher/flagsBuilder.d.ts +26 -0
- package/dist/connection/launcher/flagsBuilder.d.ts.map +1 -1
- package/dist/connection/launcher/flagsBuilder.js +36 -8
- package/dist/connection/launcher/flagsBuilder.js.map +1 -1
- package/dist/connection/launcher.d.ts.map +1 -1
- package/dist/connection/launcher.js +20 -9
- package/dist/connection/launcher.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/daemon/handlers/SessionHandlers.d.ts.map +1 -1
- package/dist/daemon/handlers/SessionHandlers.js +1 -0
- package/dist/daemon/handlers/SessionHandlers.js.map +1 -1
- package/dist/daemon/launcher.d.ts.map +1 -1
- package/dist/daemon/launcher.js +8 -2
- package/dist/daemon/launcher.js.map +1 -1
- package/dist/daemon/lifecycle/chromeConnection.js +1 -1
- package/dist/daemon/lifecycle/chromeConnection.js.map +1 -1
- package/dist/daemon/lifecycle/workerCleanup.d.ts.map +1 -1
- package/dist/daemon/lifecycle/workerCleanup.js +3 -1
- package/dist/daemon/lifecycle/workerCleanup.js.map +1 -1
- package/dist/daemon/lifecycle/workerConfig.d.ts.map +1 -1
- package/dist/daemon/lifecycle/workerConfig.js +3 -0
- package/dist/daemon/lifecycle/workerConfig.js.map +1 -1
- package/dist/daemon/startSession.d.ts +2 -0
- package/dist/daemon/startSession.d.ts.map +1 -1
- package/dist/daemon/startSession.js +7 -1
- package/dist/daemon/startSession.js.map +1 -1
- package/dist/daemon/worker/types.d.ts +2 -0
- package/dist/daemon/worker/types.d.ts.map +1 -1
- package/dist/ipc/client.d.ts.map +1 -1
- package/dist/ipc/client.js +1 -0
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/protocol/messages.d.ts +2 -2
- package/dist/ipc/protocol/messages.d.ts.map +1 -1
- package/dist/ipc/session/lifecycle.d.ts +4 -0
- package/dist/ipc/session/lifecycle.d.ts.map +1 -1
- package/dist/session/cleanup.d.ts.map +1 -1
- package/dist/session/cleanup.js +50 -4
- package/dist/session/cleanup.js.map +1 -1
- package/dist/session/paths.d.ts +1 -0
- package/dist/session/paths.d.ts.map +1 -1
- package/dist/session/paths.js +1 -0
- package/dist/session/paths.js.map +1 -1
- package/dist/session/port.d.ts +41 -0
- package/dist/session/port.d.ts.map +1 -0
- package/dist/session/port.js +134 -0
- package/dist/session/port.js.map +1 -0
- package/dist/types.d.ts +120 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/formatters/form.d.ts +15 -0
- package/dist/ui/formatters/form.d.ts.map +1 -0
- package/dist/ui/formatters/form.js +314 -0
- package/dist/ui/formatters/form.js.map +1 -0
- package/dist/ui/messages/errors.d.ts +41 -12
- package/dist/ui/messages/errors.d.ts.map +1 -1
- package/dist/ui/messages/errors.js +113 -15
- package/dist/ui/messages/errors.js.map +1 -1
- package/dist/utils/__tests__/shellDetection.unit.test.d.ts +12 -0
- package/dist/utils/__tests__/shellDetection.unit.test.d.ts.map +1 -0
- package/dist/utils/__tests__/shellDetection.unit.test.js +220 -0
- package/dist/utils/__tests__/shellDetection.unit.test.js.map +1 -0
- package/dist/utils/decisionTrees.d.ts.map +1 -1
- package/dist/utils/decisionTrees.js +5 -0
- package/dist/utils/decisionTrees.js.map +1 -1
- package/dist/utils/exitCodes.d.ts +2 -0
- package/dist/utils/exitCodes.d.ts.map +1 -1
- package/dist/utils/exitCodes.js +12 -0
- package/dist/utils/exitCodes.js.map +1 -1
- package/dist/utils/shellDetection.d.ts +34 -0
- package/dist/utils/shellDetection.d.ts.map +1 -0
- package/dist/utils/shellDetection.js +94 -0
- package/dist/utils/shellDetection.js.map +1 -0
- package/dist/utils/taskMappings.d.ts.map +1 -1
- package/dist/utils/taskMappings.js +5 -0
- package/dist/utils/taskMappings.js.map +1 -1
- package/dist/utils/url.d.ts.map +1 -1
- package/dist/utils/url.js +14 -0
- package/dist/utils/url.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,441 @@
|
|
|
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
|
+
if (!metadata.port) {
|
|
368
|
+
throw new CommandError('Session metadata missing port', { suggestion: 'Restart the session with: bdg stop && bdg <url>' }, EXIT_CODES.SESSION_FILE_ERROR);
|
|
369
|
+
}
|
|
370
|
+
await verifyTargetExists(metadata, metadata.port);
|
|
371
|
+
const cdp = new CDPConnection();
|
|
372
|
+
if (!metadata.webSocketDebuggerUrl) {
|
|
373
|
+
throw new CommandError('Session metadata missing webSocketDebuggerUrl', { suggestion: 'Start a new session with: bdg <url>' }, EXIT_CODES.SESSION_FILE_ERROR);
|
|
374
|
+
}
|
|
375
|
+
await cdp.connect(metadata.webSocketDebuggerUrl);
|
|
376
|
+
try {
|
|
377
|
+
return await fn(cdp);
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
cdp.close();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Handle form discovery command.
|
|
385
|
+
*
|
|
386
|
+
* @param options - Command options
|
|
387
|
+
*/
|
|
388
|
+
async function handleFormCommand(options) {
|
|
389
|
+
await runCommand(async () => {
|
|
390
|
+
return await withCDPConnection(async (cdp) => {
|
|
391
|
+
const rawData = await executeFormDiscovery(cdp);
|
|
392
|
+
if (rawData.forms.length === 0) {
|
|
393
|
+
const err = noFormsFoundError();
|
|
394
|
+
return {
|
|
395
|
+
success: false,
|
|
396
|
+
error: err.message,
|
|
397
|
+
exitCode: EXIT_CODES.NO_FORMS_FOUND,
|
|
398
|
+
errorContext: { suggestion: err.suggestion },
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
const iframeForm = rawData.forms.find((f) => f.inIframe);
|
|
402
|
+
if (iframeForm && rawData.forms.length === 1) {
|
|
403
|
+
const err = formInIframeError(iframeForm.iframeUrl ?? 'unknown', iframeForm.crossOrigin ?? false);
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
error: err.message,
|
|
407
|
+
exitCode: EXIT_CODES.FORM_IN_IFRAME,
|
|
408
|
+
errorContext: { suggestion: err.suggestion },
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const allForms = rawData.forms.map(transformForm);
|
|
412
|
+
const forms = options.all ? allForms : [allForms[0]];
|
|
413
|
+
// Cache ALL forms so global indices work with bdg dom fill/click
|
|
414
|
+
await cacheFormElements(allForms);
|
|
415
|
+
const result = {
|
|
416
|
+
formCount: rawData.forms.length,
|
|
417
|
+
selectedForm: 0,
|
|
418
|
+
forms,
|
|
419
|
+
brief: options.brief,
|
|
420
|
+
};
|
|
421
|
+
return { success: true, data: result };
|
|
422
|
+
});
|
|
423
|
+
}, options, formatFormDiscovery);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Register form discovery command.
|
|
427
|
+
*
|
|
428
|
+
* @param domCommand - DOM command group
|
|
429
|
+
*/
|
|
430
|
+
export function registerFormCommand(domCommand) {
|
|
431
|
+
domCommand
|
|
432
|
+
.command('form')
|
|
433
|
+
.description('Discover forms with semantic labels, values, and validation state')
|
|
434
|
+
.option('--all', 'Show all forms expanded')
|
|
435
|
+
.option('--brief', 'Quick scan: field names, types, and required status only')
|
|
436
|
+
.addOption(jsonOption())
|
|
437
|
+
.action(async (options) => {
|
|
438
|
+
await handleFormCommand(options);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
//# 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"}
|