@dyyz1993/agent-browser 0.13.2 → 0.24.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.
- package/README.md +108 -0
- package/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/bin/agent-browser-win32-x64.exe +0 -0
- package/dist/__tests__/e2e/utils/test-helpers.d.ts +1 -0
- package/dist/__tests__/e2e/utils/test-helpers.d.ts.map +1 -1
- package/dist/__tests__/e2e/utils/test-helpers.js +14 -1
- package/dist/__tests__/e2e/utils/test-helpers.js.map +1 -1
- package/dist/__tests__/utils/free-port.d.ts +2 -0
- package/dist/__tests__/utils/free-port.d.ts.map +1 -0
- package/dist/__tests__/utils/free-port.js +18 -0
- package/dist/__tests__/utils/free-port.js.map +1 -0
- package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
- package/dist/__tests__/utils/parseCli.js +83 -9
- package/dist/__tests__/utils/parseCli.js.map +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +298 -9
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +11 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +75 -19
- package/dist/browser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +172 -15
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/connection.d.ts +13 -0
- package/dist/cli/connection.d.ts.map +1 -1
- package/dist/cli/connection.js +137 -48
- package/dist/cli/connection.js.map +1 -1
- package/dist/cli/flags.d.ts.map +1 -1
- package/dist/cli/flags.js +0 -1
- package/dist/cli/flags.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +63 -22
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +0 -32
- package/dist/cli/output.js.map +1 -1
- package/dist/cli.js +20 -2
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +291 -264
- package/dist/daemon.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/flow/exporters/cypress.d.ts +9 -0
- package/dist/flow/exporters/cypress.d.ts.map +1 -0
- package/dist/flow/exporters/cypress.js +256 -0
- package/dist/flow/exporters/cypress.js.map +1 -0
- package/dist/flow/exporters/index.d.ts +6 -0
- package/dist/flow/exporters/index.d.ts.map +1 -0
- package/dist/flow/exporters/index.js +5 -0
- package/dist/flow/exporters/index.js.map +1 -0
- package/dist/flow/exporters/playwright.d.ts +20 -0
- package/dist/flow/exporters/playwright.d.ts.map +1 -0
- package/dist/flow/exporters/playwright.js +175 -0
- package/dist/flow/exporters/playwright.js.map +1 -0
- package/dist/flow/exporters/python.d.ts +20 -0
- package/dist/flow/exporters/python.d.ts.map +1 -0
- package/dist/flow/exporters/python.js +163 -0
- package/dist/flow/exporters/python.js.map +1 -0
- package/dist/flow/exporters/selenium.d.ts +9 -0
- package/dist/flow/exporters/selenium.d.ts.map +1 -0
- package/dist/flow/exporters/selenium.js +298 -0
- package/dist/flow/exporters/selenium.js.map +1 -0
- package/dist/flow/exporters/types.d.ts +13 -0
- package/dist/flow/exporters/types.d.ts.map +1 -0
- package/dist/flow/exporters/types.js +2 -0
- package/dist/flow/exporters/types.js.map +1 -0
- package/dist/flow/flow-executor.d.ts +57 -0
- package/dist/flow/flow-executor.d.ts.map +1 -0
- package/dist/flow/flow-executor.js +1263 -0
- package/dist/flow/flow-executor.js.map +1 -0
- package/dist/flow/index.d.ts +15 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +10 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/output.d.ts +11 -0
- package/dist/flow/output.d.ts.map +1 -0
- package/dist/flow/output.js +84 -0
- package/dist/flow/output.js.map +1 -0
- package/dist/flow/plugin-system.d.ts +48 -0
- package/dist/flow/plugin-system.d.ts.map +1 -0
- package/dist/flow/plugin-system.js +132 -0
- package/dist/flow/plugin-system.js.map +1 -0
- package/dist/flow/plugins/file-output-plugin.d.ts +8 -0
- package/dist/flow/plugins/file-output-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/file-output-plugin.js +31 -0
- package/dist/flow/plugins/file-output-plugin.js.map +1 -0
- package/dist/flow/plugins/index.d.ts +4 -0
- package/dist/flow/plugins/index.d.ts.map +1 -0
- package/dist/flow/plugins/index.js +4 -0
- package/dist/flow/plugins/index.js.map +1 -0
- package/dist/flow/plugins/logging-plugin.d.ts +7 -0
- package/dist/flow/plugins/logging-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/logging-plugin.js +40 -0
- package/dist/flow/plugins/logging-plugin.js.map +1 -0
- package/dist/flow/plugins/webhook-plugin.d.ts +7 -0
- package/dist/flow/plugins/webhook-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/webhook-plugin.js +24 -0
- package/dist/flow/plugins/webhook-plugin.js.map +1 -0
- package/dist/flow/presets/index.d.ts +10 -0
- package/dist/flow/presets/index.d.ts.map +1 -0
- package/dist/flow/presets/index.js +29 -0
- package/dist/flow/presets/index.js.map +1 -0
- package/dist/flow/recorder-to-flow.d.ts +70 -0
- package/dist/flow/recorder-to-flow.d.ts.map +1 -0
- package/dist/flow/recorder-to-flow.js +392 -0
- package/dist/flow/recorder-to-flow.js.map +1 -0
- package/dist/flow/site-manager.d.ts +24 -0
- package/dist/flow/site-manager.d.ts.map +1 -0
- package/dist/flow/site-manager.js +125 -0
- package/dist/flow/site-manager.js.map +1 -0
- package/dist/flow/types.d.ts +196 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +2 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/flow/yaml-parser.d.ts +15 -0
- package/dist/flow/yaml-parser.d.ts.map +1 -0
- package/dist/flow/yaml-parser.js +216 -0
- package/dist/flow/yaml-parser.js.map +1 -0
- package/dist/human-mouse.d.ts.map +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +15 -11
- package/dist/protocol.js.map +1 -1
- package/dist/rc-config.d.ts.map +1 -1
- package/dist/rc-config.js +1 -2
- package/dist/rc-config.js.map +1 -1
- package/dist/recorder/inject.js +730 -332
- package/dist/snapshot-store.d.ts +83 -0
- package/dist/snapshot-store.d.ts.map +1 -0
- package/dist/snapshot-store.js +112 -0
- package/dist/snapshot-store.js.map +1 -0
- package/dist/snapshot.d.ts +6 -7
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +471 -17
- package/dist/snapshot.js.map +1 -1
- package/dist/stream-server-standalone.d.ts.map +1 -1
- package/dist/stream-server-standalone.js.map +1 -1
- package/dist/stream-server.d.ts.map +1 -1
- package/dist/stream-server.js +38 -13
- package/dist/stream-server.js.map +1 -1
- package/dist/test-live.js +5 -5
- package/dist/test-live.js.map +1 -1
- package/dist/types.d.ts +13 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer-script.d.ts.map +1 -1
- package/dist/viewer-script.js +12 -6
- package/dist/viewer-script.js.map +1 -1
- package/package.json +18 -5
- package/skills/agent-browser/SKILL.md +151 -3
- package/dist/ios-actions.d.ts +0 -11
- package/dist/ios-actions.d.ts.map +0 -1
- package/dist/ios-actions.js +0 -228
- package/dist/ios-actions.js.map +0 -1
- package/dist/ios-manager.d.ts +0 -266
- package/dist/ios-manager.d.ts.map +0 -1
- package/dist/ios-manager.js +0 -1076
- package/dist/ios-manager.js.map +0 -1
package/dist/recorder/inject.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function() {
|
|
1
|
+
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
// 配置常量
|
|
@@ -76,6 +76,8 @@
|
|
|
76
76
|
let pendingScroll = null;
|
|
77
77
|
let lastFillSelector = null;
|
|
78
78
|
let lastFillValue = '';
|
|
79
|
+
let lastFillFallbacks = [];
|
|
80
|
+
let lastFillIdentity = null;
|
|
79
81
|
let fillTimeout = null;
|
|
80
82
|
let currentViewport = { width: window.innerWidth, height: window.innerHeight };
|
|
81
83
|
let pendingResize = null;
|
|
@@ -89,7 +91,25 @@
|
|
|
89
91
|
// 暴露初始视口(隐蔽名称)
|
|
90
92
|
window.xyzVp = { ...currentViewport };
|
|
91
93
|
|
|
92
|
-
const SKIP_TAGS = new Set([
|
|
94
|
+
const SKIP_TAGS = new Set([
|
|
95
|
+
'SCRIPT',
|
|
96
|
+
'STYLE',
|
|
97
|
+
'META',
|
|
98
|
+
'LINK',
|
|
99
|
+
'HEAD',
|
|
100
|
+
'NOSCRIPT',
|
|
101
|
+
'BR',
|
|
102
|
+
'HR',
|
|
103
|
+
'SVG',
|
|
104
|
+
'PATH',
|
|
105
|
+
'TITLE',
|
|
106
|
+
'BASE',
|
|
107
|
+
'WBR',
|
|
108
|
+
'AREA',
|
|
109
|
+
'MAP',
|
|
110
|
+
'COL',
|
|
111
|
+
'COLGROUP',
|
|
112
|
+
]);
|
|
93
113
|
|
|
94
114
|
// ============ 私有函数:统一事件 API ============
|
|
95
115
|
function pushEvent(action) {
|
|
@@ -113,7 +133,7 @@
|
|
|
113
133
|
if (!action.id) {
|
|
114
134
|
return { success: false, steps, error: 'Missing id for update action' };
|
|
115
135
|
}
|
|
116
|
-
const updateIndex = steps.findIndex(s => s.id === action.id);
|
|
136
|
+
const updateIndex = steps.findIndex((s) => s.id === action.id);
|
|
117
137
|
if (updateIndex >= 0) {
|
|
118
138
|
steps[updateIndex] = { ...steps[updateIndex], ...action.data };
|
|
119
139
|
window.xyzQueue = steps;
|
|
@@ -125,7 +145,7 @@
|
|
|
125
145
|
if (!action.id) {
|
|
126
146
|
return { success: false, steps, error: 'Missing id for delete action' };
|
|
127
147
|
}
|
|
128
|
-
const deleteIndex = steps.findIndex(s => s.id === action.id);
|
|
148
|
+
const deleteIndex = steps.findIndex((s) => s.id === action.id);
|
|
129
149
|
if (deleteIndex >= 0) {
|
|
130
150
|
steps.splice(deleteIndex, 1);
|
|
131
151
|
window.xyzQueue = steps;
|
|
@@ -151,7 +171,7 @@
|
|
|
151
171
|
|
|
152
172
|
const now = Date.now();
|
|
153
173
|
const cached = highlightCache.get(element);
|
|
154
|
-
if (cached &&
|
|
174
|
+
if (cached && now - cached.time < CACHE_TTL) {
|
|
155
175
|
return cached.result;
|
|
156
176
|
}
|
|
157
177
|
|
|
@@ -180,7 +200,8 @@
|
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
const style = window.getComputedStyle(element);
|
|
183
|
-
const result =
|
|
203
|
+
const result =
|
|
204
|
+
style.display !== 'none' && style.visibility !== 'hidden' && parseFloat(style.opacity) !== 0;
|
|
184
205
|
|
|
185
206
|
highlightCache.set(element, { time: now, result });
|
|
186
207
|
return result;
|
|
@@ -194,7 +215,9 @@
|
|
|
194
215
|
// 使用动态绑定名称
|
|
195
216
|
const bindingName = window.xyzBindingName || 'xyzTrack';
|
|
196
217
|
if (typeof window[bindingName] === 'function') {
|
|
197
|
-
try {
|
|
218
|
+
try {
|
|
219
|
+
window[bindingName](JSON.stringify(event.data.step));
|
|
220
|
+
} catch (e) {}
|
|
198
221
|
}
|
|
199
222
|
} else {
|
|
200
223
|
try {
|
|
@@ -204,26 +227,30 @@
|
|
|
204
227
|
}
|
|
205
228
|
});
|
|
206
229
|
|
|
207
|
-
document.addEventListener(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
mousePath.
|
|
230
|
+
document.addEventListener(
|
|
231
|
+
'mousemove',
|
|
232
|
+
(e) => {
|
|
233
|
+
// 检查录制会话是否仍然活跃
|
|
234
|
+
// 注意:xyzActive 可能是 undefined(在 iframe 中),所以只检查明确为 false 的情况
|
|
235
|
+
if (window.xyzActive === false || window.xyzStopped) return;
|
|
236
|
+
|
|
237
|
+
// 检查当前会话是否是最新的
|
|
238
|
+
// 由于 addInitScript 是累积的,旧的监听器可能会继续工作
|
|
239
|
+
// 通过比较时间戳来确保只有最新的会话记录事件
|
|
240
|
+
const currentTimestamp = parseInt((window.xyzSessionId || '').replace('recorder-', '')) || 0;
|
|
241
|
+
if (thisTimestamp > 0 && currentTimestamp > thisTimestamp) return;
|
|
242
|
+
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
if (now - lastTime > TRAJECTORY_INTERVAL) {
|
|
245
|
+
mousePath.push({ x: e.clientX, y: e.clientY, t: now });
|
|
246
|
+
if (mousePath.length > MAX_TRAJECTORY_POINTS) {
|
|
247
|
+
mousePath.shift();
|
|
248
|
+
}
|
|
249
|
+
lastTime = now;
|
|
223
250
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
},
|
|
252
|
+
true
|
|
253
|
+
);
|
|
227
254
|
|
|
228
255
|
function getTrajectory() {
|
|
229
256
|
// 检查当前会话是否是最新的
|
|
@@ -234,7 +261,7 @@
|
|
|
234
261
|
mousePath = [];
|
|
235
262
|
return [];
|
|
236
263
|
}
|
|
237
|
-
|
|
264
|
+
|
|
238
265
|
const points = mousePath.slice(-4);
|
|
239
266
|
mousePath = [];
|
|
240
267
|
return points;
|
|
@@ -281,11 +308,21 @@
|
|
|
281
308
|
|
|
282
309
|
function syncStep(step) {
|
|
283
310
|
if (pendingResize) {
|
|
284
|
-
syncStepDirect({
|
|
311
|
+
syncStepDirect({
|
|
312
|
+
timestamp: Date.now(),
|
|
313
|
+
action: 'resize',
|
|
314
|
+
from: pendingResize.from,
|
|
315
|
+
to: pendingResize.to,
|
|
316
|
+
});
|
|
285
317
|
pendingResize = null;
|
|
286
318
|
}
|
|
287
319
|
if (pendingScroll) {
|
|
288
|
-
syncStepDirect({
|
|
320
|
+
syncStepDirect({
|
|
321
|
+
timestamp: Date.now(),
|
|
322
|
+
action: 'scroll',
|
|
323
|
+
x: pendingScroll.x,
|
|
324
|
+
y: pendingScroll.y,
|
|
325
|
+
});
|
|
289
326
|
pendingScroll = null;
|
|
290
327
|
}
|
|
291
328
|
const trajectory = getTrajectory();
|
|
@@ -295,41 +332,52 @@
|
|
|
295
332
|
syncStepDirect(step);
|
|
296
333
|
}
|
|
297
334
|
|
|
298
|
-
window.addEventListener(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
window.addEventListener(
|
|
336
|
+
'resize',
|
|
337
|
+
() => {
|
|
338
|
+
clearTimeout(resizeTimeout);
|
|
339
|
+
resizeTimeout = setTimeout(() => {
|
|
340
|
+
const newWidth = window.innerWidth;
|
|
341
|
+
const newHeight = window.innerHeight;
|
|
342
|
+
if (newWidth !== currentViewport.width || newHeight !== currentViewport.height) {
|
|
343
|
+
pendingResize = {
|
|
344
|
+
from: { ...currentViewport },
|
|
345
|
+
to: { width: newWidth, height: newHeight },
|
|
346
|
+
};
|
|
347
|
+
currentViewport = { width: newWidth, height: newHeight };
|
|
348
|
+
}
|
|
349
|
+
}, 100);
|
|
350
|
+
},
|
|
351
|
+
true
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
window.addEventListener(
|
|
355
|
+
'scroll',
|
|
356
|
+
() => {
|
|
357
|
+
clearTimeout(scrollTimeout);
|
|
358
|
+
scrollTimeout = setTimeout(() => {
|
|
359
|
+
const scrollX = window.scrollX;
|
|
360
|
+
const scrollY = window.scrollY;
|
|
361
|
+
if (
|
|
362
|
+
Math.abs(scrollY - lastScrollY) > SCROLL_THRESHOLD ||
|
|
363
|
+
Math.abs(scrollX - lastScrollX) > SCROLL_THRESHOLD
|
|
364
|
+
) {
|
|
365
|
+
pendingScroll = { x: scrollX, y: scrollY };
|
|
366
|
+
lastScrollX = scrollX;
|
|
367
|
+
lastScrollY = scrollY;
|
|
368
|
+
}
|
|
369
|
+
}, 100);
|
|
370
|
+
},
|
|
371
|
+
true
|
|
372
|
+
);
|
|
322
373
|
|
|
323
374
|
// ============ XPath 和 Selector 工具函数 ============
|
|
324
375
|
function isUniqueXPath(xpath) {
|
|
325
376
|
try {
|
|
326
|
-
return
|
|
327
|
-
'count(' + xpath + ')',
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
XPathResult.NUMBER_TYPE,
|
|
331
|
-
null
|
|
332
|
-
).numberValue === 1;
|
|
377
|
+
return (
|
|
378
|
+
document.evaluate('count(' + xpath + ')', document, null, XPathResult.NUMBER_TYPE, null)
|
|
379
|
+
.numberValue === 1
|
|
380
|
+
);
|
|
333
381
|
} catch (e) {
|
|
334
382
|
return false;
|
|
335
383
|
}
|
|
@@ -392,7 +440,16 @@
|
|
|
392
440
|
}
|
|
393
441
|
|
|
394
442
|
if (!result) {
|
|
395
|
-
const semanticAttrs = [
|
|
443
|
+
const semanticAttrs = [
|
|
444
|
+
'data-testid',
|
|
445
|
+
'data-test',
|
|
446
|
+
'data-cy',
|
|
447
|
+
'aria-label',
|
|
448
|
+
'name',
|
|
449
|
+
'role',
|
|
450
|
+
'title',
|
|
451
|
+
'placeholder',
|
|
452
|
+
];
|
|
396
453
|
for (const attr of semanticAttrs) {
|
|
397
454
|
const value = element.getAttribute(attr);
|
|
398
455
|
if (value) {
|
|
@@ -408,7 +465,8 @@
|
|
|
408
465
|
if (!result) {
|
|
409
466
|
const text = element.innerText?.trim();
|
|
410
467
|
if (text && text.length < 30 && ['BUTTON', 'A', 'SPAN', 'LABEL'].includes(element.tagName)) {
|
|
411
|
-
const xpath =
|
|
468
|
+
const xpath =
|
|
469
|
+
'//' + element.tagName.toLowerCase() + '[contains(text(), "' + text.slice(0, 20) + '")]';
|
|
412
470
|
if (isUniqueXPath(xpath)) result = xpath;
|
|
413
471
|
}
|
|
414
472
|
}
|
|
@@ -441,19 +499,27 @@
|
|
|
441
499
|
|
|
442
500
|
// 语义属性优先级列表
|
|
443
501
|
const SEMANTIC_ATTRS = [
|
|
444
|
-
'data-testid',
|
|
445
|
-
'
|
|
446
|
-
'
|
|
502
|
+
'data-testid',
|
|
503
|
+
'data-test',
|
|
504
|
+
'data-cy',
|
|
505
|
+
'name',
|
|
506
|
+
'aria-label',
|
|
507
|
+
'aria-labelledby',
|
|
508
|
+
'role',
|
|
509
|
+
'type',
|
|
510
|
+
'placeholder',
|
|
511
|
+
'title',
|
|
512
|
+
'alt',
|
|
447
513
|
];
|
|
448
514
|
|
|
449
515
|
// 工具类名排除规则
|
|
450
516
|
const UTILITY_CLASS_PATTERNS = [
|
|
451
|
-
/^_/,
|
|
452
|
-
/^css-/,
|
|
517
|
+
/^_/, // 下划线开头
|
|
518
|
+
/^css-/, // CSS Modules
|
|
453
519
|
/^[a-z]{1,2}$/, // 1-2个字符的短类名
|
|
454
520
|
/^(active|disabled|hidden|visible|selected|hover|focus|current|open|closed)$/i,
|
|
455
521
|
/^(text-|font-|bg-|p-|m-|w-|h-|flex|grid|border|rounded|shadow|opacity|z-)/,
|
|
456
|
-
/^(sm:|md:|lg:|xl:|2xl:)
|
|
522
|
+
/^(sm:|md:|lg:|xl:|2xl:)/, // 响应式前缀
|
|
457
523
|
];
|
|
458
524
|
|
|
459
525
|
// 检测高熵类名(CSS Modules/Emotion/Styled Components 自动生成的随机类名)
|
|
@@ -493,14 +559,17 @@
|
|
|
493
559
|
// 过滤有用的类名
|
|
494
560
|
function filterUsefulClasses(element) {
|
|
495
561
|
if (!element.className || typeof element.className !== 'string') return [];
|
|
496
|
-
return element.className
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
562
|
+
return element.className
|
|
563
|
+
.trim()
|
|
564
|
+
.split(/\s+/)
|
|
565
|
+
.filter((c) => {
|
|
566
|
+
if (!c) return false;
|
|
567
|
+
// 过滤工具类名
|
|
568
|
+
if (UTILITY_CLASS_PATTERNS.some((p) => p.test(c))) return false;
|
|
569
|
+
// 过滤高熵类名
|
|
570
|
+
if (isHighEntropyClassName(c)) return false;
|
|
571
|
+
return true;
|
|
572
|
+
});
|
|
504
573
|
}
|
|
505
574
|
|
|
506
575
|
// 策略1: 多属性组合选择器
|
|
@@ -527,9 +596,18 @@
|
|
|
527
596
|
if (attrs.length >= 2) {
|
|
528
597
|
for (let i = 0; i < attrs.length; i++) {
|
|
529
598
|
for (let j = i + 1; j < attrs.length; j++) {
|
|
530
|
-
const selector =
|
|
531
|
-
|
|
532
|
-
'[' +
|
|
599
|
+
const selector =
|
|
600
|
+
tag +
|
|
601
|
+
'[' +
|
|
602
|
+
attrs[i].attr +
|
|
603
|
+
'="' +
|
|
604
|
+
CSS.escape(attrs[i].value) +
|
|
605
|
+
'"]' +
|
|
606
|
+
'[' +
|
|
607
|
+
attrs[j].attr +
|
|
608
|
+
'="' +
|
|
609
|
+
CSS.escape(attrs[j].value) +
|
|
610
|
+
'"]';
|
|
533
611
|
if (isUniqueSelector(selector)) return selector;
|
|
534
612
|
}
|
|
535
613
|
}
|
|
@@ -552,7 +630,8 @@
|
|
|
552
630
|
for (const attr of SEMANTIC_ATTRS) {
|
|
553
631
|
const value = element.getAttribute(attr);
|
|
554
632
|
if (value) {
|
|
555
|
-
const selector =
|
|
633
|
+
const selector =
|
|
634
|
+
tag + '.' + CSS.escape(bestClass) + '[' + attr + '="' + CSS.escape(value) + '"]';
|
|
556
635
|
if (isUniqueSelector(selector)) return selector;
|
|
557
636
|
}
|
|
558
637
|
}
|
|
@@ -578,7 +657,13 @@
|
|
|
578
657
|
|
|
579
658
|
// 尝试组合多个类名
|
|
580
659
|
for (let i = 2; i <= Math.min(3, classes.length); i++) {
|
|
581
|
-
const selector =
|
|
660
|
+
const selector =
|
|
661
|
+
tag +
|
|
662
|
+
'.' +
|
|
663
|
+
classes
|
|
664
|
+
.slice(0, i)
|
|
665
|
+
.map((c) => CSS.escape(c))
|
|
666
|
+
.join('.');
|
|
582
667
|
if (isUniqueSelector(selector)) return selector;
|
|
583
668
|
}
|
|
584
669
|
|
|
@@ -659,7 +744,12 @@
|
|
|
659
744
|
if (classes.length > 0) {
|
|
660
745
|
// 按长度排序,取最具体的类名
|
|
661
746
|
classes.sort((a, b) => b.length - a.length);
|
|
662
|
-
selector +=
|
|
747
|
+
selector +=
|
|
748
|
+
'.' +
|
|
749
|
+
classes
|
|
750
|
+
.slice(0, 2)
|
|
751
|
+
.map((c) => CSS.escape(c))
|
|
752
|
+
.join('.');
|
|
663
753
|
}
|
|
664
754
|
return selector;
|
|
665
755
|
}
|
|
@@ -669,7 +759,7 @@
|
|
|
669
759
|
if (!parent) return baseSelector;
|
|
670
760
|
|
|
671
761
|
const siblings = Array.from(parent.children);
|
|
672
|
-
const sameTagSiblings = siblings.filter(s => s.tagName === element.tagName);
|
|
762
|
+
const sameTagSiblings = siblings.filter((s) => s.tagName === element.tagName);
|
|
673
763
|
|
|
674
764
|
if (sameTagSiblings.length === 1) {
|
|
675
765
|
return baseSelector;
|
|
@@ -843,13 +933,110 @@
|
|
|
843
933
|
return getSelectorWithShadow(element);
|
|
844
934
|
}
|
|
845
935
|
|
|
936
|
+
// === Fallback Selectors (Enhancement 1) ===
|
|
937
|
+
function getFallbackSelectors(element) {
|
|
938
|
+
const primary = getSelectorInternal(element);
|
|
939
|
+
const candidates = [];
|
|
940
|
+
|
|
941
|
+
const tryCandidate = (selector) => {
|
|
942
|
+
if (selector && selector !== primary && !candidates.includes(selector)) {
|
|
943
|
+
candidates.push(selector);
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
if (element.id) {
|
|
948
|
+
const sel = '#' + CSS.escape(element.id);
|
|
949
|
+
try {
|
|
950
|
+
if (document.querySelectorAll(sel).length === 1) tryCandidate(sel);
|
|
951
|
+
} catch (e) {}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
tryCandidate(getMultiAttributeSelector(element));
|
|
955
|
+
tryCandidate(getAttributeClassComboSelector(element));
|
|
956
|
+
tryCandidate(getBestClassSelector(element));
|
|
957
|
+
tryCandidate(getSiblingBasedSelector(element));
|
|
958
|
+
tryCandidate(buildComposedSelector(element));
|
|
959
|
+
|
|
960
|
+
const base = getBaseSelector(element);
|
|
961
|
+
const nthSel = makeUniqueWithNth(element, base);
|
|
962
|
+
try {
|
|
963
|
+
if (document.querySelectorAll(nthSel).length === 1) tryCandidate(nthSel);
|
|
964
|
+
} catch (e) {}
|
|
965
|
+
|
|
966
|
+
tryCandidate(buildUniquePath(element));
|
|
967
|
+
|
|
968
|
+
return candidates.slice(0, 3);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// === Element Identity Capture (Enhancement 2) ===
|
|
972
|
+
function captureSemanticAttributes(element) {
|
|
973
|
+
const attrs = {};
|
|
974
|
+
const semanticAttrs = [
|
|
975
|
+
'name',
|
|
976
|
+
'aria-label',
|
|
977
|
+
'data-testid',
|
|
978
|
+
'data-test',
|
|
979
|
+
'placeholder',
|
|
980
|
+
'type',
|
|
981
|
+
'role',
|
|
982
|
+
'title',
|
|
983
|
+
'href',
|
|
984
|
+
];
|
|
985
|
+
for (const attr of semanticAttrs) {
|
|
986
|
+
const value = element.getAttribute(attr);
|
|
987
|
+
if (value) attrs[attr] = value;
|
|
988
|
+
}
|
|
989
|
+
return attrs;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function getParentSignature(element) {
|
|
993
|
+
let current = element.parentElement;
|
|
994
|
+
let depth = 0;
|
|
995
|
+
while (current && current !== document.body && depth < 10) {
|
|
996
|
+
if (current.id) {
|
|
997
|
+
return '#' + CSS.escape(current.id);
|
|
998
|
+
}
|
|
999
|
+
const testId = current.getAttribute('data-testid') || current.getAttribute('data-test');
|
|
1000
|
+
if (testId) {
|
|
1001
|
+
return current.tagName.toLowerCase() + '[data-testid="' + testId + '"]';
|
|
1002
|
+
}
|
|
1003
|
+
const classes = filterUsefulClasses(current);
|
|
1004
|
+
if (classes.length > 0) {
|
|
1005
|
+
const sel = current.tagName.toLowerCase() + '.' + CSS.escape(classes[0]);
|
|
1006
|
+
try {
|
|
1007
|
+
if (document.querySelectorAll(sel).length === 1) return sel;
|
|
1008
|
+
} catch (e) {}
|
|
1009
|
+
}
|
|
1010
|
+
current = current.parentElement;
|
|
1011
|
+
depth++;
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function captureElementIdentity(element) {
|
|
1017
|
+
const r = element.getBoundingClientRect();
|
|
1018
|
+
return {
|
|
1019
|
+
tagName: element.tagName.toLowerCase(),
|
|
1020
|
+
textContent: (element.textContent || '').trim().substring(0, 100),
|
|
1021
|
+
attributes: captureSemanticAttributes(element),
|
|
1022
|
+
classes: filterUsefulClasses(element).slice(0, 5),
|
|
1023
|
+
boundingRect: {
|
|
1024
|
+
x: Math.round(r.x),
|
|
1025
|
+
y: Math.round(r.y),
|
|
1026
|
+
width: Math.round(r.width),
|
|
1027
|
+
height: Math.round(r.height),
|
|
1028
|
+
},
|
|
1029
|
+
parentSignature: getParentSignature(element),
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
846
1033
|
function getElementInfo(element) {
|
|
847
1034
|
return {
|
|
848
1035
|
tagName: element.tagName.toLowerCase(),
|
|
849
1036
|
id: element.id,
|
|
850
1037
|
className: element.className,
|
|
851
1038
|
text: element.innerText ? element.innerText.slice(0, 50) : '',
|
|
852
|
-
xpath: getXPath(element)
|
|
1039
|
+
xpath: getXPath(element),
|
|
853
1040
|
};
|
|
854
1041
|
}
|
|
855
1042
|
|
|
@@ -874,13 +1061,19 @@
|
|
|
874
1061
|
value: data.value,
|
|
875
1062
|
elementInfo: data.elementInfo,
|
|
876
1063
|
annotation: data.annotation,
|
|
877
|
-
iframe: isInIframe
|
|
1064
|
+
iframe: isInIframe,
|
|
1065
|
+
fallbackSelectors: data.fallbackSelectors
|
|
1066
|
+
? data.fallbackSelectors.map((s) => iframePrefix + s)
|
|
1067
|
+
: undefined,
|
|
1068
|
+
elementIdentity: data.elementIdentity || undefined,
|
|
878
1069
|
};
|
|
879
1070
|
|
|
880
1071
|
if (action === 'keyboard') {
|
|
881
1072
|
delete step.selector;
|
|
882
1073
|
delete step.xpath;
|
|
883
1074
|
delete step.elementInfo;
|
|
1075
|
+
delete step.fallbackSelectors;
|
|
1076
|
+
delete step.elementIdentity;
|
|
884
1077
|
// 复制键盘事件相关属性
|
|
885
1078
|
step.key = data.key;
|
|
886
1079
|
step.code = data.code;
|
|
@@ -893,177 +1086,245 @@
|
|
|
893
1086
|
syncStep(step);
|
|
894
1087
|
}
|
|
895
1088
|
|
|
896
|
-
document.addEventListener(
|
|
897
|
-
|
|
898
|
-
|
|
1089
|
+
document.addEventListener(
|
|
1090
|
+
'click',
|
|
1091
|
+
(e) => {
|
|
1092
|
+
const path = e.composedPath();
|
|
1093
|
+
const element = path[0] || e.target;
|
|
899
1094
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1095
|
+
if (isInPanel(element)) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (element === document.body || element === document.documentElement) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
906
1101
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1102
|
+
const link = element.closest('a[href]');
|
|
1103
|
+
if (link) {
|
|
1104
|
+
const href = link.href;
|
|
1105
|
+
const target = link.target || '_self';
|
|
1106
|
+
let isExternal = target === '_blank';
|
|
1107
|
+
if (!isExternal && href.startsWith('http')) {
|
|
1108
|
+
try {
|
|
1109
|
+
const linkHost = new URL(href).host;
|
|
1110
|
+
isExternal = linkHost !== window.location.host;
|
|
1111
|
+
} catch (e) {}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
recordStep('link_click', {
|
|
1115
|
+
selector: getSelector(link),
|
|
1116
|
+
xpath: getXPath(link),
|
|
1117
|
+
value: href,
|
|
1118
|
+
elementInfo: { ...getElementInfo(link), target: target, isExternal: isExternal },
|
|
1119
|
+
fallbackSelectors: getFallbackSelectors(link),
|
|
1120
|
+
elementIdentity: captureElementIdentity(link),
|
|
1121
|
+
});
|
|
1122
|
+
return;
|
|
917
1123
|
}
|
|
918
1124
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
xpath: getXPath(link),
|
|
922
|
-
value: href,
|
|
923
|
-
elementInfo: { ...getElementInfo(link), target: target, isExternal: isExternal }
|
|
924
|
-
});
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
1125
|
+
const tag = element.tagName;
|
|
1126
|
+
const inputType = (element.type || '').toLowerCase();
|
|
927
1127
|
|
|
928
|
-
|
|
929
|
-
|
|
1128
|
+
if (tag === 'INPUT' && (inputType === 'checkbox' || inputType === 'radio')) {
|
|
1129
|
+
const isChecked = element.checked;
|
|
1130
|
+
const action = inputType === 'radio' ? 'check' : isChecked ? 'check' : 'uncheck';
|
|
1131
|
+
recordStep(action, {
|
|
1132
|
+
selector: getSelector(element),
|
|
1133
|
+
xpath: getXPath(element),
|
|
1134
|
+
elementInfo: getElementInfo(element),
|
|
1135
|
+
fallbackSelectors: getFallbackSelectors(element),
|
|
1136
|
+
elementIdentity: captureElementIdentity(element),
|
|
1137
|
+
});
|
|
1138
|
+
if (!HIDE_UI && !isInIframe && typeof addMarker === 'function') {
|
|
1139
|
+
addMarker(element, action);
|
|
1140
|
+
}
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
930
1143
|
|
|
931
|
-
|
|
932
|
-
const isChecked = element.checked;
|
|
933
|
-
const action = inputType === 'radio' ? 'check' : (isChecked ? 'check' : 'uncheck');
|
|
934
|
-
recordStep(action, {
|
|
1144
|
+
recordStep('click', {
|
|
935
1145
|
selector: getSelector(element),
|
|
936
1146
|
xpath: getXPath(element),
|
|
937
|
-
elementInfo: getElementInfo(element)
|
|
1147
|
+
elementInfo: getElementInfo(element),
|
|
1148
|
+
fallbackSelectors: getFallbackSelectors(element),
|
|
1149
|
+
elementIdentity: captureElementIdentity(element),
|
|
938
1150
|
});
|
|
1151
|
+
|
|
1152
|
+
// 非隐藏模式下添加标记
|
|
939
1153
|
if (!HIDE_UI && !isInIframe && typeof addMarker === 'function') {
|
|
940
|
-
addMarker(element,
|
|
1154
|
+
addMarker(element, 'default');
|
|
941
1155
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
// Skip checkbox, radio, and select - they are handled by click and change events
|
|
963
|
-
if (element.tagName === 'SELECT') return;
|
|
964
|
-
const inputType = (element.type || '').toLowerCase();
|
|
965
|
-
if (inputType === 'checkbox' || inputType === 'radio') return;
|
|
966
|
-
|
|
967
|
-
const selector = getSelector(element);
|
|
968
|
-
const value = element.value;
|
|
969
|
-
|
|
970
|
-
clearTimeout(fillTimeout);
|
|
971
|
-
|
|
972
|
-
if (lastFillSelector && lastFillSelector !== selector && lastFillValue) {
|
|
973
|
-
recordStep('fill', { selector: lastFillSelector, value: lastFillValue });
|
|
974
|
-
}
|
|
1156
|
+
},
|
|
1157
|
+
true
|
|
1158
|
+
);
|
|
1159
|
+
|
|
1160
|
+
document.addEventListener(
|
|
1161
|
+
'input',
|
|
1162
|
+
(e) => {
|
|
1163
|
+
const element = e.target;
|
|
1164
|
+
if (!element || !element.tagName) return;
|
|
1165
|
+
if (isInPanel(element)) return;
|
|
1166
|
+
|
|
1167
|
+
// Skip checkbox, radio, and select - they are handled by click and change events
|
|
1168
|
+
if (element.tagName === 'SELECT') return;
|
|
1169
|
+
const inputType = (element.type || '').toLowerCase();
|
|
1170
|
+
if (inputType === 'checkbox' || inputType === 'radio') return;
|
|
1171
|
+
|
|
1172
|
+
const selector = getSelector(element);
|
|
1173
|
+
const value = element.value;
|
|
1174
|
+
const fallbacks = getFallbackSelectors(element);
|
|
1175
|
+
const identity = captureElementIdentity(element);
|
|
975
1176
|
|
|
976
|
-
|
|
977
|
-
lastFillValue = value;
|
|
1177
|
+
clearTimeout(fillTimeout);
|
|
978
1178
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1179
|
+
if (lastFillSelector && lastFillSelector !== selector && lastFillValue) {
|
|
1180
|
+
recordStep('fill', {
|
|
1181
|
+
selector: lastFillSelector,
|
|
1182
|
+
value: lastFillValue,
|
|
1183
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1184
|
+
elementIdentity: lastFillIdentity,
|
|
1185
|
+
});
|
|
984
1186
|
}
|
|
985
|
-
}, 300);
|
|
986
|
-
}, true); // capture phase
|
|
987
|
-
|
|
988
|
-
// Also listen in bubbling phase to catch programmatically dispatched events
|
|
989
|
-
document.addEventListener('input', (e) => {
|
|
990
|
-
const element = e.target;
|
|
991
|
-
if (!element || !element.tagName) return;
|
|
992
|
-
if (isInPanel(element)) return;
|
|
993
|
-
|
|
994
|
-
// Skip checkbox, radio, and select - they are handled by click and change events
|
|
995
|
-
if (element.tagName === 'SELECT') return;
|
|
996
|
-
const inputType = (element.type || '').toLowerCase();
|
|
997
|
-
if (inputType === 'checkbox' || inputType === 'radio') return;
|
|
998
|
-
|
|
999
|
-
const selector = getSelector(element);
|
|
1000
|
-
const value = element.value;
|
|
1001
1187
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1188
|
+
lastFillSelector = selector;
|
|
1189
|
+
lastFillValue = value;
|
|
1190
|
+
lastFillFallbacks = fallbacks;
|
|
1191
|
+
lastFillIdentity = identity;
|
|
1192
|
+
|
|
1193
|
+
fillTimeout = setTimeout(() => {
|
|
1194
|
+
if (lastFillSelector && lastFillValue) {
|
|
1195
|
+
recordStep('fill', {
|
|
1196
|
+
selector: lastFillSelector,
|
|
1197
|
+
value: lastFillValue,
|
|
1198
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1199
|
+
elementIdentity: lastFillIdentity,
|
|
1200
|
+
});
|
|
1201
|
+
lastFillSelector = null;
|
|
1202
|
+
lastFillValue = '';
|
|
1203
|
+
lastFillFallbacks = [];
|
|
1204
|
+
lastFillIdentity = null;
|
|
1205
|
+
}
|
|
1206
|
+
}, 300);
|
|
1207
|
+
},
|
|
1208
|
+
true
|
|
1209
|
+
); // capture phase
|
|
1008
1210
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1211
|
+
// Also listen in bubbling phase to catch programmatically dispatched events
|
|
1212
|
+
document.addEventListener(
|
|
1213
|
+
'input',
|
|
1214
|
+
(e) => {
|
|
1215
|
+
const element = e.target;
|
|
1216
|
+
if (!element || !element.tagName) return;
|
|
1217
|
+
if (isInPanel(element)) return;
|
|
1218
|
+
|
|
1219
|
+
// Skip checkbox, radio, and select - they are handled by click and change events
|
|
1220
|
+
if (element.tagName === 'SELECT') return;
|
|
1221
|
+
const inputType = (element.type || '').toLowerCase();
|
|
1222
|
+
if (inputType === 'checkbox' || inputType === 'radio') return;
|
|
1223
|
+
|
|
1224
|
+
const selector = getSelector(element);
|
|
1225
|
+
const value = element.value;
|
|
1226
|
+
const fallbacks = getFallbackSelectors(element);
|
|
1227
|
+
const identity = captureElementIdentity(element);
|
|
1228
|
+
|
|
1229
|
+
// Only process if not already processed in capture phase
|
|
1230
|
+
if (lastFillSelector === selector && lastFillValue === value) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1012
1233
|
|
|
1013
|
-
|
|
1014
|
-
lastFillValue = value;
|
|
1234
|
+
clearTimeout(fillTimeout);
|
|
1015
1235
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1236
|
+
if (lastFillSelector && lastFillSelector !== selector && lastFillValue) {
|
|
1237
|
+
recordStep('fill', {
|
|
1238
|
+
selector: lastFillSelector,
|
|
1239
|
+
value: lastFillValue,
|
|
1240
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1241
|
+
elementIdentity: lastFillIdentity,
|
|
1242
|
+
});
|
|
1021
1243
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1244
|
+
|
|
1245
|
+
lastFillSelector = selector;
|
|
1246
|
+
lastFillValue = value;
|
|
1247
|
+
lastFillFallbacks = fallbacks;
|
|
1248
|
+
lastFillIdentity = identity;
|
|
1249
|
+
|
|
1250
|
+
fillTimeout = setTimeout(() => {
|
|
1251
|
+
if (lastFillSelector && lastFillValue) {
|
|
1252
|
+
recordStep('fill', {
|
|
1253
|
+
selector: lastFillSelector,
|
|
1254
|
+
value: lastFillValue,
|
|
1255
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1256
|
+
elementIdentity: lastFillIdentity,
|
|
1257
|
+
});
|
|
1258
|
+
lastFillSelector = null;
|
|
1259
|
+
lastFillValue = '';
|
|
1260
|
+
lastFillFallbacks = [];
|
|
1261
|
+
lastFillIdentity = null;
|
|
1262
|
+
}
|
|
1263
|
+
}, 300);
|
|
1264
|
+
},
|
|
1265
|
+
false
|
|
1266
|
+
); // bubbling phase
|
|
1024
1267
|
|
|
1025
1268
|
// 标记事件监听器已注册
|
|
1026
1269
|
window.xyzHasInputListener = true;
|
|
1027
1270
|
|
|
1028
|
-
document.addEventListener(
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
selector: getSelector(element),
|
|
1035
|
-
xpath: getXPath(element),
|
|
1036
|
-
value: element.value,
|
|
1037
|
-
elementInfo: getElementInfo(element)
|
|
1038
|
-
});
|
|
1039
|
-
}, true);
|
|
1040
|
-
|
|
1041
|
-
document.addEventListener('keydown', (e) => {
|
|
1042
|
-
const element = document.activeElement;
|
|
1271
|
+
document.addEventListener(
|
|
1272
|
+
'change',
|
|
1273
|
+
(e) => {
|
|
1274
|
+
const element = e.target;
|
|
1275
|
+
if (!element || element.tagName !== 'SELECT') return;
|
|
1276
|
+
if (isInPanel(element)) return;
|
|
1043
1277
|
|
|
1044
|
-
|
|
1278
|
+
recordStep('select', {
|
|
1279
|
+
selector: getSelector(element),
|
|
1280
|
+
xpath: getXPath(element),
|
|
1281
|
+
value: element.value,
|
|
1282
|
+
elementInfo: getElementInfo(element),
|
|
1283
|
+
fallbackSelectors: getFallbackSelectors(element),
|
|
1284
|
+
elementIdentity: captureElementIdentity(element),
|
|
1285
|
+
});
|
|
1286
|
+
},
|
|
1287
|
+
true
|
|
1288
|
+
);
|
|
1289
|
+
|
|
1290
|
+
document.addEventListener(
|
|
1291
|
+
'keydown',
|
|
1292
|
+
(e) => {
|
|
1293
|
+
const element = document.activeElement;
|
|
1294
|
+
|
|
1295
|
+
if (isInPanel(element)) return;
|
|
1296
|
+
|
|
1297
|
+
const specialKeys = [
|
|
1298
|
+
'Enter',
|
|
1299
|
+
'Tab',
|
|
1300
|
+
'Escape',
|
|
1301
|
+
'Backspace',
|
|
1302
|
+
'ArrowUp',
|
|
1303
|
+
'ArrowDown',
|
|
1304
|
+
'ArrowLeft',
|
|
1305
|
+
'ArrowRight',
|
|
1306
|
+
];
|
|
1045
1307
|
|
|
1046
|
-
|
|
1308
|
+
if (specialKeys.includes(e.key) || e.ctrlKey || e.metaKey || e.altKey) {
|
|
1309
|
+
if (!e.ctrlKey && !e.metaKey && !e.altKey && !specialKeys.includes(e.key)) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1047
1312
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1313
|
+
recordStep('keyboard', {
|
|
1314
|
+
key: e.key,
|
|
1315
|
+
code: e.code,
|
|
1316
|
+
ctrlKey: e.ctrlKey,
|
|
1317
|
+
metaKey: e.metaKey,
|
|
1318
|
+
altKey: e.altKey,
|
|
1319
|
+
shiftKey: e.shiftKey,
|
|
1320
|
+
selector: element ? getSelector(element) : '',
|
|
1321
|
+
xpath: element ? getXPath(element) : '',
|
|
1322
|
+
elementInfo: element ? getElementInfo(element) : null,
|
|
1323
|
+
});
|
|
1051
1324
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
key: e.key,
|
|
1056
|
-
code: e.code,
|
|
1057
|
-
ctrlKey: e.ctrlKey,
|
|
1058
|
-
metaKey: e.metaKey,
|
|
1059
|
-
altKey: e.altKey,
|
|
1060
|
-
shiftKey: e.shiftKey,
|
|
1061
|
-
selector: element ? getSelector(element) : '',
|
|
1062
|
-
xpath: element ? getXPath(element) : '',
|
|
1063
|
-
elementInfo: element ? getElementInfo(element) : null
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
}, true);
|
|
1325
|
+
},
|
|
1326
|
+
true
|
|
1327
|
+
);
|
|
1067
1328
|
|
|
1068
1329
|
window.addEventListener('beforeunload', () => {
|
|
1069
1330
|
if (lastFillSelector && lastFillValue) {
|
|
@@ -1072,22 +1333,31 @@
|
|
|
1072
1333
|
timestamp: Date.now(),
|
|
1073
1334
|
action: 'fill',
|
|
1074
1335
|
selector: lastFillSelector,
|
|
1075
|
-
value: lastFillValue
|
|
1336
|
+
value: lastFillValue,
|
|
1337
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1338
|
+
elementIdentity: lastFillIdentity,
|
|
1076
1339
|
});
|
|
1077
1340
|
}
|
|
1078
1341
|
syncStepDirect({
|
|
1079
1342
|
id: 'step-' + Date.now(),
|
|
1080
1343
|
timestamp: Date.now(),
|
|
1081
1344
|
action: 'navigate',
|
|
1082
|
-
value: window.location.href
|
|
1345
|
+
value: window.location.href,
|
|
1083
1346
|
});
|
|
1084
1347
|
});
|
|
1085
1348
|
|
|
1086
|
-
window.xyzFlushPending = function() {
|
|
1349
|
+
window.xyzFlushPending = function () {
|
|
1087
1350
|
if (lastFillSelector && lastFillValue) {
|
|
1088
|
-
recordStep('fill', {
|
|
1351
|
+
recordStep('fill', {
|
|
1352
|
+
selector: lastFillSelector,
|
|
1353
|
+
value: lastFillValue,
|
|
1354
|
+
fallbackSelectors: lastFillFallbacks,
|
|
1355
|
+
elementIdentity: lastFillIdentity,
|
|
1356
|
+
});
|
|
1089
1357
|
lastFillSelector = null;
|
|
1090
1358
|
lastFillValue = '';
|
|
1359
|
+
lastFillFallbacks = [];
|
|
1360
|
+
lastFillIdentity = null;
|
|
1091
1361
|
}
|
|
1092
1362
|
if (fillTimeout) {
|
|
1093
1363
|
clearTimeout(fillTimeout);
|
|
@@ -1103,7 +1373,7 @@
|
|
|
1103
1373
|
let _checkPanelInterval = null;
|
|
1104
1374
|
|
|
1105
1375
|
// 关闭面板函数(暴露给外部调用)
|
|
1106
|
-
window.xyzClose = function() {
|
|
1376
|
+
window.xyzClose = function () {
|
|
1107
1377
|
if (_animationFrameId) {
|
|
1108
1378
|
cancelAnimationFrame(_animationFrameId);
|
|
1109
1379
|
_animationFrameId = null;
|
|
@@ -1129,10 +1399,10 @@
|
|
|
1129
1399
|
document.getElementById('xyzMk'),
|
|
1130
1400
|
document.getElementById('xyzCv'),
|
|
1131
1401
|
document.getElementById('xyzSh'),
|
|
1132
|
-
document.getElementById('xyzSt')
|
|
1402
|
+
document.getElementById('xyzSt'),
|
|
1133
1403
|
];
|
|
1134
1404
|
|
|
1135
|
-
elements.forEach(el => {
|
|
1405
|
+
elements.forEach((el) => {
|
|
1136
1406
|
if (el && el.parentNode) {
|
|
1137
1407
|
el.parentNode.removeChild(el);
|
|
1138
1408
|
}
|
|
@@ -1140,24 +1410,23 @@
|
|
|
1140
1410
|
|
|
1141
1411
|
window.xyzInited = false;
|
|
1142
1412
|
window.xyzQueue = [];
|
|
1143
|
-
|
|
1144
1413
|
};
|
|
1145
1414
|
|
|
1146
1415
|
// 检查录制会话是否已停止
|
|
1147
1416
|
if (window.xyzStopped) {
|
|
1148
|
-
|
|
1149
1417
|
return;
|
|
1150
1418
|
}
|
|
1151
1419
|
|
|
1152
1420
|
// 检查录制会话是否激活
|
|
1153
1421
|
if (!window.xyzActive) {
|
|
1154
|
-
|
|
1155
1422
|
return;
|
|
1156
1423
|
}
|
|
1157
1424
|
|
|
1158
1425
|
let uiElements = {};
|
|
1159
1426
|
let currentElement = null;
|
|
1160
|
-
let mouseX = 0,
|
|
1427
|
+
let mouseX = 0,
|
|
1428
|
+
mouseY = 0,
|
|
1429
|
+
currentEdge = null;
|
|
1161
1430
|
const EDGE_THRESHOLD = 30;
|
|
1162
1431
|
let animationFrameId = null;
|
|
1163
1432
|
let highlightRafId = null;
|
|
@@ -1311,24 +1580,29 @@
|
|
|
1311
1580
|
});
|
|
1312
1581
|
|
|
1313
1582
|
// Prevent scroll penetration
|
|
1314
|
-
panelBody.addEventListener(
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1583
|
+
panelBody.addEventListener(
|
|
1584
|
+
'wheel',
|
|
1585
|
+
(e) => {
|
|
1586
|
+
const { scrollTop, scrollHeight, clientHeight } = panelBody;
|
|
1587
|
+
const atTop = scrollTop === 0;
|
|
1588
|
+
const atBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
1589
|
+
|
|
1590
|
+
if ((atTop && e.deltaY < 0) || (atBottom && e.deltaY > 0)) {
|
|
1591
|
+
e.preventDefault();
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
{ passive: false }
|
|
1595
|
+
);
|
|
1323
1596
|
|
|
1324
1597
|
// Track scroll position for auto-scroll
|
|
1325
1598
|
panelBody.addEventListener('scroll', () => {
|
|
1326
|
-
const isAtBottom =
|
|
1599
|
+
const isAtBottom =
|
|
1600
|
+
panelBody.scrollHeight - panelBody.scrollTop - panelBody.clientHeight < 10;
|
|
1327
1601
|
autoScroll = isAtBottom;
|
|
1328
1602
|
});
|
|
1329
1603
|
|
|
1330
1604
|
// Tool selection for current step
|
|
1331
|
-
panelTools.querySelectorAll('.tool-btn').forEach(btn => {
|
|
1605
|
+
panelTools.querySelectorAll('.tool-btn').forEach((btn) => {
|
|
1332
1606
|
btn.addEventListener('click', () => {
|
|
1333
1607
|
if (!currentStepId) {
|
|
1334
1608
|
const steps = window.xyzQueue || [];
|
|
@@ -1345,8 +1619,10 @@
|
|
|
1345
1619
|
|
|
1346
1620
|
// Panel drag functionality
|
|
1347
1621
|
let isDragging = false;
|
|
1348
|
-
let dragStartX = 0,
|
|
1349
|
-
|
|
1622
|
+
let dragStartX = 0,
|
|
1623
|
+
dragStartY = 0;
|
|
1624
|
+
let panelStartX = 0,
|
|
1625
|
+
panelStartY = 0;
|
|
1350
1626
|
|
|
1351
1627
|
const header = panel.querySelector('.xyzPnl-hdr');
|
|
1352
1628
|
header.addEventListener('mousedown', (e) => {
|
|
@@ -1375,11 +1651,14 @@
|
|
|
1375
1651
|
if (isDragging) {
|
|
1376
1652
|
isDragging = false;
|
|
1377
1653
|
try {
|
|
1378
|
-
localStorage.setItem(
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1654
|
+
localStorage.setItem(
|
|
1655
|
+
'xyzPnl-pos',
|
|
1656
|
+
JSON.stringify({
|
|
1657
|
+
left: panel.style.left,
|
|
1658
|
+
top: panel.style.top,
|
|
1659
|
+
})
|
|
1660
|
+
);
|
|
1661
|
+
} catch (e) {}
|
|
1383
1662
|
}
|
|
1384
1663
|
});
|
|
1385
1664
|
|
|
@@ -1394,7 +1673,7 @@
|
|
|
1394
1673
|
panel.style.right = 'auto';
|
|
1395
1674
|
}
|
|
1396
1675
|
}
|
|
1397
|
-
} catch(e) {}
|
|
1676
|
+
} catch (e) {}
|
|
1398
1677
|
|
|
1399
1678
|
const markersContainer = document.createElement('div');
|
|
1400
1679
|
markersContainer.className = 'xyzMk';
|
|
@@ -1431,38 +1710,60 @@
|
|
|
1431
1710
|
|
|
1432
1711
|
const displaySteps = steps.slice(-20);
|
|
1433
1712
|
|
|
1434
|
-
container.innerHTML = displaySteps
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
const hasAnnotation = step.annotation && step.annotation.label;
|
|
1452
|
-
const isSelected = stepId === currentStepId;
|
|
1713
|
+
container.innerHTML = displaySteps
|
|
1714
|
+
.map((step) => {
|
|
1715
|
+
const action = step.action || 'unknown';
|
|
1716
|
+
const selector = step.selector || '';
|
|
1717
|
+
const value = step.value || '';
|
|
1718
|
+
const stepId = step.id || '';
|
|
1719
|
+
|
|
1720
|
+
let extra = '';
|
|
1721
|
+
if (action === 'trajectory' && step.points) {
|
|
1722
|
+
extra = '<div class="selector">🖱️ ' + step.points.length + ' points</div>';
|
|
1723
|
+
} else if (action === 'scroll') {
|
|
1724
|
+
extra = '<div class="selector">📜 (' + step.x + ', ' + step.y + ')</div>';
|
|
1725
|
+
} else if (action === 'resize') {
|
|
1726
|
+
extra = '<div class="selector">📐 ' + step.to.width + 'x' + step.to.height + '</div>';
|
|
1727
|
+
} else if (action === 'link_click') {
|
|
1728
|
+
extra = '<div class="selector">🔗 ' + (value || '') + '</div>';
|
|
1729
|
+
}
|
|
1453
1730
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1731
|
+
const hasAnnotation = step.annotation && step.annotation.label;
|
|
1732
|
+
const isSelected = stepId === currentStepId;
|
|
1733
|
+
|
|
1734
|
+
return (
|
|
1735
|
+
'<div class="xyzStp ' +
|
|
1736
|
+
action +
|
|
1737
|
+
(isSelected ? ' selected' : '') +
|
|
1738
|
+
'" data-step-id="' +
|
|
1739
|
+
stepId +
|
|
1740
|
+
'">' +
|
|
1741
|
+
'<div class="action">' +
|
|
1742
|
+
action.toUpperCase() +
|
|
1743
|
+
'</div>' +
|
|
1744
|
+
(selector ? '<div class="selector">' + selector + '</div>' : '') +
|
|
1745
|
+
(value && !['trajectory', 'scroll', 'resize', 'link_click'].includes(action)
|
|
1746
|
+
? '<div class="value">"' +
|
|
1747
|
+
value.slice(0, 30) +
|
|
1748
|
+
(value.length > 30 ? '...' : '') +
|
|
1749
|
+
'"</div>'
|
|
1750
|
+
: '') +
|
|
1751
|
+
extra +
|
|
1752
|
+
(hasAnnotation
|
|
1753
|
+
? '<span class="annotation">🏷️ ' + step.annotation.label + '</span>'
|
|
1754
|
+
: '') +
|
|
1755
|
+
(isSelected
|
|
1756
|
+
? '<button class="xyzDelBtn" data-step-id="' +
|
|
1757
|
+
stepId +
|
|
1758
|
+
'" title="Delete step">🗑️</button>'
|
|
1759
|
+
: '') +
|
|
1760
|
+
'</div>'
|
|
1761
|
+
);
|
|
1762
|
+
})
|
|
1763
|
+
.join('');
|
|
1463
1764
|
|
|
1464
1765
|
// Click to select step
|
|
1465
|
-
container.querySelectorAll('.xyzStp').forEach(stepEl => {
|
|
1766
|
+
container.querySelectorAll('.xyzStp').forEach((stepEl) => {
|
|
1466
1767
|
stepEl.addEventListener('click', (e) => {
|
|
1467
1768
|
if (e.target.classList.contains('xyzDelBtn')) return;
|
|
1468
1769
|
|
|
@@ -1473,7 +1774,7 @@
|
|
|
1473
1774
|
});
|
|
1474
1775
|
|
|
1475
1776
|
// Delete button click
|
|
1476
|
-
container.querySelectorAll('.xyzDelBtn').forEach(btn => {
|
|
1777
|
+
container.querySelectorAll('.xyzDelBtn').forEach((btn) => {
|
|
1477
1778
|
btn.addEventListener('click', (e) => {
|
|
1478
1779
|
e.stopPropagation();
|
|
1479
1780
|
const stepId = btn.dataset.stepId;
|
|
@@ -1500,7 +1801,7 @@
|
|
|
1500
1801
|
}
|
|
1501
1802
|
|
|
1502
1803
|
const steps = window.xyzQueue || [];
|
|
1503
|
-
const step = steps.find(s => s.id === stepId);
|
|
1804
|
+
const step = steps.find((s) => s.id === stepId);
|
|
1504
1805
|
if (!step) return;
|
|
1505
1806
|
|
|
1506
1807
|
const labels = {
|
|
@@ -1510,7 +1811,7 @@
|
|
|
1510
1811
|
pagination: 'Pagination',
|
|
1511
1812
|
login_check: 'Login',
|
|
1512
1813
|
checkpoint: 'Check',
|
|
1513
|
-
custom: 'Custom'
|
|
1814
|
+
custom: 'Custom',
|
|
1514
1815
|
};
|
|
1515
1816
|
|
|
1516
1817
|
let annotation = null;
|
|
@@ -1527,14 +1828,14 @@
|
|
|
1527
1828
|
annotation = {
|
|
1528
1829
|
type: toolType,
|
|
1529
1830
|
label: labels[toolType],
|
|
1530
|
-
waitTimeout: parseInt(timeout) || 10000
|
|
1831
|
+
waitTimeout: parseInt(timeout) || 10000,
|
|
1531
1832
|
};
|
|
1532
1833
|
} else if (toolType === 'data_container') {
|
|
1533
1834
|
const itemSelector = prompt('Enter item selector (e.g., .product-item):');
|
|
1534
1835
|
annotation = {
|
|
1535
1836
|
type: toolType,
|
|
1536
1837
|
label: labels[toolType],
|
|
1537
|
-
itemSelector: itemSelector || ''
|
|
1838
|
+
itemSelector: itemSelector || '',
|
|
1538
1839
|
};
|
|
1539
1840
|
} else {
|
|
1540
1841
|
annotation = { type: toolType, label: labels[toolType] };
|
|
@@ -1545,7 +1846,9 @@
|
|
|
1545
1846
|
if (result.success) {
|
|
1546
1847
|
if (typeof window[window.xyzBindingName || 'xyzTrack'] === 'function') {
|
|
1547
1848
|
try {
|
|
1548
|
-
window[window.xyzBindingName || 'xyzTrack'](
|
|
1849
|
+
window[window.xyzBindingName || 'xyzTrack'](
|
|
1850
|
+
JSON.stringify({ action: 'xyzUpdate', id: stepId, data: { annotation } })
|
|
1851
|
+
);
|
|
1549
1852
|
} catch (e) {}
|
|
1550
1853
|
}
|
|
1551
1854
|
|
|
@@ -1561,7 +1864,9 @@
|
|
|
1561
1864
|
if (result.success) {
|
|
1562
1865
|
if (typeof window[window.xyzBindingName || 'xyzTrack'] === 'function') {
|
|
1563
1866
|
try {
|
|
1564
|
-
window[window.xyzBindingName || 'xyzTrack'](
|
|
1867
|
+
window[window.xyzBindingName || 'xyzTrack'](
|
|
1868
|
+
JSON.stringify({ action: 'xyzDelete', id: stepId })
|
|
1869
|
+
);
|
|
1565
1870
|
} catch (e) {}
|
|
1566
1871
|
}
|
|
1567
1872
|
|
|
@@ -1572,7 +1877,7 @@
|
|
|
1572
1877
|
}
|
|
1573
1878
|
}
|
|
1574
1879
|
|
|
1575
|
-
window.addEventListener('xyzEvt', function(e) {
|
|
1880
|
+
window.addEventListener('xyzEvt', function (e) {
|
|
1576
1881
|
window.xyzQueue = e.detail;
|
|
1577
1882
|
updateUI();
|
|
1578
1883
|
});
|
|
@@ -1581,14 +1886,16 @@
|
|
|
1581
1886
|
window[window.xyzBindingName || 'xyzTrack']('');
|
|
1582
1887
|
}
|
|
1583
1888
|
|
|
1584
|
-
document.getElementById('xyzClear').addEventListener('click', function() {
|
|
1889
|
+
document.getElementById('xyzClear').addEventListener('click', function () {
|
|
1585
1890
|
window.xyzQueue = [];
|
|
1586
1891
|
document.getElementById('xyzMk').innerHTML = '';
|
|
1587
1892
|
markedElements.clear();
|
|
1588
1893
|
annotations.clear();
|
|
1589
1894
|
updateUI();
|
|
1590
1895
|
if (typeof window[window.xyzBindingName || 'xyzTrack'] === 'function') {
|
|
1591
|
-
try {
|
|
1896
|
+
try {
|
|
1897
|
+
window[window.xyzBindingName || 'xyzTrack'](JSON.stringify({ action: 'xyzClear' }));
|
|
1898
|
+
} catch (e) {}
|
|
1592
1899
|
}
|
|
1593
1900
|
});
|
|
1594
1901
|
|
|
@@ -1635,7 +1942,7 @@
|
|
|
1635
1942
|
pagination: 'Pagination',
|
|
1636
1943
|
login_check: 'Login',
|
|
1637
1944
|
checkpoint: 'Check',
|
|
1638
|
-
custom: 'Note'
|
|
1945
|
+
custom: 'Note',
|
|
1639
1946
|
};
|
|
1640
1947
|
let annotation = null;
|
|
1641
1948
|
if (type === 'custom') {
|
|
@@ -1648,7 +1955,7 @@
|
|
|
1648
1955
|
type,
|
|
1649
1956
|
label: labels[type],
|
|
1650
1957
|
selector: selector,
|
|
1651
|
-
waitTimeout: parseInt(timeout) || 10000
|
|
1958
|
+
waitTimeout: parseInt(timeout) || 10000,
|
|
1652
1959
|
};
|
|
1653
1960
|
} else if (type === 'data_container') {
|
|
1654
1961
|
const itemSelector = prompt('Enter item selector (e.g., .product-item):');
|
|
@@ -1656,7 +1963,7 @@
|
|
|
1656
1963
|
type,
|
|
1657
1964
|
label: labels[type],
|
|
1658
1965
|
selector: selector,
|
|
1659
|
-
itemSelector: itemSelector || ''
|
|
1966
|
+
itemSelector: itemSelector || '',
|
|
1660
1967
|
};
|
|
1661
1968
|
} else {
|
|
1662
1969
|
annotation = { type, label: labels[type], selector: selector };
|
|
@@ -1668,12 +1975,17 @@
|
|
|
1668
1975
|
selector: selector,
|
|
1669
1976
|
xpath: getXPath(element),
|
|
1670
1977
|
annotation: annotation,
|
|
1671
|
-
elementInfo: getElementInfo(element)
|
|
1978
|
+
elementInfo: getElementInfo(element),
|
|
1979
|
+
fallbackSelectors: getFallbackSelectors(element),
|
|
1980
|
+
elementIdentity: captureElementIdentity(element),
|
|
1672
1981
|
});
|
|
1673
1982
|
|
|
1674
1983
|
shadowBox.style.transition = 'none';
|
|
1675
1984
|
shadowBox.style.boxShadow = '0 0 20px 5px rgba(76, 175, 80, 0.8)';
|
|
1676
|
-
setTimeout(() => {
|
|
1985
|
+
setTimeout(() => {
|
|
1986
|
+
shadowBox.style.transition = 'box-shadow 0.3s ease';
|
|
1987
|
+
shadowBox.style.boxShadow = '';
|
|
1988
|
+
}, 200);
|
|
1677
1989
|
|
|
1678
1990
|
updateShadowBox(element);
|
|
1679
1991
|
}
|
|
@@ -1695,30 +2007,34 @@
|
|
|
1695
2007
|
}
|
|
1696
2008
|
}
|
|
1697
2009
|
|
|
1698
|
-
document.addEventListener(
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
2010
|
+
document.addEventListener(
|
|
2011
|
+
'mousemove',
|
|
2012
|
+
(e) => {
|
|
2013
|
+
const element = e.composedPath()[0] || e.target;
|
|
2014
|
+
mouseX = e.clientX;
|
|
2015
|
+
mouseY = e.clientY;
|
|
1702
2016
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
2017
|
+
if (element === shadowBox) return;
|
|
2018
|
+
if (isInPanel(element)) {
|
|
2019
|
+
throttledHighlight(null);
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
if (element === document.body || element === document.documentElement) {
|
|
2023
|
+
throttledHighlight(null);
|
|
2024
|
+
currentEdge = null;
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
1713
2027
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
2028
|
+
if (!shouldHighlightElement(element)) {
|
|
2029
|
+
throttledHighlight(null);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
1718
2032
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2033
|
+
currentElement = element;
|
|
2034
|
+
throttledHighlight(element);
|
|
2035
|
+
},
|
|
2036
|
+
true
|
|
2037
|
+
);
|
|
1722
2038
|
|
|
1723
2039
|
const ctx = canvas.getContext('2d');
|
|
1724
2040
|
function resizeCanvas() {
|
|
@@ -1780,11 +2096,9 @@
|
|
|
1780
2096
|
// 延迟创建面板
|
|
1781
2097
|
setTimeout(() => {
|
|
1782
2098
|
if (window.xyzStopped) {
|
|
1783
|
-
|
|
1784
2099
|
return;
|
|
1785
2100
|
}
|
|
1786
2101
|
if (!window.xyzActive) {
|
|
1787
|
-
|
|
1788
2102
|
return;
|
|
1789
2103
|
}
|
|
1790
2104
|
createRecorderOverlay();
|
|
@@ -1805,7 +2119,6 @@
|
|
|
1805
2119
|
panelObserver = new MutationObserver((mutations) => {
|
|
1806
2120
|
// 检查录制会话是否已停止
|
|
1807
2121
|
if (window.xyzStopped) {
|
|
1808
|
-
|
|
1809
2122
|
if (typeof window.xyzClose === 'function') {
|
|
1810
2123
|
window.xyzClose();
|
|
1811
2124
|
}
|
|
@@ -1818,7 +2131,6 @@
|
|
|
1818
2131
|
|
|
1819
2132
|
// 检查录制会话是否激活
|
|
1820
2133
|
if (!window.xyzActive) {
|
|
1821
|
-
|
|
1822
2134
|
return;
|
|
1823
2135
|
}
|
|
1824
2136
|
|
|
@@ -1826,7 +2138,6 @@
|
|
|
1826
2138
|
const panel = document.getElementById('xyzPnl');
|
|
1827
2139
|
const style = document.getElementById('xyzSt');
|
|
1828
2140
|
if (document.body && (!panel || !style)) {
|
|
1829
|
-
|
|
1830
2141
|
createRecorderOverlay();
|
|
1831
2142
|
}
|
|
1832
2143
|
});
|
|
@@ -1834,13 +2145,100 @@
|
|
|
1834
2145
|
// 启动观察器
|
|
1835
2146
|
panelObserver.observe(document.body, {
|
|
1836
2147
|
childList: true,
|
|
1837
|
-
subtree: false
|
|
2148
|
+
subtree: false,
|
|
1838
2149
|
});
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
2150
|
}
|
|
1842
2151
|
|
|
1843
2152
|
// 启动 MutationObserver
|
|
1844
2153
|
startPanelObserver();
|
|
1845
2154
|
}
|
|
2155
|
+
|
|
2156
|
+
// === SPA URL Change Detection (Enhancement 3) ===
|
|
2157
|
+
(function setupURLChangeDetection() {
|
|
2158
|
+
function pushSignal(data) {
|
|
2159
|
+
if (!window.xyzActive) return;
|
|
2160
|
+
const step = {
|
|
2161
|
+
id: 'env-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
|
|
2162
|
+
timestamp: Date.now(),
|
|
2163
|
+
action: 'environment_signal',
|
|
2164
|
+
signalType: 'url_change',
|
|
2165
|
+
data: {
|
|
2166
|
+
from: data.from || '',
|
|
2167
|
+
to: data.to || window.location.href,
|
|
2168
|
+
},
|
|
2169
|
+
url: window.location.href,
|
|
2170
|
+
};
|
|
2171
|
+
syncStepDirect(step);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
const origPushState = history.pushState;
|
|
2175
|
+
if (origPushState) {
|
|
2176
|
+
history.pushState = function () {
|
|
2177
|
+
const from = window.location.href;
|
|
2178
|
+
const result = origPushState.apply(this, arguments);
|
|
2179
|
+
pushSignal({ from: from, to: window.location.href });
|
|
2180
|
+
return result;
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
const origReplaceState = history.replaceState;
|
|
2185
|
+
if (origReplaceState) {
|
|
2186
|
+
history.replaceState = function () {
|
|
2187
|
+
const from = window.location.href;
|
|
2188
|
+
const result = origReplaceState.apply(this, arguments);
|
|
2189
|
+
pushSignal({ from: from, to: window.location.href });
|
|
2190
|
+
return result;
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
window.addEventListener('popstate', function () {
|
|
2195
|
+
pushSignal({ to: window.location.href });
|
|
2196
|
+
});
|
|
2197
|
+
})();
|
|
2198
|
+
|
|
2199
|
+
// === DOM Stability Detection (Enhancement 4) ===
|
|
2200
|
+
(function setupDOMStabilityDetection() {
|
|
2201
|
+
let mutationTimer = null;
|
|
2202
|
+
let lastMutationTime = 0;
|
|
2203
|
+
|
|
2204
|
+
function computeContentHash() {
|
|
2205
|
+
var main =
|
|
2206
|
+
document.querySelector('main, [role="main"], #content, .content, article') || document.body;
|
|
2207
|
+
if (!main) return '0';
|
|
2208
|
+
var html = main.innerHTML.substring(0, 10240);
|
|
2209
|
+
var hash = 0;
|
|
2210
|
+
for (var i = 0; i < html.length; i++) {
|
|
2211
|
+
hash = ((hash << 5) - hash + html.charCodeAt(i)) | 0;
|
|
2212
|
+
}
|
|
2213
|
+
return hash.toString(36);
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
var observer = new MutationObserver(function () {
|
|
2217
|
+
lastMutationTime = Date.now();
|
|
2218
|
+
clearTimeout(mutationTimer);
|
|
2219
|
+
mutationTimer = setTimeout(function () {
|
|
2220
|
+
if (!window.xyzActive) return;
|
|
2221
|
+
var step = {
|
|
2222
|
+
id: 'env-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5),
|
|
2223
|
+
timestamp: Date.now(),
|
|
2224
|
+
action: 'environment_signal',
|
|
2225
|
+
signalType: 'dom_stable',
|
|
2226
|
+
data: {
|
|
2227
|
+
stableAfterMs: Date.now() - lastMutationTime,
|
|
2228
|
+
contentHash: computeContentHash(),
|
|
2229
|
+
},
|
|
2230
|
+
url: window.location.href,
|
|
2231
|
+
};
|
|
2232
|
+
syncStepDirect(step);
|
|
2233
|
+
}, 300);
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
if (document.body) {
|
|
2237
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2238
|
+
} else {
|
|
2239
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
2240
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
})();
|
|
1846
2244
|
})();
|