browser-opt 1.0.24 → 1.0.26
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/dist/browser-core/agent.d.ts +4 -0
- package/dist/browser-core/agent.d.ts.map +1 -1
- package/dist/browser-core/agent.js +13 -0
- package/dist/browser-core/agent.js.map +1 -1
- package/dist/browser-opt/index.d.ts +1 -1
- package/dist/browser-opt/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/index.d.ts +1 -1
- package/dist/browser-opt/runner/actions/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/index.js +135 -3
- package/dist/browser-opt/runner/actions/index.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/click-action.js +10 -4
- package/dist/browser-opt/runner/actions/utils/click-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/date-action.js +5 -1
- package/dist/browser-opt/runner/actions/utils/date-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/dom-action.d.ts +14 -3
- package/dist/browser-opt/runner/actions/utils/dom-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/dom-action.js +301 -20
- package/dist/browser-opt/runner/actions/utils/dom-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/fill-action.js +27 -7
- package/dist/browser-opt/runner/actions/utils/fill-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.d.ts +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/select-option-action.js +187 -65
- package/dist/browser-opt/runner/actions/utils/select-option-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.d.ts +2 -2
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.js +79 -19
- package/dist/browser-opt/runner/actions/utils/table-row-checkbox-action.js.map +1 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.d.ts +8 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.d.ts.map +1 -1
- package/dist/browser-opt/runner/actions/utils/upload-action.js +517 -64
- package/dist/browser-opt/runner/actions/utils/upload-action.js.map +1 -1
- package/dist/browser-opt/runner/index.d.ts.map +1 -1
- package/dist/browser-opt/runner/index.js +7 -0
- package/dist/browser-opt/runner/index.js.map +1 -1
- package/dist/browser-opt/runner/step-executor.d.ts.map +1 -1
- package/dist/browser-opt/runner/step-executor.js +39 -8
- package/dist/browser-opt/runner/step-executor.js.map +1 -1
- package/dist/browser-opt/type.d.ts +28 -0
- package/dist/browser-opt/type.d.ts.map +1 -1
- package/dist/browser-opt/utils.d.ts.map +1 -1
- package/dist/browser-opt/utils.js +164 -12
- package/dist/browser-opt/utils.js.map +1 -1
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +11 -0
- package/dist/cli/utils/output.js.map +1 -1
- package/package.json +2 -2
- package/skills/browser-opt/SKILL.md +10 -2
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
export function clickFieldScopedDomTarget(agent, field, target) {
|
|
1
|
+
export function clickFieldScopedDomTarget(agent, field, target, rowNumber) {
|
|
2
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
2
3
|
const script = `(() => {
|
|
3
4
|
const helper = ${fieldScopedDomHelperSource()};
|
|
4
|
-
return JSON.stringify(helper.clickFieldScopedTarget(${JSON.stringify(field)}, ${JSON.stringify(target)}));
|
|
5
|
+
return JSON.stringify(helper.clickFieldScopedTarget(${JSON.stringify(field)}, ${JSON.stringify(target)}${rowArgument}));
|
|
5
6
|
})()`;
|
|
6
7
|
try {
|
|
7
8
|
const parsed = parseEvalJson(agent.evaluate(script));
|
|
8
9
|
if (parsed.found && parsed.clicked) {
|
|
9
10
|
agent.waitMs(300);
|
|
10
11
|
const clickedText = parsed.targetText ? ` (${parsed.targetText.trim()})` : '';
|
|
11
|
-
|
|
12
|
+
const rowLabel = rowNumber ? `第 ${rowNumber} 行 ` : '';
|
|
13
|
+
return `click dom ${rowLabel}${field} -> ${target}${clickedText}`;
|
|
12
14
|
}
|
|
13
15
|
return null;
|
|
14
16
|
}
|
|
@@ -16,16 +18,36 @@ export function clickFieldScopedDomTarget(agent, field, target) {
|
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
|
-
|
|
21
|
+
/** 未提供列名时在指定数据行内查找唯一目标,适用于“点击第 N 行删除按钮”这类表达。 */
|
|
22
|
+
export function clickTableRowDomTarget(agent, target, rowNumber) {
|
|
20
23
|
const script = `(() => {
|
|
21
24
|
const helper = ${fieldScopedDomHelperSource()};
|
|
22
|
-
return JSON.stringify(helper.
|
|
25
|
+
return JSON.stringify(helper.clickTableRowTarget(${JSON.stringify(target)}, ${rowNumber}));
|
|
26
|
+
})()`;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = parseEvalJson(agent.evaluate(script));
|
|
29
|
+
if (parsed.found && parsed.clicked) {
|
|
30
|
+
agent.waitMs(300);
|
|
31
|
+
return `click dom 第 ${rowNumber} 行 -> ${target}`;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function fillFieldScopedDomTarget(agent, field, value, preserveFocus = false, rowNumber) {
|
|
40
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
41
|
+
const script = `(() => {
|
|
42
|
+
const helper = ${fieldScopedDomHelperSource()};
|
|
43
|
+
return JSON.stringify(helper.fillFieldScopedTarget(${JSON.stringify(field)}, ${JSON.stringify(value)}, ${preserveFocus}${rowArgument}));
|
|
23
44
|
})()`;
|
|
24
45
|
try {
|
|
25
46
|
const parsed = parseEvalJson(agent.evaluate(script));
|
|
26
47
|
if (parsed.found && parsed.filled) {
|
|
27
48
|
agent.waitMs(300);
|
|
28
|
-
|
|
49
|
+
const rowLabel = rowNumber ? `第 ${rowNumber} 行 ` : '';
|
|
50
|
+
return `fill dom ${rowLabel}${field} ${JSON.stringify(value)}`;
|
|
29
51
|
}
|
|
30
52
|
return null;
|
|
31
53
|
}
|
|
@@ -34,10 +56,11 @@ export function fillFieldScopedDomTarget(agent, field, value) {
|
|
|
34
56
|
}
|
|
35
57
|
}
|
|
36
58
|
/** 读取字段对应输入控件的真实 DOM 值,供 snapshot 未暴露值时做后置校验。 */
|
|
37
|
-
export function readFieldScopedDomValue(agent, field) {
|
|
59
|
+
export function readFieldScopedDomValue(agent, field, rowNumber) {
|
|
60
|
+
const rowArgument = rowNumber ? `, ${rowNumber}` : '';
|
|
38
61
|
const script = `(() => {
|
|
39
62
|
const helper = ${fieldScopedDomHelperSource()};
|
|
40
|
-
return JSON.stringify(helper.readFieldScopedValue(${JSON.stringify(field)}));
|
|
63
|
+
return JSON.stringify(helper.readFieldScopedValue(${JSON.stringify(field)}${rowArgument}));
|
|
41
64
|
})()`;
|
|
42
65
|
try {
|
|
43
66
|
const parsed = parseEvalJson(agent.evaluate(script));
|
|
@@ -47,6 +70,43 @@ export function readFieldScopedDomValue(agent, field) {
|
|
|
47
70
|
return null;
|
|
48
71
|
}
|
|
49
72
|
}
|
|
73
|
+
/** 在指定表格行列内处理开关、单复选框或打开下拉框,避免同列多行控件串位。 */
|
|
74
|
+
export function selectFieldScopedDomTarget(agent, field, option, rowNumber, mode = 'select') {
|
|
75
|
+
const script = `(() => {
|
|
76
|
+
const helper = ${fieldScopedDomHelperSource()};
|
|
77
|
+
return JSON.stringify(helper.selectFieldScopedTarget(${JSON.stringify(field)}, ${JSON.stringify(option)}, ${rowNumber}, ${JSON.stringify(mode)}, false));
|
|
78
|
+
})()`;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = parseEvalJson(agent.evaluate(script));
|
|
81
|
+
if (parsed.opened) {
|
|
82
|
+
agent.waitMs(300);
|
|
83
|
+
return { dropdownOpened: true, output: null };
|
|
84
|
+
}
|
|
85
|
+
if (parsed.reached) {
|
|
86
|
+
agent.waitMs(parsed.changed ? 300 : 0);
|
|
87
|
+
const verb = parsed.changed ? 'select dom' : 'selection skipped';
|
|
88
|
+
return { dropdownOpened: false, output: `${verb} 第 ${rowNumber} 行 ${field}=${option}` };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** 从指定表格单元格读取选择类控件状态,确保后置校验不引用其他数据行。 */
|
|
97
|
+
export function verifyFieldScopedDomSelection(agent, field, option, rowNumber, mode = 'select') {
|
|
98
|
+
const script = `(() => {
|
|
99
|
+
const helper = ${fieldScopedDomHelperSource()};
|
|
100
|
+
return JSON.stringify(helper.selectFieldScopedTarget(${JSON.stringify(field)}, ${JSON.stringify(option)}, ${rowNumber}, ${JSON.stringify(mode)}, true));
|
|
101
|
+
})()`;
|
|
102
|
+
try {
|
|
103
|
+
const parsed = parseEvalJson(agent.evaluate(script));
|
|
104
|
+
return typeof parsed.reached === 'boolean' ? parsed.reached : null;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
50
110
|
function parseEvalJson(raw) {
|
|
51
111
|
const decoded = JSON.parse(raw.trim());
|
|
52
112
|
return typeof decoded === 'string'
|
|
@@ -92,6 +152,32 @@ const inputLike = (element) => {
|
|
|
92
152
|
if (element instanceof HTMLInputElement) return !['hidden', 'file', 'checkbox', 'radio', 'button', 'submit', 'reset'].includes(element.type);
|
|
93
153
|
return element.isContentEditable === true;
|
|
94
154
|
};
|
|
155
|
+
const switchLike = (element) => element.matches('[role="switch"], .ant-switch, [class*="switch"]') && !disabled(element);
|
|
156
|
+
const selectLike = (element) => element.matches('select:not(:disabled), .ant-select:not(.ant-select-disabled), .el-select:not(.is-disabled), [role="combobox"]');
|
|
157
|
+
const choiceInput = (element, optionText) => {
|
|
158
|
+
const input = element.matches('input[type="radio"], input[type="checkbox"]')
|
|
159
|
+
? element
|
|
160
|
+
: element.querySelector?.('input[type="radio"], input[type="checkbox"]');
|
|
161
|
+
if (!input || disabled(input)) return null;
|
|
162
|
+
const label = element.closest('label') || input.closest('label') || (input.id ? document.querySelector('label[for="' + CSS.escape(input.id) + '"]') : null);
|
|
163
|
+
const text = normalize(textOf(label || element));
|
|
164
|
+
return text && (text.includes(optionText) || optionText.includes(text)) ? { input, label } : null;
|
|
165
|
+
};
|
|
166
|
+
const switchState = (element) => {
|
|
167
|
+
const aria = element.getAttribute('aria-checked');
|
|
168
|
+
if (aria === 'true' || aria === '1') return true;
|
|
169
|
+
if (aria === 'false' || aria === '0') return false;
|
|
170
|
+
if (typeof element.checked === 'boolean') return element.checked;
|
|
171
|
+
const className = String(element.className || '');
|
|
172
|
+
if (element.classList.contains('ant-switch-checked') || element.classList.contains('is-checked') || className.includes('switch-checked')) return true;
|
|
173
|
+
return false;
|
|
174
|
+
};
|
|
175
|
+
const desiredSwitchState = (option) => {
|
|
176
|
+
const text = normalize(option);
|
|
177
|
+
if (/^(是|开|开启|打开|启用|展示|true|yes|on)$/.test(text)) return true;
|
|
178
|
+
if (/^(否|关|关闭|停用|禁用|不展示|false|no|off)$/.test(text)) return false;
|
|
179
|
+
return null;
|
|
180
|
+
};
|
|
95
181
|
const dispatchMouse = (element) => {
|
|
96
182
|
element.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
97
183
|
const rect = element.getBoundingClientRect();
|
|
@@ -107,8 +193,15 @@ const dispatchMouse = (element) => {
|
|
|
107
193
|
element.dispatchEvent(new MouseEvent('mouseup', init));
|
|
108
194
|
element.dispatchEvent(new MouseEvent('click', init));
|
|
109
195
|
};
|
|
110
|
-
const dispatchValue = (element, value) => {
|
|
196
|
+
const dispatchValue = (element, value, keepFocus = false) => {
|
|
111
197
|
element.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
198
|
+
// Ant 虚拟表格的表头与表体使用独立滚动容器,scrollIntoView 后显式同步表头避免视觉与列映射错位。
|
|
199
|
+
const virtualTable = element.closest('.ant-table-virtual');
|
|
200
|
+
const virtualBody = element.closest('.ant-table-tbody-virtual-holder');
|
|
201
|
+
const virtualHeader = virtualTable?.querySelector('.ant-table-header');
|
|
202
|
+
if (virtualBody && virtualHeader) {
|
|
203
|
+
virtualHeader.scrollLeft = virtualBody.scrollLeft;
|
|
204
|
+
}
|
|
112
205
|
element.focus?.();
|
|
113
206
|
const preserveFocus = Boolean(element.closest('[role="combobox"], .ant-select, .el-select'));
|
|
114
207
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
@@ -120,7 +213,7 @@ const dispatchValue = (element, value) => {
|
|
|
120
213
|
}
|
|
121
214
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
122
215
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
123
|
-
if (!preserveFocus) {
|
|
216
|
+
if (!preserveFocus && !keepFocus) {
|
|
124
217
|
element.blur?.();
|
|
125
218
|
}
|
|
126
219
|
};
|
|
@@ -134,6 +227,107 @@ const fieldElements = (field) => {
|
|
|
134
227
|
};
|
|
135
228
|
const closestFieldContainer = (element) => element.closest('.ant-form-item, .el-form-item, .form-item, [class*="form-item"], [class*="FormItem"], [class*="field"], [class*="Field"]');
|
|
136
229
|
const actionableDescendants = (root, predicate) => [...root.querySelectorAll('*')].filter((element) => visible(element) && predicate(element));
|
|
230
|
+
const tableComponentRoot = (header) => header.closest('.ant-table, .el-table, [role="table"], [role="grid"]')
|
|
231
|
+
|| header.closest('table')?.parentElement;
|
|
232
|
+
const tableRows = (root) => root
|
|
233
|
+
? [...root.querySelectorAll('tbody tr, [role="row"], .ant-table-tbody-virtual-holder-inner .ant-table-row')]
|
|
234
|
+
.filter((row) => !row.closest('thead') && !row.querySelector(':scope > [role="columnheader"]'))
|
|
235
|
+
: [];
|
|
236
|
+
const tableRowCells = (row) => [...row.querySelectorAll(':scope > td, :scope > th, :scope > [role="cell"], :scope > [role="gridcell"], :scope > .ant-table-cell')];
|
|
237
|
+
const tableRowGroups = (rows) => {
|
|
238
|
+
const groups = [];
|
|
239
|
+
for (const row of [...rows].sort((left, right) => left.getBoundingClientRect().top - right.getBoundingClientRect().top)) {
|
|
240
|
+
const top = row.getBoundingClientRect().top;
|
|
241
|
+
const group = groups.find((item) => Math.abs(item.top - top) <= 2);
|
|
242
|
+
if (group) {
|
|
243
|
+
group.rows.push(row);
|
|
244
|
+
} else {
|
|
245
|
+
groups.push({ top, rows: [row] });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return groups.map((group) => group.rows);
|
|
249
|
+
};
|
|
250
|
+
const horizontalOverlap = (left, right) => Math.max(0, Math.min(left.right, right.right) - Math.max(left.left, right.left));
|
|
251
|
+
const tableCellIdentities = (cell) => {
|
|
252
|
+
const identities = [];
|
|
253
|
+
for (const name of ['data-column-key', 'data-field', 'data-prop', 'data-index', 'aria-colindex']) {
|
|
254
|
+
const value = cell.getAttribute(name);
|
|
255
|
+
if (value) identities.push(name + '=' + value);
|
|
256
|
+
}
|
|
257
|
+
for (const token of cell.classList || []) {
|
|
258
|
+
if (/column|(^|[-_])col([-_]|\\d)|cell[-_]\\d/i.test(token) && !/^(ant-table-cell|el-table__cell)$/.test(token)) {
|
|
259
|
+
identities.push('class=' + token);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const table = cell.closest('table');
|
|
263
|
+
const index = cell.cellIndex;
|
|
264
|
+
const col = table && index >= 0 ? table.querySelectorAll('colgroup col')[index] : null;
|
|
265
|
+
if (col) {
|
|
266
|
+
for (const name of ['name', 'class', 'data-column-key', 'data-index']) {
|
|
267
|
+
const value = col.getAttribute(name);
|
|
268
|
+
if (value) identities.push('col-' + name + '=' + value);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return [...new Set(identities)];
|
|
272
|
+
};
|
|
273
|
+
const targetInside = (cell, predicate) => {
|
|
274
|
+
if (!cell) return null;
|
|
275
|
+
if (visible(cell) && predicate(cell)) return cell;
|
|
276
|
+
return actionableDescendants(cell, predicate)[0] || null;
|
|
277
|
+
};
|
|
278
|
+
const tableColumnTarget = (fieldElement, predicate, rowNumber) => {
|
|
279
|
+
const header = fieldElement.closest('th, td')
|
|
280
|
+
|| (fieldElement.getAttribute('role') === 'columnheader' ? fieldElement : null);
|
|
281
|
+
if (!header) return null;
|
|
282
|
+
const component = tableComponentRoot(header);
|
|
283
|
+
const headerRect = header.getBoundingClientRect();
|
|
284
|
+
const rows = tableRows(component);
|
|
285
|
+
const targetRows = rowNumber ? tableRowGroups(rows)[rowNumber - 1] || [] : rows;
|
|
286
|
+
const headerCells = [...(header.parentElement?.children || [])];
|
|
287
|
+
const columnIndex = header.cellIndex >= 0 ? header.cellIndex : headerCells.indexOf(header);
|
|
288
|
+
|
|
289
|
+
// Ant 虚拟表格保留完整列顺序但表头、表体滚动位置可能短暂不同,必须优先按稳定列序号定位。
|
|
290
|
+
if (component?.matches('.ant-table-virtual') && columnIndex >= 0) {
|
|
291
|
+
for (const row of targetRows) {
|
|
292
|
+
const target = targetInside(tableRowCells(row)[columnIndex], predicate);
|
|
293
|
+
if (target) return target;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const headerIdentities = tableCellIdentities(header);
|
|
297
|
+
const identityTargets = headerIdentities.length === 0 ? [] : targetRows
|
|
298
|
+
.flatMap((row) => tableRowCells(row)
|
|
299
|
+
.filter((cell) => tableCellIdentities(cell).some((identity) => headerIdentities.includes(identity)))
|
|
300
|
+
.map((cell) => ({ target: targetInside(cell, predicate), rowTop: row.getBoundingClientRect().top })))
|
|
301
|
+
.filter((item) => item.target)
|
|
302
|
+
.sort((a, b) => a.rowTop - b.rowTop);
|
|
303
|
+
if (identityTargets.length > 0) return identityTargets[0].target;
|
|
304
|
+
|
|
305
|
+
// 滚动表格常把表头和表体拆成不同 table,并额外渲染固定列副本;按屏幕横坐标映射才能避免列序号漂移。
|
|
306
|
+
const sameTextHeaders = component ? [...component.querySelectorAll('thead th, thead td, [role="columnheader"]')]
|
|
307
|
+
.filter((item) => normalize(textOf(item)) === normalize(textOf(header))) : [];
|
|
308
|
+
const geometricTargets = sameTextHeaders.length > 1 ? [] : targetRows
|
|
309
|
+
.flatMap((row) => tableRowCells(row)
|
|
310
|
+
.map((cell) => ({ cell, rowTop: row.getBoundingClientRect().top, overlap: horizontalOverlap(headerRect, cell.getBoundingClientRect()) })))
|
|
311
|
+
.filter((item) => item.overlap > 0)
|
|
312
|
+
.map((item) => ({ ...item, target: targetInside(item.cell, predicate) }))
|
|
313
|
+
.filter((item) => item.target)
|
|
314
|
+
.sort((a, b) => b.overlap - a.overlap || a.rowTop - b.rowTop);
|
|
315
|
+
if (geometricTargets.length > 0) return geometricTargets[0].target;
|
|
316
|
+
|
|
317
|
+
if (columnIndex < 0) return null;
|
|
318
|
+
const ownTable = header.closest('table');
|
|
319
|
+
const ownRows = ownTable?.querySelector('tbody tr') ? [...ownTable.querySelectorAll('tbody tr')] : [];
|
|
320
|
+
const indexedRows = rowNumber
|
|
321
|
+
? (ownRows.length > 0 ? [ownRows[rowNumber - 1]].filter(Boolean) : targetRows)
|
|
322
|
+
: (ownRows.length > 0 ? ownRows : rows);
|
|
323
|
+
for (const row of indexedRows) {
|
|
324
|
+
const cell = tableRowCells(row)[columnIndex];
|
|
325
|
+
if (!cell) continue;
|
|
326
|
+
const target = targetInside(cell, predicate);
|
|
327
|
+
if (target) return target;
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
};
|
|
137
331
|
const collectSiblingScope = (element) => {
|
|
138
332
|
const scope = [];
|
|
139
333
|
let current = element;
|
|
@@ -169,9 +363,19 @@ const structurallyScopedTargets = (fieldElement, predicate) => {
|
|
|
169
363
|
}
|
|
170
364
|
return null;
|
|
171
365
|
};
|
|
172
|
-
const scopedTarget = (field, predicate) => {
|
|
366
|
+
const scopedTarget = (field, predicate, rowNumber = null) => {
|
|
173
367
|
const fields = fieldElements(field);
|
|
174
|
-
|
|
368
|
+
const tableFields = fields.filter((fieldItem) => fieldItem.element.closest('th, [role="columnheader"]'));
|
|
369
|
+
for (const fieldItem of tableFields.length > 0 ? tableFields : fields) {
|
|
370
|
+
const tableTarget = tableColumnTarget(fieldItem.element, predicate, rowNumber);
|
|
371
|
+
if (tableTarget) {
|
|
372
|
+
return { element: tableTarget, fields };
|
|
373
|
+
}
|
|
374
|
+
// 列头定位失败时不能退化为普通近邻搜索,否则会把值写入相邻列并造成同源校验假阳性。
|
|
375
|
+
if (fieldItem.element.closest('th, [role="columnheader"]')) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
175
379
|
const container = closestFieldContainer(fieldItem.element);
|
|
176
380
|
const scoped = container ? [...container.querySelectorAll('*')].filter((element) => visible(element) && predicate(element)) : [];
|
|
177
381
|
if (scoped.length > 0) {
|
|
@@ -185,25 +389,41 @@ const scopedTarget = (field, predicate) => {
|
|
|
185
389
|
}
|
|
186
390
|
return { element: null, fields };
|
|
187
391
|
};
|
|
188
|
-
function clickFieldScopedTarget(field, target) {
|
|
392
|
+
function clickFieldScopedTarget(field, target, rowNumber = null) {
|
|
189
393
|
const targetText = normalize(target);
|
|
190
|
-
const result = scopedTarget(field, (element) => clickable(element) && textMatches(element, targetText));
|
|
394
|
+
const result = scopedTarget(field, (element) => clickable(element) && textMatches(element, targetText), rowNumber);
|
|
191
395
|
if (result.element) {
|
|
192
396
|
dispatchMouse(result.element);
|
|
193
397
|
return { found: true, clicked: true, targetText: textOf(result.element) };
|
|
194
398
|
}
|
|
195
399
|
return { found: result.fields.length > 0, clicked: false };
|
|
196
400
|
}
|
|
197
|
-
function
|
|
198
|
-
const
|
|
401
|
+
function clickTableRowTarget(target, rowNumber) {
|
|
402
|
+
const targetText = normalize(target);
|
|
403
|
+
const rootSelector = '.ant-table, .el-table, [role="table"], [role="grid"], table';
|
|
404
|
+
const roots = [...document.querySelectorAll(rootSelector)]
|
|
405
|
+
.filter((root) => !root.parentElement?.closest(rootSelector));
|
|
406
|
+
const targets = roots.flatMap((root) => {
|
|
407
|
+
const rows = tableRowGroups(tableRows(root))[rowNumber - 1] || [];
|
|
408
|
+
return rows.flatMap((row) => [row, ...row.querySelectorAll('*')])
|
|
409
|
+
.filter((element) => visible(element) && clickable(element) && textMatches(element, targetText));
|
|
410
|
+
});
|
|
411
|
+
const targetElement = targets
|
|
412
|
+
.sort((left, right) => normalize(textOf(left)).length - normalize(textOf(right)).length)[0];
|
|
413
|
+
if (!targetElement) return { found: false, clicked: false };
|
|
414
|
+
dispatchMouse(targetElement);
|
|
415
|
+
return { found: true, clicked: true, targetText: textOf(targetElement) };
|
|
416
|
+
}
|
|
417
|
+
function fillFieldScopedTarget(field, value, preserveFocus, rowNumber = null) {
|
|
418
|
+
const result = scopedTarget(field, inputLike, rowNumber);
|
|
199
419
|
if (result.element) {
|
|
200
|
-
dispatchValue(result.element, value);
|
|
420
|
+
dispatchValue(result.element, value, preserveFocus);
|
|
201
421
|
return { found: true, filled: true, targetText: textOf(result.element) };
|
|
202
422
|
}
|
|
203
423
|
return { found: result.fields.length > 0, filled: false };
|
|
204
424
|
}
|
|
205
|
-
function readFieldScopedValue(field) {
|
|
206
|
-
const result = scopedTarget(field, inputLike);
|
|
425
|
+
function readFieldScopedValue(field, rowNumber = null) {
|
|
426
|
+
const result = scopedTarget(field, inputLike, rowNumber);
|
|
207
427
|
if (!result.element) {
|
|
208
428
|
return { found: false };
|
|
209
429
|
}
|
|
@@ -212,7 +432,68 @@ function readFieldScopedValue(field) {
|
|
|
212
432
|
: result.element.textContent || '';
|
|
213
433
|
return { found: true, value };
|
|
214
434
|
}
|
|
215
|
-
|
|
435
|
+
function selectFieldScopedTarget(field, option, rowNumber, mode = 'select', verifyOnly = false) {
|
|
436
|
+
const optionText = normalize(option);
|
|
437
|
+
const result = scopedTarget(field, (element) => switchLike(element) || selectLike(element) || Boolean(choiceInput(element, optionText)), rowNumber);
|
|
438
|
+
if (!result.element) return { found: false };
|
|
439
|
+
|
|
440
|
+
const switchElement = result.element.closest('[role="switch"], .ant-switch, [class*="switch"]');
|
|
441
|
+
if (switchElement && switchLike(switchElement)) {
|
|
442
|
+
const desired = desiredSwitchState(option);
|
|
443
|
+
if (desired === null) return { found: true, kind: 'switch' };
|
|
444
|
+
const before = switchState(switchElement);
|
|
445
|
+
if (!verifyOnly && before !== desired) dispatchMouse(switchElement);
|
|
446
|
+
const reached = verifyOnly ? before === desired : switchState(switchElement) === desired;
|
|
447
|
+
return { found: true, kind: 'switch', reached, changed: !verifyOnly && before !== desired };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const choice = choiceInput(result.element, optionText);
|
|
451
|
+
if (choice) {
|
|
452
|
+
const desired = mode !== 'deselect';
|
|
453
|
+
const cell = choice.input.closest('td, th, [role="cell"], [role="gridcell"], .ant-table-cell');
|
|
454
|
+
const group = cell ? [...cell.querySelectorAll('input[type="checkbox"]')].filter((input) => !disabled(input)) : [choice.input];
|
|
455
|
+
let changed = false;
|
|
456
|
+
if (!verifyOnly && mode === 'exclusive') {
|
|
457
|
+
for (const sibling of group) {
|
|
458
|
+
if (sibling !== choice.input && sibling.checked) {
|
|
459
|
+
(sibling.closest('label') || sibling).click();
|
|
460
|
+
changed = true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (!verifyOnly && choice.input.checked !== desired) {
|
|
465
|
+
(choice.label || choice.input).click();
|
|
466
|
+
changed = true;
|
|
467
|
+
}
|
|
468
|
+
const reached = choice.input.checked === desired
|
|
469
|
+
&& (mode !== 'exclusive' || group.filter((input) => input !== choice.input && input.checked).length === 0);
|
|
470
|
+
return { found: true, kind: choice.input.type, reached, changed };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const selectElement = result.element.closest('select, .ant-select, .el-select, [role="combobox"]');
|
|
474
|
+
if (!selectElement) return { found: true };
|
|
475
|
+
const values = [
|
|
476
|
+
selectElement instanceof HTMLSelectElement ? selectElement.selectedOptions?.[0]?.textContent || selectElement.value : '',
|
|
477
|
+
selectElement.querySelector('input:not([type="hidden"])')?.value,
|
|
478
|
+
selectElement.querySelector('.ant-select-selection-item, .el-input__inner, [class*="singleValue"]')?.textContent,
|
|
479
|
+
].map(normalize).filter(Boolean);
|
|
480
|
+
if (verifyOnly) {
|
|
481
|
+
return { found: true, kind: 'dropdown', reached: values.some((value) => value === optionText) };
|
|
482
|
+
}
|
|
483
|
+
if (selectElement instanceof HTMLSelectElement) {
|
|
484
|
+
const nativeOption = [...selectElement.options].find((item) => normalize(item.textContent || item.value) === optionText);
|
|
485
|
+
if (!nativeOption) return { found: true, kind: 'dropdown', reached: false };
|
|
486
|
+
selectElement.value = nativeOption.value;
|
|
487
|
+
selectElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
488
|
+
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
489
|
+
return { found: true, kind: 'dropdown', reached: true, changed: true };
|
|
490
|
+
}
|
|
491
|
+
document.querySelectorAll('[data-browser-opt-active-select="true"]').forEach((element) => element.removeAttribute('data-browser-opt-active-select'));
|
|
492
|
+
selectElement.setAttribute('data-browser-opt-active-select', 'true');
|
|
493
|
+
dispatchMouse(selectElement.querySelector('.ant-select-selector, .el-input, [role="combobox"]') || selectElement);
|
|
494
|
+
return { found: true, kind: 'dropdown', opened: true };
|
|
495
|
+
}
|
|
496
|
+
return { clickFieldScopedTarget, clickTableRowTarget, fillFieldScopedTarget, readFieldScopedValue, selectFieldScopedTarget };
|
|
216
497
|
})()
|
|
217
498
|
`;
|
|
218
499
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-action.js","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/dom-action.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dom-action.js","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/dom-action.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { BrowserAgent } from '#browser-core/agent';
|
|
5
5
|
import type { DeterministicAction, SnapshotEvidence } from '../../../type.js';
|
|
6
|
-
/**
|
|
6
|
+
/** 输入前按需滚动目标,登录页阻塞时转为 handoff,避免误报找不到业务字段。 */
|
|
7
7
|
export declare function executeFillAction(agent: BrowserAgent, action: Extract<DeterministicAction, {
|
|
8
8
|
type: 'fill';
|
|
9
9
|
}>, snapshot: SnapshotEvidence): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill-action.d.ts","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/fill-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAM9E,
|
|
1
|
+
{"version":3,"file":"fill-action.d.ts","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/fill-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAM9E,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,QAAQ,EAAE,gBAAgB,GACzB,MAAM,CAuCR;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,aAAa,EAAE,gBAAgB,GAC9B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAyCtC"}
|
|
@@ -2,32 +2,52 @@ import { findTextboxRef, readTextboxValue } from '../../../utils.js';
|
|
|
2
2
|
import { captureTransientSnapshot } from '../../evidence.js';
|
|
3
3
|
import { buildLoginHandoffActionOutput, isLoginLikeSnapshot } from '../../handoff.js';
|
|
4
4
|
import { fillFieldScopedDomTarget, readFieldScopedDomValue } from './dom-action.js';
|
|
5
|
-
/**
|
|
5
|
+
/** 输入前按需滚动目标,登录页阻塞时转为 handoff,避免误报找不到业务字段。 */
|
|
6
6
|
export function executeFillAction(agent, action, snapshot) {
|
|
7
|
-
let ref = findTextboxRef(snapshot, action.field);
|
|
7
|
+
let ref = action.rowNumber ? null : findTextboxRef(snapshot, action.field);
|
|
8
8
|
if (!ref) {
|
|
9
9
|
if (isLoginLikeSnapshot(snapshot)) {
|
|
10
10
|
return buildLoginHandoffActionOutput(agent, `当前页面仍在登录页,无法继续填写“${action.field}”,请先完成登录后再继续自动化。`);
|
|
11
11
|
}
|
|
12
12
|
agent.waitMs(500);
|
|
13
13
|
const settledSnapshot = captureTransientSnapshot(agent);
|
|
14
|
-
ref = findTextboxRef(settledSnapshot, action.field);
|
|
14
|
+
ref = action.rowNumber ? null : findTextboxRef(settledSnapshot, action.field);
|
|
15
15
|
if (ref) {
|
|
16
|
+
agent.scrollIntoView(ref);
|
|
16
17
|
const output = agent.fill(ref, action.value);
|
|
17
|
-
|
|
18
|
+
const pressOutput = action.pressKey ? agent.press(action.pressKey) : '';
|
|
19
|
+
return `fill delayed @${ref} ${JSON.stringify(action.value)}\n${output}\n${pressOutput}`.trim();
|
|
18
20
|
}
|
|
19
|
-
const domOutput = fillFieldScopedDomTarget(agent, action.field, action.value);
|
|
21
|
+
const domOutput = fillFieldScopedDomTarget(agent, action.field, action.value, Boolean(action.pressKey), action.rowNumber);
|
|
20
22
|
if (domOutput) {
|
|
21
|
-
|
|
23
|
+
const pressOutput = action.pressKey ? agent.press(action.pressKey) : '';
|
|
24
|
+
return `${domOutput}\n${pressOutput}`.trim();
|
|
22
25
|
}
|
|
23
26
|
throw new Error(`无法找到输入框:${action.field}`);
|
|
24
27
|
}
|
|
28
|
+
agent.scrollIntoView(ref);
|
|
25
29
|
const output = agent.fill(ref, action.value);
|
|
26
|
-
|
|
30
|
+
const pressOutput = action.pressKey ? agent.press(action.pressKey) : '';
|
|
31
|
+
return `fill @${ref} ${JSON.stringify(action.value)}\n${output}\n${pressOutput}`.trim();
|
|
27
32
|
}
|
|
28
33
|
/** 确认目标输入框已写入预期值,避免命令成功返回但受控表单没有接收输入时误报通过。 */
|
|
29
34
|
export function verifyFillActionEffect(agent, action, afterSnapshot) {
|
|
35
|
+
if (action.rowNumber) {
|
|
36
|
+
const domValue = readFieldScopedDomValue(agent, action.field, action.rowNumber);
|
|
37
|
+
const target = `第 ${action.rowNumber} 行${action.field}`;
|
|
38
|
+
if (domValue === action.value) {
|
|
39
|
+
return { passed: true, message: `已通过 DOM 确认输入值:${target}=${action.value}` };
|
|
40
|
+
}
|
|
41
|
+
const domDescription = domValue === null ? 'DOM 未定位到该行输入框' : `DOM 实际值为${JSON.stringify(domValue)}`;
|
|
42
|
+
return {
|
|
43
|
+
passed: false,
|
|
44
|
+
message: `动作后未确认输入值:${target}=${action.value}(${domDescription})`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
30
47
|
const actualValue = readTextboxValue(afterSnapshot, action.field);
|
|
48
|
+
if (action.pressKey === 'Enter' && afterSnapshot.text.includes(action.value)) {
|
|
49
|
+
return { passed: true, message: `已确认输入并按 Enter 提交:${action.field}=${action.value}` };
|
|
50
|
+
}
|
|
31
51
|
if (actualValue === action.value) {
|
|
32
52
|
return { passed: true, message: `已确认输入值:${action.field}=${action.value}` };
|
|
33
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill-action.js","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/fill-action.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAEpF,
|
|
1
|
+
{"version":3,"file":"fill-action.js","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/fill-action.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { BrowserAgent } from '#browser-core/agent';
|
|
5
5
|
import type { DeterministicAction, SnapshotEvidence, DeterministicExecutionOptions } from '../../../type.js';
|
|
6
|
-
/**
|
|
6
|
+
/** 选择前按需滚动目标,包含 switch、原生单选复选框和长表单滚动搜索兜底。 */
|
|
7
7
|
export declare function executeSelectOptionAction(agent: BrowserAgent, action: Extract<DeterministicAction, {
|
|
8
8
|
type: 'select-option';
|
|
9
9
|
}>, snapshot: SnapshotEvidence, options: DeterministicExecutionOptions): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-option-action.d.ts","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/select-option-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"select-option-action.d.ts","sourceRoot":"","sources":["../../../../../src/browser-opt/runner/actions/utils/select-option-action.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AAa7G,6CAA6C;AAC7C,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,EAC/D,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,6BAA6B,GACrC,MAAM,CAyHR;AAED,2CAA2C;AAC3C,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,CAAC,mBAAmB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,EAC/D,aAAa,EAAE,gBAAgB,GAC9B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CA4DtC"}
|