@xbrowser/cli 1.0.0 → 1.0.3
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 +17 -26
- package/dist/{browser-DSVV4GHS.js → browser-5CTOA2WS.js} +4 -3
- package/dist/{browser-53KUFEEM.js → browser-ITLZZDHJ.js} +5 -5
- package/dist/{browser-GURRY444.js → browser-IUJXXNBT.js} +6 -3
- package/dist/{cdp-driver-MNPR3HZH.js → cdp-driver-4X3DK6PS.js} +339 -59
- package/dist/{cdp-driver-SSXUGXP6.js → cdp-driver-D6WMSMWX.js} +4 -3
- package/dist/chunk-2SVQTI2O.js +2794 -0
- package/dist/{chunk-IDVD44ED.js → chunk-6WOSXSCQ.js} +23 -7
- package/dist/{chunk-ZZ2TFWIV.js → chunk-ABXMBNQ6.js} +1 -1
- package/dist/{chunk-2MFXKN32.js → chunk-ACFE6PKF.js} +1013 -119
- package/dist/chunk-AMI64BSD.js +268 -0
- package/dist/{chunk-E4O5ZU3H.js → chunk-DKWR54XQ.js} +412 -98
- package/dist/{chunk-DTJRVA76.js → chunk-ETCO4SNK.js} +2 -2
- package/dist/chunk-GDKLH7ZY.js +8 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/{chunk-2BQZIT3S.js → chunk-LRBSUKUZ.js} +85 -2497
- package/dist/{chunk-ITKPSIP7.js → chunk-MDAPTB7C.js} +6 -25
- package/dist/{chunk-42RPMJ76.js → chunk-N2JFPWMI.js} +342 -60
- package/dist/chunk-OZKD3W4X.js +417 -0
- package/dist/{chunk-T4J4C2NZ.js → chunk-TNEN6VQ2.js} +17 -4
- package/dist/{chunk-YKOHDEFV.js → chunk-TWWOIJM7.js} +74 -38
- package/dist/chunk-WJRE55TN.js +83 -0
- package/dist/cli.js +1558 -1122
- package/dist/{convert-EGFYNICZ.js → convert-LB3GJTLR.js} +3 -3
- package/dist/{convert-EKQVHKB4.js → convert-R3XXYKC6.js} +2 -2
- package/dist/{daemon-client-YAVQ343A.js → daemon-client-3JOKX2L2.js} +3 -2
- package/dist/{daemon-client-3VM7VU7O.js → daemon-client-DIEHGP5B.js} +28 -74
- package/dist/daemon-main.js +2296 -1722
- package/dist/{extract-JUOQQX4V.js → extract-2ZFW2MX7.js} +1 -1
- package/dist/{extract-L2IW3IUB.js → extract-BSYBM4MR.js} +1 -1
- package/dist/{filter-HC4RA7JY.js → filter-KCFO4RSV.js} +1 -1
- package/dist/{filter-VID2GGZ7.js → filter-T7DSZ2X7.js} +1 -1
- package/dist/{human-interaction-W753RVJB.js → human-interaction-UKAS5ZXV.js} +2 -2
- package/dist/index.d.ts +166 -109
- package/dist/index.js +2668 -1742
- package/dist/launcher-L2JNDB2H.js +20 -0
- package/dist/{launcher-KA7J32K5.js → launcher-OZXJQPNG.js} +1 -1
- package/dist/{network-store-66A2RATI.js → network-store-XGZ25FFC.js} +1 -1
- package/dist/{network-store-BN6QEZ7R.js → network-store-YVDNUREI.js} +1 -1
- package/dist/{parse-action-dsl-T3DYC33D.js → parse-action-dsl-UM333TL2.js} +1 -1
- package/dist/{proxy-WKGUCH2C.js → proxy-C6CK3UH5.js} +2 -2
- package/dist/session-recorder-RTDGURIJ.js +8 -0
- package/dist/session-recorder-YI7YYM36.js +7 -0
- package/dist/session-replayer-MY27H4DX.js +276 -0
- package/dist/site-knowledge-SYC6VCDB.js +23 -0
- package/package.json +5 -4
- package/dist/screenshot-CWAWMXVA.js +0 -28
- package/dist/session-recorder-MA75PKTQ.js +0 -7
|
@@ -1,14 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
init_site_knowledge,
|
|
3
|
+
site_knowledge_exports,
|
|
4
|
+
updateSiteKnowledge
|
|
5
|
+
} from "./chunk-OZKD3W4X.js";
|
|
6
|
+
import {
|
|
7
|
+
__require,
|
|
8
|
+
__toCommonJS
|
|
9
|
+
} from "./chunk-KFQGP6VL.js";
|
|
10
|
+
|
|
1
11
|
// src/recorder/session-recorder.ts
|
|
2
12
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "fs";
|
|
3
13
|
import { join } from "path";
|
|
4
14
|
import { homedir } from "os";
|
|
15
|
+
|
|
16
|
+
// src/recorder/selector-utils.ts
|
|
17
|
+
function getSelectorGeneratorScript() {
|
|
18
|
+
return `
|
|
19
|
+
(function() {
|
|
20
|
+
if (window.__xb_generateSelector) return;
|
|
21
|
+
|
|
22
|
+
var IGNORE_CLASS = [
|
|
23
|
+
/^css-[a-zA-Z0-9]+$/,
|
|
24
|
+
/^sc-[a-zA-Z0-9]+$/,
|
|
25
|
+
/^emotion-\\d+$/,
|
|
26
|
+
/^_[a-f0-9]{4,}$/,
|
|
27
|
+
/^[a-f0-9]{6,}$/,
|
|
28
|
+
/^styled-[a-zA-Z0-9]+/,
|
|
29
|
+
/^__[a-zA-Z0-9]{4,}$/,
|
|
30
|
+
/^makeStyles-/,
|
|
31
|
+
/^MuiPrivate/,
|
|
32
|
+
/^jss\\d+$/,
|
|
33
|
+
/^ant-[a-z]+$/,
|
|
34
|
+
/^semi-[a-z]{4,}/
|
|
35
|
+
];
|
|
36
|
+
var STABLE_ATTRS = ['data-testid', 'data-test-id', 'data-cy', 'data-qa', 'data-el'];
|
|
37
|
+
var MAX_DEPTH = 5;
|
|
38
|
+
|
|
39
|
+
function isStableClass(cls) {
|
|
40
|
+
if (!cls || cls.length <= 1) return false;
|
|
41
|
+
for (var i = 0; i < IGNORE_CLASS.length; i++) {
|
|
42
|
+
if (IGNORE_CLASS[i].test(cls)) return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isUnique(root, sel) {
|
|
48
|
+
try { return root.querySelectorAll(sel).length === 1; } catch(e) { return false; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function esc(s) { return CSS.escape(s); }
|
|
52
|
+
|
|
53
|
+
window.__xb_generateSelector = function(el, root) {
|
|
54
|
+
root = root || document;
|
|
55
|
+
if (!el || !el.tagName) return null;
|
|
56
|
+
if (el === root || el === document.documentElement) return { selector: 'html', strategy: 'root', confidence: 'high' };
|
|
57
|
+
|
|
58
|
+
var tag = el.tagName.toLowerCase();
|
|
59
|
+
|
|
60
|
+
// #id
|
|
61
|
+
if (el.id) {
|
|
62
|
+
var s = '#' + esc(el.id);
|
|
63
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'id', confidence: 'high' };
|
|
64
|
+
s = tag + s;
|
|
65
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'id+tag', confidence: 'high' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// [data-testid]
|
|
69
|
+
for (var si = 0; si < STABLE_ATTRS.length; si++) {
|
|
70
|
+
var v = el.getAttribute(STABLE_ATTRS[si]);
|
|
71
|
+
if (v) {
|
|
72
|
+
var s = '[' + STABLE_ATTRS[si] + '="' + esc(v) + '"]';
|
|
73
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'testid', confidence: 'high' };
|
|
74
|
+
s = tag + s;
|
|
75
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'testid+tag', confidence: 'high' };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// [name]
|
|
80
|
+
var name = el.getAttribute('name');
|
|
81
|
+
if (name) {
|
|
82
|
+
var s = tag + '[name="' + esc(name) + '"]';
|
|
83
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'name', confidence: 'high' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// [aria-label]
|
|
87
|
+
var aria = el.getAttribute('aria-label');
|
|
88
|
+
if (aria) {
|
|
89
|
+
var s = '[aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
90
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label', confidence: 'high' };
|
|
91
|
+
s = tag + s;
|
|
92
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label+tag', confidence: 'high' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// [placeholder]
|
|
96
|
+
var ph = el.getAttribute('placeholder');
|
|
97
|
+
if (ph) {
|
|
98
|
+
var s = tag + '[placeholder="' + esc(ph.substring(0, 60)) + '"]';
|
|
99
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'placeholder', confidence: 'high' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// [alt]
|
|
103
|
+
var alt = el.getAttribute('alt');
|
|
104
|
+
if (alt && tag === 'img') {
|
|
105
|
+
var s = 'img[alt="' + esc(alt.substring(0, 60)) + '"]';
|
|
106
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'alt', confidence: 'high' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// [title]
|
|
110
|
+
var title = el.getAttribute('title');
|
|
111
|
+
if (title) {
|
|
112
|
+
var s = tag + '[title="' + esc(title.substring(0, 60)) + '"]';
|
|
113
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'title', confidence: 'high' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Unique attribute (skip URL-like, long)
|
|
117
|
+
var skipAttr = {class:1,style:1,id:1,name:1,'aria-label':1,placeholder:1,alt:1,title:1,role:1,src:1,href:1,action:1,'data-src':1,'data-href':1};
|
|
118
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
119
|
+
var a = el.attributes[ai];
|
|
120
|
+
if (skipAttr[a.name] || a.name.startsWith('data-') || a.name.startsWith('aria-')) continue;
|
|
121
|
+
if (a.value && a.value.length > 2 && a.value.length <= 60) {
|
|
122
|
+
var s = tag + '[' + a.name + '="' + esc(a.value) + '"]';
|
|
123
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'attribute', confidence: 'medium' };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Class combos
|
|
128
|
+
var rawCls = (typeof el.className === 'string' ? el.className : '').trim().split(/\\s+/);
|
|
129
|
+
var cls = rawCls.filter(isStableClass);
|
|
130
|
+
if (cls.length > 0) {
|
|
131
|
+
cls.sort(function(a, b) {
|
|
132
|
+
return root.querySelectorAll('.' + esc(a)).length - root.querySelectorAll('.' + esc(b)).length;
|
|
133
|
+
});
|
|
134
|
+
// single class
|
|
135
|
+
for (var i = 0; i < cls.length; i++) {
|
|
136
|
+
var s = '.' + esc(cls[i]);
|
|
137
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'class', confidence: 'medium' };
|
|
138
|
+
}
|
|
139
|
+
// tag + class
|
|
140
|
+
for (var i = 0; i < cls.length; i++) {
|
|
141
|
+
var s = tag + '.' + esc(cls[i]);
|
|
142
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'tag+class', confidence: 'medium' };
|
|
143
|
+
}
|
|
144
|
+
// 2-class combo
|
|
145
|
+
for (var i = 0; i < cls.length && i < 5; i++) {
|
|
146
|
+
for (var j = i+1; j < cls.length && j < 5; j++) {
|
|
147
|
+
var s = '.' + esc(cls[i]) + '.' + esc(cls[j]);
|
|
148
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'class-combo', confidence: 'medium' };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// tag + 2-class combo
|
|
152
|
+
for (var i = 0; i < cls.length && i < 4; i++) {
|
|
153
|
+
for (var j = i+1; j < cls.length && j < 4; j++) {
|
|
154
|
+
var s = tag + '.' + esc(cls[i]) + '.' + esc(cls[j]);
|
|
155
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'tag+class-combo', confidence: 'medium' };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Parent scope
|
|
161
|
+
var parent = el.parentElement;
|
|
162
|
+
if (parent && parent !== root) {
|
|
163
|
+
if (parent.id) {
|
|
164
|
+
var s = '#' + esc(parent.id) + ' > ' + tag;
|
|
165
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'parent-scope', confidence: 'medium' };
|
|
166
|
+
}
|
|
167
|
+
var pCls = (typeof parent.className === 'string' ? parent.className : '').trim().split(/\\s+/).filter(isStableClass);
|
|
168
|
+
for (var i = 0; i < pCls.length; i++) {
|
|
169
|
+
var s = '.' + esc(pCls[i]) + ' > ' + tag;
|
|
170
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'parent-class-scope', confidence: 'medium' };
|
|
171
|
+
}
|
|
172
|
+
if (cls.length > 0) {
|
|
173
|
+
for (var i = 0; i < Math.min(cls.length, 3); i++) {
|
|
174
|
+
var s = parent.tagName.toLowerCase() + ' > ' + tag + '.' + esc(cls[i]);
|
|
175
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'parent>tag.class', confidence: 'medium' };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// :nth-of-type chain \u2014 walk all the way up, then find shortest unique suffix
|
|
181
|
+
var path = [];
|
|
182
|
+
var cur = el;
|
|
183
|
+
while (cur && cur !== root && cur !== document.documentElement) {
|
|
184
|
+
var p = cur.parentElement;
|
|
185
|
+
if (!p || p === root) break;
|
|
186
|
+
var t = cur.tagName.toLowerCase();
|
|
187
|
+
var same = [];
|
|
188
|
+
for (var k = 0; k < p.children.length; k++) {
|
|
189
|
+
if (p.children[k].tagName === cur.tagName) same.push(p.children[k]);
|
|
190
|
+
}
|
|
191
|
+
var nth = same.length === 1 ? null : (same.indexOf(cur) + 1);
|
|
192
|
+
path.unshift({tag: t, nth: nth});
|
|
193
|
+
if (cur !== el && cur.id) {
|
|
194
|
+
path.unshift({tag: '#' + esc(cur.id), nth: null});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
cur = p;
|
|
198
|
+
}
|
|
199
|
+
// Try suffixes from shortest to longest
|
|
200
|
+
for (var d = 1; d <= path.length; d++) {
|
|
201
|
+
var parts = path.slice(-d).map(function(p) {
|
|
202
|
+
return p.nth !== null ? p.tag + ':nth-of-type(' + p.nth + ')' : p.tag;
|
|
203
|
+
});
|
|
204
|
+
var sel = parts.join(' > ');
|
|
205
|
+
if (isUnique(root, sel)) return { selector: sel, strategy: 'nth-of-type', confidence: 'low' };
|
|
206
|
+
}
|
|
207
|
+
// Fallback
|
|
208
|
+
if (path.length > 0) {
|
|
209
|
+
var parts = path.map(function(p) {
|
|
210
|
+
return p.nth !== null ? p.tag + ':nth-of-type(' + p.nth + ')' : p.tag;
|
|
211
|
+
});
|
|
212
|
+
return { selector: parts.join(' > '), strategy: 'nth-of-type', confidence: 'low' };
|
|
213
|
+
}
|
|
214
|
+
return { selector: tag, strategy: 'tag-only', confidence: 'low' };
|
|
215
|
+
};
|
|
216
|
+
})();
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/recorder/session-recorder.ts
|
|
221
|
+
init_site_knowledge();
|
|
5
222
|
var ACTION_SIGNAL_SCRIPT = `
|
|
6
223
|
(function() {
|
|
7
224
|
if (window.__xb_action_signal) return;
|
|
8
225
|
window.__xb_action_signal = true;
|
|
9
226
|
window.__xb_pending_actions = [];
|
|
10
227
|
|
|
11
|
-
// --- Unique short selector generator ---
|
|
228
|
+
// --- Unique short selector generator (delegates to 13-strategy selector-utils) ---
|
|
12
229
|
function uniqueSelector(el) {
|
|
13
230
|
if (!el || !el.tagName) return null;
|
|
14
231
|
var doc = el.ownerDocument || document;
|
|
@@ -17,6 +234,14 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
17
234
|
try { return doc.querySelectorAll(sel).length === 1; } catch(e) { return false; }
|
|
18
235
|
}
|
|
19
236
|
|
|
237
|
+
// Prefer window.__xb_generateSelector from selector-utils (13 strategies)
|
|
238
|
+
if (typeof window.__xb_generateSelector === 'function') {
|
|
239
|
+
try {
|
|
240
|
+
var result = window.__xb_generateSelector(el, doc);
|
|
241
|
+
if (result && result.selector) return result.selector;
|
|
242
|
+
} catch(e) { /* fallback to local logic */ }
|
|
243
|
+
}
|
|
244
|
+
|
|
20
245
|
// 1. #id (shortest, globally unique)
|
|
21
246
|
if (el.id) {
|
|
22
247
|
var idSel = '#' + CSS.escape(el.id);
|
|
@@ -103,10 +328,92 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
103
328
|
? (el.value || el.getAttribute('placeholder') || '').trim().substring(0, 40)
|
|
104
329
|
: (el.textContent || '').trim().substring(0, 40);
|
|
105
330
|
if (tag === 'a' && el.getAttribute('href')) displayText = el.textContent.trim().substring(0, 40);
|
|
331
|
+
|
|
332
|
+
// Prefer window.__xb_generateSelector (13 strategies) \u2014 also extracts strategy + confidence
|
|
333
|
+
var selector, strategy, confidence;
|
|
334
|
+
if (typeof window.__xb_generateSelector === 'function') {
|
|
335
|
+
try {
|
|
336
|
+
var result = window.__xb_generateSelector(el, el.ownerDocument || document);
|
|
337
|
+
if (result && result.selector) {
|
|
338
|
+
selector = result.selector;
|
|
339
|
+
strategy = result.strategy;
|
|
340
|
+
confidence = result.confidence;
|
|
341
|
+
}
|
|
342
|
+
} catch(e) { /* fall through to local */ }
|
|
343
|
+
}
|
|
344
|
+
if (!selector) {
|
|
345
|
+
selector = uniqueSelector(el);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// For low-confidence selectors (nth-of-type), generate a text-based fallback
|
|
349
|
+
// when the element has short, unique text (e.g. menu items "\u5220\u9664", "\u786E\u8BA4")
|
|
350
|
+
var textFallback;
|
|
351
|
+
var popupContext;
|
|
352
|
+
|
|
353
|
+
// Check if element is inside a popup/menu first (needed for scoped text uniqueness)
|
|
354
|
+
var popupEl;
|
|
355
|
+
try {
|
|
356
|
+
popupEl = el.closest('[role="menu"], [role="listbox"], [role="dialog"], [role="tooltip"], [role="list"], [class*="popover"], [class*="popup"], [class*="dropdown"], [class*="menu"], [class*="modal"], [id*="menu"], [id*="dropdown"], [id*="popup"], [id*="modal"]');
|
|
357
|
+
} catch(e) {}
|
|
358
|
+
|
|
359
|
+
if (confidence === 'low') {
|
|
360
|
+
var rawText = (el.textContent || '').trim();
|
|
361
|
+
if (rawText && rawText.length >= 1 && rawText.length <= 30 && el.children.length === 0) {
|
|
362
|
+
try {
|
|
363
|
+
var doc = el.ownerDocument || document;
|
|
364
|
+
var escapedText = rawText.replace(/'/g, "\\'");
|
|
365
|
+
|
|
366
|
+
// If inside popup, check uniqueness WITHIN popup only
|
|
367
|
+
var count;
|
|
368
|
+
if (popupEl && popupEl !== el) {
|
|
369
|
+
var popupCount = doc.evaluate(
|
|
370
|
+
"count(.//*[normalize-space(text())='" + escapedText + "'])",
|
|
371
|
+
popupEl, null, XPathResult.NUMBER_TYPE, null
|
|
372
|
+
);
|
|
373
|
+
count = popupCount.numberValue;
|
|
374
|
+
} else {
|
|
375
|
+
// Check global uniqueness
|
|
376
|
+
var globalCount = doc.evaluate(
|
|
377
|
+
"count(//*[normalize-space(text())='" + escapedText + "'])",
|
|
378
|
+
doc, null, XPathResult.NUMBER_TYPE, null
|
|
379
|
+
);
|
|
380
|
+
count = globalCount.numberValue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (count === 1) {
|
|
384
|
+
textFallback = {
|
|
385
|
+
type: popupEl && popupEl !== el ? 'popup-text' : 'text',
|
|
386
|
+
value: rawText,
|
|
387
|
+
selector: popupEl && popupEl !== el ? 'popup-text=' + rawText : 'text=' + rawText,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
} catch(e) { /* xpath not available or error */ }
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Generate popup context info
|
|
395
|
+
if (popupEl && popupEl !== el) {
|
|
396
|
+
try {
|
|
397
|
+
var popupResult = window.__xb_generateSelector
|
|
398
|
+
? window.__xb_generateSelector(popupEl, el.ownerDocument || document)
|
|
399
|
+
: null;
|
|
400
|
+
if (popupResult && popupResult.selector) {
|
|
401
|
+
popupContext = {
|
|
402
|
+
containerSelector: popupResult.selector,
|
|
403
|
+
containerText: (popupEl.textContent || '').trim().substring(0, 50),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
} catch(e) { /* skip */ }
|
|
407
|
+
}
|
|
408
|
+
|
|
106
409
|
return {
|
|
107
410
|
tag: tag,
|
|
108
|
-
selector:
|
|
411
|
+
selector: selector,
|
|
109
412
|
text: displayText,
|
|
413
|
+
strategy: strategy,
|
|
414
|
+
confidence: confidence,
|
|
415
|
+
textFallback: textFallback,
|
|
416
|
+
popup: popupContext,
|
|
110
417
|
role: el.getAttribute('role') || undefined,
|
|
111
418
|
type: el.getAttribute('type') || undefined,
|
|
112
419
|
placeholder: el.getAttribute('placeholder') || undefined,
|
|
@@ -115,6 +422,107 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
115
422
|
};
|
|
116
423
|
}
|
|
117
424
|
|
|
425
|
+
// \u2500\u2500 Mouse trajectory capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
426
|
+
// Continuously samples mousemove (every ~60ms).
|
|
427
|
+
// When a meaningful action fires, we snapshot the buffer,
|
|
428
|
+
// simplify it (Douglas-Peucker), and attach as trajectory.
|
|
429
|
+
var __xb_traj_buffer = []; // raw samples: {x, y, t}
|
|
430
|
+
var __xb_traj_last_action = null; // {x, y, t} of previous action
|
|
431
|
+
|
|
432
|
+
document.addEventListener('mousemove', function(e) {
|
|
433
|
+
var now = Date.now();
|
|
434
|
+
// Sample at ~60ms intervals
|
|
435
|
+
if (__xb_traj_buffer.length > 0) {
|
|
436
|
+
var last = __xb_traj_buffer[__xb_traj_buffer.length - 1];
|
|
437
|
+
if (now - last.t < 60) return;
|
|
438
|
+
}
|
|
439
|
+
__xb_traj_buffer.push({ x: e.clientX, y: e.clientY, t: now });
|
|
440
|
+
// Cap buffer at 200 points (~12 seconds at 60ms)
|
|
441
|
+
if (__xb_traj_buffer.length > 200) {
|
|
442
|
+
__xb_traj_buffer = __xb_traj_buffer.slice(-150);
|
|
443
|
+
}
|
|
444
|
+
}, true);
|
|
445
|
+
|
|
446
|
+
// Douglas-Peucker simplification: keep only points that define the path shape
|
|
447
|
+
function dpSimplify(pts, epsilon) {
|
|
448
|
+
if (pts.length <= 2) return pts;
|
|
449
|
+
var maxDist = 0, maxIdx = 0;
|
|
450
|
+
var first = pts[0], last = pts[pts.length - 1];
|
|
451
|
+
for (var i = 1; i < pts.length - 1; i++) {
|
|
452
|
+
var d = pointLineDistance(pts[i], first, last);
|
|
453
|
+
if (d > maxDist) { maxDist = d; maxIdx = i; }
|
|
454
|
+
}
|
|
455
|
+
if (maxDist > epsilon) {
|
|
456
|
+
var left = dpSimplify(pts.slice(0, maxIdx + 1), epsilon);
|
|
457
|
+
var right = dpSimplify(pts.slice(maxIdx), epsilon);
|
|
458
|
+
return left.slice(0, -1).concat(right);
|
|
459
|
+
}
|
|
460
|
+
return [first, last];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function pointLineDistance(p, a, b) {
|
|
464
|
+
var dx = b.x - a.x, dy = b.y - a.y;
|
|
465
|
+
var lenSq = dx * dx + dy * dy;
|
|
466
|
+
if (lenSq === 0) return Math.sqrt((p.x - a.x) * (p.x - a.x) + (p.y - a.y) * (p.y - a.y));
|
|
467
|
+
var t = Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / lenSq));
|
|
468
|
+
var projX = a.x + t * dx, projY = a.y + t * dy;
|
|
469
|
+
return Math.sqrt((p.x - projX) * (p.x - projX) + (p.y - projY) * (p.y - projY));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Extract trajectory from buffer ending at (toX, toY, toT).
|
|
473
|
+
// Returns null if no meaningful path.
|
|
474
|
+
function extractTrajectory(toX, toY) {
|
|
475
|
+
var now = Date.now();
|
|
476
|
+
// Add current position as final point
|
|
477
|
+
var raw = __xb_traj_buffer.slice();
|
|
478
|
+
raw.push({ x: toX, y: toY, t: now });
|
|
479
|
+
|
|
480
|
+
// Trim to last 5 seconds
|
|
481
|
+
var cutoff = now - 5000;
|
|
482
|
+
while (raw.length > 0 && raw[0].t < cutoff) raw.shift();
|
|
483
|
+
if (raw.length < 2) { __xb_traj_buffer = []; return null; }
|
|
484
|
+
|
|
485
|
+
// If we know the previous action position, prepend it as start
|
|
486
|
+
if (__xb_traj_last_action) {
|
|
487
|
+
// Trim raw points before the last action
|
|
488
|
+
while (raw.length > 1 && raw[0].t < __xb_traj_last_action.t) raw.shift();
|
|
489
|
+
raw.unshift({ x: __xb_traj_last_action.x, y: __xb_traj_last_action.y, t: __xb_traj_last_action.t });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Simplify (epsilon=3px keeps shape but removes jitter)
|
|
493
|
+
var simplified = dpSimplify(raw, 3);
|
|
494
|
+
if (simplified.length < 2) { __xb_traj_buffer = []; return null; }
|
|
495
|
+
|
|
496
|
+
// Build result with delta times
|
|
497
|
+
var totalDist = 0;
|
|
498
|
+
var points = [];
|
|
499
|
+
for (var i = 0; i < simplified.length; i++) {
|
|
500
|
+
var dt = i === 0 ? 0 : simplified[i].t - simplified[i - 1].t;
|
|
501
|
+
if (i > 0) {
|
|
502
|
+
var ddx = simplified[i].x - simplified[i - 1].x;
|
|
503
|
+
var ddy = simplified[i].y - simplified[i - 1].y;
|
|
504
|
+
totalDist += Math.sqrt(ddx * ddx + ddy * ddy);
|
|
505
|
+
}
|
|
506
|
+
points.push({ x: simplified[i].x, y: simplified[i].y, dt: dt });
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
var duration = simplified[simplified.length - 1].t - simplified[0].t;
|
|
510
|
+
|
|
511
|
+
// Only return if meaningful movement (>5px total distance)
|
|
512
|
+
if (totalDist < 5) { __xb_traj_buffer = []; return null; }
|
|
513
|
+
|
|
514
|
+
// Reset buffer
|
|
515
|
+
__xb_traj_buffer = [];
|
|
516
|
+
__xb_traj_last_action = { x: toX, y: toY, t: now };
|
|
517
|
+
|
|
518
|
+
return { points: points, distance: Math.round(totalDist), duration: duration };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// \u2500\u2500 End trajectory capture \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
522
|
+
|
|
523
|
+
// Expose describe() for CDP command element metadata extraction
|
|
524
|
+
window.__xb_describe = describe;
|
|
525
|
+
|
|
118
526
|
function isMeaningful(el) {
|
|
119
527
|
if (!el || !el.tagName) return false;
|
|
120
528
|
var tag = el.tagName.toLowerCase();
|
|
@@ -153,6 +561,12 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
153
561
|
}
|
|
154
562
|
|
|
155
563
|
function pushAction(type, detail) {
|
|
564
|
+
// Attach mouse trajectory for actions with coordinates
|
|
565
|
+
if (detail && detail.x != null && detail.y != null) {
|
|
566
|
+
var traj = extractTrajectory(detail.x, detail.y);
|
|
567
|
+
if (traj) detail.trajectory = traj;
|
|
568
|
+
}
|
|
569
|
+
|
|
156
570
|
if (type === 'input') {
|
|
157
571
|
if (__xb_input_timer) clearTimeout(__xb_input_timer);
|
|
158
572
|
__xb_input_pending = {
|
|
@@ -331,12 +745,66 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
331
745
|
var tag = target.tagName && target.tagName.toLowerCase();
|
|
332
746
|
if (tag === 'select') {
|
|
333
747
|
pushAction('change', { element: describe(target), value: (target.value || '').substring(0, 100) });
|
|
748
|
+
} else if (tag === 'input' && target.type === 'file') {
|
|
749
|
+
var files = target.files;
|
|
750
|
+
var fileNames = [];
|
|
751
|
+
for (var i = 0; i < files.length; i++) {
|
|
752
|
+
fileNames.push(files[i].name);
|
|
753
|
+
}
|
|
754
|
+
// Read file contents asynchronously, then push action
|
|
755
|
+
var readers = [];
|
|
756
|
+
for (var i = 0; i < files.length; i++) {
|
|
757
|
+
readers.push(new Promise(function(resolve) {
|
|
758
|
+
var reader = new FileReader();
|
|
759
|
+
reader.onload = function() { resolve(reader.result); };
|
|
760
|
+
reader.onerror = function() { resolve(null); };
|
|
761
|
+
reader.readAsDataURL(files[i]);
|
|
762
|
+
}));
|
|
763
|
+
}
|
|
764
|
+
Promise.all(readers).then(function(contents) {
|
|
765
|
+
var fileData = [];
|
|
766
|
+
for (var i = 0; i < files.length; i++) {
|
|
767
|
+
fileData.push({
|
|
768
|
+
name: files[i].name,
|
|
769
|
+
type: files[i].type,
|
|
770
|
+
size: files[i].size,
|
|
771
|
+
dataUrl: contents[i],
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
pushAction('filechooser', {
|
|
775
|
+
element: describe(target),
|
|
776
|
+
value: fileNames.join(', '),
|
|
777
|
+
files: {
|
|
778
|
+
names: fileNames,
|
|
779
|
+
count: files.length,
|
|
780
|
+
isMultiple: target.multiple,
|
|
781
|
+
fileData: fileData,
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
});
|
|
334
785
|
}
|
|
335
786
|
}, true);
|
|
336
787
|
|
|
337
788
|
document.addEventListener('keydown', function(e) {
|
|
789
|
+
// Special keys always recorded
|
|
338
790
|
if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape' || e.key.startsWith('Arrow')) {
|
|
339
791
|
pushAction('keydown', { key: e.key, element: describe(actualTarget(e)) });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
// Editing keys
|
|
795
|
+
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
796
|
+
pushAction('keydown', { key: e.key, element: describe(actualTarget(e)) });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
// Modifier combinations (Ctrl/Cmd/Alt + key)
|
|
800
|
+
if (e.ctrlKey || e.metaKey || e.altKey) {
|
|
801
|
+
var combo = '';
|
|
802
|
+
if (e.ctrlKey) combo += 'Ctrl+';
|
|
803
|
+
if (e.metaKey) combo += 'Meta+';
|
|
804
|
+
if (e.altKey) combo += 'Alt+';
|
|
805
|
+
if (e.shiftKey) combo += 'Shift+';
|
|
806
|
+
combo += e.key;
|
|
807
|
+
pushAction('keydown', { key: combo, element: describe(actualTarget(e)) });
|
|
340
808
|
}
|
|
341
809
|
}, true);
|
|
342
810
|
|
|
@@ -350,6 +818,155 @@ var ACTION_SIGNAL_SCRIPT = `
|
|
|
350
818
|
pushAction('scroll', { scrollX: window.scrollX, scrollY: window.scrollY });
|
|
351
819
|
}
|
|
352
820
|
}, true);
|
|
821
|
+
|
|
822
|
+
// \u2500\u2500 Double click \u2500\u2500
|
|
823
|
+
document.addEventListener('dblclick', function(e) {
|
|
824
|
+
pushAction('dblclick', {
|
|
825
|
+
element: describe(resolveMeaningful(e)),
|
|
826
|
+
x: e.clientX,
|
|
827
|
+
y: e.clientY,
|
|
828
|
+
});
|
|
829
|
+
}, true);
|
|
830
|
+
|
|
831
|
+
// \u2500\u2500 Right click (context menu) \u2500\u2500
|
|
832
|
+
document.addEventListener('contextmenu', function(e) {
|
|
833
|
+
pushAction('contextmenu', {
|
|
834
|
+
element: describe(resolveMeaningful(e)),
|
|
835
|
+
x: e.clientX,
|
|
836
|
+
y: e.clientY,
|
|
837
|
+
});
|
|
838
|
+
}, true);
|
|
839
|
+
|
|
840
|
+
// \u2500\u2500 Hover (throttled to 800ms) \u2500\u2500
|
|
841
|
+
var __xb_last_hover = 0;
|
|
842
|
+
document.addEventListener('mouseover', function(e) {
|
|
843
|
+
if (Date.now() - __xb_last_hover < 800) return;
|
|
844
|
+
__xb_last_hover = Date.now();
|
|
845
|
+
var target = resolveMeaningful(e);
|
|
846
|
+
// Only record hover on interactive elements
|
|
847
|
+
var tag = target.tagName && target.tagName.toLowerCase();
|
|
848
|
+
var isInteractive = tag === 'a' || tag === 'button' || tag === 'input'
|
|
849
|
+
|| tag === 'select' || tag === 'textarea' || tag === 'summary'
|
|
850
|
+
|| target.getAttribute('role') === 'button'
|
|
851
|
+
|| target.getAttribute('role') === 'link'
|
|
852
|
+
|| target.getAttribute('role') === 'menuitem'
|
|
853
|
+
|| target.getAttribute('role') === 'tab'
|
|
854
|
+
|| target.getAttribute('role') === 'option'
|
|
855
|
+
|| !!target.closest('[role="menu"], [role="menubar"], [role="tablist"], [role="listbox"], [role="tree"], nav, menu');
|
|
856
|
+
if (isInteractive) {
|
|
857
|
+
pushAction('hover', {
|
|
858
|
+
element: describe(target),
|
|
859
|
+
x: e.clientX,
|
|
860
|
+
y: e.clientY,
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
}, true);
|
|
864
|
+
|
|
865
|
+
// \u2500\u2500 Drag & drop \u2500\u2500
|
|
866
|
+
var __xb_drag_source = null;
|
|
867
|
+
var __xb_drag_start_pos = null;
|
|
868
|
+
document.addEventListener('dragstart', function(e) {
|
|
869
|
+
__xb_drag_source = e.target;
|
|
870
|
+
__xb_drag_start_pos = { x: e.clientX, y: e.clientY };
|
|
871
|
+
}, true);
|
|
872
|
+
document.addEventListener('drop', function(e) {
|
|
873
|
+
if (__xb_drag_source && __xb_drag_start_pos) {
|
|
874
|
+
pushAction('drag', {
|
|
875
|
+
x: e.clientX,
|
|
876
|
+
y: e.clientY,
|
|
877
|
+
drag: {
|
|
878
|
+
fromX: __xb_drag_start_pos.x,
|
|
879
|
+
fromY: __xb_drag_start_pos.y,
|
|
880
|
+
toX: e.clientX,
|
|
881
|
+
toY: e.clientY,
|
|
882
|
+
source: describe(__xb_drag_source),
|
|
883
|
+
target: describe(resolveMeaningful(e)),
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
__xb_drag_source = null;
|
|
888
|
+
__xb_drag_start_pos = null;
|
|
889
|
+
}, true);
|
|
890
|
+
document.addEventListener('dragend', function() {
|
|
891
|
+
__xb_drag_source = null;
|
|
892
|
+
__xb_drag_start_pos = null;
|
|
893
|
+
}, true);
|
|
894
|
+
|
|
895
|
+
// \u2500\u2500 Window resize \u2500\u2500
|
|
896
|
+
var __xb_last_resize = 0;
|
|
897
|
+
window.addEventListener('resize', function() {
|
|
898
|
+
if (Date.now() - __xb_last_resize < 1000) return;
|
|
899
|
+
__xb_last_resize = Date.now();
|
|
900
|
+
pushAction('resize', {
|
|
901
|
+
resize: { width: window.innerWidth, height: window.innerHeight },
|
|
902
|
+
});
|
|
903
|
+
}, true);
|
|
904
|
+
|
|
905
|
+
// \u2500\u2500 Clipboard (copy/paste/cut) \u2500\u2500
|
|
906
|
+
document.addEventListener('copy', function(e) {
|
|
907
|
+
pushAction('clipboard', { clipboard: { operation: 'copy' } });
|
|
908
|
+
}, true);
|
|
909
|
+
document.addEventListener('paste', function(e) {
|
|
910
|
+
var preview = '';
|
|
911
|
+
try {
|
|
912
|
+
preview = (e.clipboardData || window.clipboardData).getData('text').substring(0, 100);
|
|
913
|
+
} catch(ex) {}
|
|
914
|
+
pushAction('clipboard', { clipboard: { operation: 'paste', textPreview: preview } });
|
|
915
|
+
}, true);
|
|
916
|
+
document.addEventListener('cut', function(e) {
|
|
917
|
+
pushAction('clipboard', { clipboard: { operation: 'cut' } });
|
|
918
|
+
}, true);
|
|
919
|
+
|
|
920
|
+
// \u2500\u2500 Touch events \u2500\u2500
|
|
921
|
+
document.addEventListener('touchstart', function(e) {
|
|
922
|
+
var touches = [];
|
|
923
|
+
for (var i = 0; i < e.touches.length; i++) {
|
|
924
|
+
touches.push({ x: e.touches[i].clientX, y: e.touches[i].clientY });
|
|
925
|
+
}
|
|
926
|
+
pushAction('touch', {
|
|
927
|
+
element: describe(resolveMeaningful(e)),
|
|
928
|
+
touch: { touchType: 'start', touches: touches },
|
|
929
|
+
});
|
|
930
|
+
}, true);
|
|
931
|
+
document.addEventListener('touchend', function(e) {
|
|
932
|
+
var touches = [];
|
|
933
|
+
for (var i = 0; i < e.changedTouches.length; i++) {
|
|
934
|
+
touches.push({ x: e.changedTouches[i].clientX, y: e.changedTouches[i].clientY });
|
|
935
|
+
}
|
|
936
|
+
pushAction('touch', {
|
|
937
|
+
element: describe(resolveMeaningful(e)),
|
|
938
|
+
touch: { touchType: 'end', touches: touches },
|
|
939
|
+
});
|
|
940
|
+
}, true);
|
|
941
|
+
|
|
942
|
+
// \u2500\u2500 Focus / Blur \u2500\u2500
|
|
943
|
+
document.addEventListener('focusin', function(e) {
|
|
944
|
+
var target = actualTarget(e);
|
|
945
|
+
var tag = target.tagName && target.tagName.toLowerCase();
|
|
946
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select' || target.isContentEditable) {
|
|
947
|
+
pushAction('focus', {
|
|
948
|
+
element: describe(target),
|
|
949
|
+
focus: { focusType: 'focus' },
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}, true);
|
|
953
|
+
document.addEventListener('focusout', function(e) {
|
|
954
|
+
var target = actualTarget(e);
|
|
955
|
+
var tag = target.tagName && target.tagName.toLowerCase();
|
|
956
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select' || target.isContentEditable) {
|
|
957
|
+
pushAction('focus', {
|
|
958
|
+
element: describe(target),
|
|
959
|
+
focus: { focusType: 'blur' },
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}, true);
|
|
963
|
+
|
|
964
|
+
// \u2500\u2500 Visibility change (tab switch) \u2500\u2500
|
|
965
|
+
document.addEventListener('visibilitychange', function() {
|
|
966
|
+
pushAction('visibility', {
|
|
967
|
+
visibility: { state: document.hidden ? 'hidden' : 'visible' },
|
|
968
|
+
});
|
|
969
|
+
}, true);
|
|
353
970
|
})();
|
|
354
971
|
`;
|
|
355
972
|
var CHECKPOINT_OVERLAY_SCRIPT = `
|
|
@@ -605,6 +1222,13 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
605
1222
|
flushTimer = null;
|
|
606
1223
|
lastActionTs = 0;
|
|
607
1224
|
activePages = /* @__PURE__ */ new Set();
|
|
1225
|
+
lastKnownUrl = "";
|
|
1226
|
+
// Track URL to detect real navigation changes
|
|
1227
|
+
/** Dedup window: after a CDP command action, ignore matching action signals within this window */
|
|
1228
|
+
cdpActionDedup = null;
|
|
1229
|
+
/** Network dedup: last request key for short-window dedup */
|
|
1230
|
+
_lastRequestKey = "";
|
|
1231
|
+
_lastRequestTs = 0;
|
|
608
1232
|
_isRecording = false;
|
|
609
1233
|
constructor(context, page, sessionName) {
|
|
610
1234
|
this.context = context;
|
|
@@ -617,6 +1241,43 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
617
1241
|
get actionCount() {
|
|
618
1242
|
return this.actions.length;
|
|
619
1243
|
}
|
|
1244
|
+
/** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
|
|
1245
|
+
recordCommandAction(action) {
|
|
1246
|
+
const normalizedType = action.type === "cdp-fill" ? "input" : action.type === "cdp-click" ? "click" : action.type;
|
|
1247
|
+
const recent = this.actions[this.actions.length - 1];
|
|
1248
|
+
if (recent && Date.now() - recent.timestamp < 1500) {
|
|
1249
|
+
const typeMatch = recent.type === action.type || recent.type === normalizedType;
|
|
1250
|
+
const valueMatch = !action.value || recent.value === action.value;
|
|
1251
|
+
const selectorMatch = !action.selector || recent.element?.selector && (recent.element.selector === action.selector || recent.element.selector.endsWith(" " + action.selector) || action.selector.endsWith(" " + recent.element.selector));
|
|
1252
|
+
if (typeMatch && valueMatch && selectorMatch) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
this.actionCounter++;
|
|
1257
|
+
const ts = Date.now();
|
|
1258
|
+
const actionUrl = action.url && action.url !== "about:blank" ? action.url : this.lastKnownUrl || this.page.url();
|
|
1259
|
+
this.actions.push({
|
|
1260
|
+
id: this.actionCounter,
|
|
1261
|
+
type: action.type,
|
|
1262
|
+
timestamp: ts,
|
|
1263
|
+
url: actionUrl,
|
|
1264
|
+
pageTitle: "",
|
|
1265
|
+
element: action.element || (action.selector ? { tag: "", selector: action.selector, text: "" } : void 0),
|
|
1266
|
+
value: action.value
|
|
1267
|
+
});
|
|
1268
|
+
this.lastActionTs = ts;
|
|
1269
|
+
this.cdpActionDedup = {
|
|
1270
|
+
type: normalizedType,
|
|
1271
|
+
value: action.value,
|
|
1272
|
+
selector: action.selector,
|
|
1273
|
+
until: Date.now() + 1500
|
|
1274
|
+
};
|
|
1275
|
+
if (action.url && action.url !== "about:blank") {
|
|
1276
|
+
this.lastKnownUrl = action.url;
|
|
1277
|
+
} else if (action.type === "goto" && action.value && action.value !== "about:blank") {
|
|
1278
|
+
this.lastKnownUrl = action.value;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
620
1281
|
get networkCount() {
|
|
621
1282
|
return this.network.length;
|
|
622
1283
|
}
|
|
@@ -663,6 +1324,10 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
663
1324
|
this.contextChanges = [];
|
|
664
1325
|
this.checkpoints = [];
|
|
665
1326
|
this.checkpointCounter = 0;
|
|
1327
|
+
this.lastKnownUrl = this.page.url();
|
|
1328
|
+
await this.page.addInitScript(getSelectorGeneratorScript());
|
|
1329
|
+
await this.page.addInitScript(ACTION_SIGNAL_SCRIPT);
|
|
1330
|
+
await this.page.addInitScript(CHECKPOINT_OVERLAY_SCRIPT);
|
|
666
1331
|
if (url) {
|
|
667
1332
|
await this.page.goto(url, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
668
1333
|
this.startUrl = url;
|
|
@@ -678,13 +1343,17 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
678
1343
|
};
|
|
679
1344
|
writeFileSync(this.controlFilePath, JSON.stringify(control, null, 2), "utf-8");
|
|
680
1345
|
await this.injectActionScript(this.page);
|
|
681
|
-
await this.page.addInitScript(ACTION_SIGNAL_SCRIPT);
|
|
682
|
-
await this.page.addInitScript(CHECKPOINT_OVERLAY_SCRIPT);
|
|
683
1346
|
this.context.on("request", this.handleRequest);
|
|
684
1347
|
this.context.on("response", this.handleResponse);
|
|
685
1348
|
this.context.on("page", this.handleNewPage);
|
|
1349
|
+
for (const p of this.context.pages()) {
|
|
1350
|
+
p.on("request", this.handleRequest);
|
|
1351
|
+
p.on("response", this.handleResponse);
|
|
1352
|
+
}
|
|
1353
|
+
this.context.on("page", this.handleNewPage);
|
|
686
1354
|
this.page.on("framenavigated", this.handleFrameNavigated);
|
|
687
1355
|
this.page.on("dialog", this.handleDialog);
|
|
1356
|
+
this.page.on("filechooser", this.handleFileChooser);
|
|
688
1357
|
this.pollTimer = setInterval(() => void this.pollActions(), 200);
|
|
689
1358
|
this.flushTimer = setInterval(() => this.flushToDisk(), 5e3);
|
|
690
1359
|
}
|
|
@@ -719,6 +1388,21 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
719
1388
|
const data = this.buildData();
|
|
720
1389
|
const summary = this.buildSummary(data);
|
|
721
1390
|
this.writeFinalOutput(data, summary);
|
|
1391
|
+
try {
|
|
1392
|
+
const knowledge = updateSiteKnowledge(data);
|
|
1393
|
+
const knowledgeDir = join(this.recordingsDir, "site-knowledge.md");
|
|
1394
|
+
const knowledgeJson = join(this.recordingsDir, "site-knowledge.json");
|
|
1395
|
+
const { readFileSync: rf } = __require("fs");
|
|
1396
|
+
const { getKnowledgePath } = (init_site_knowledge(), __toCommonJS(site_knowledge_exports));
|
|
1397
|
+
const mdPath = getKnowledgePath(knowledge.domain, "md");
|
|
1398
|
+
try {
|
|
1399
|
+
const md = rf(mdPath, "utf-8");
|
|
1400
|
+
writeFileSync(knowledgeDir, md, "utf-8");
|
|
1401
|
+
} catch {
|
|
1402
|
+
}
|
|
1403
|
+
writeFileSync(knowledgeJson, JSON.stringify(knowledge, null, 2), "utf-8");
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
722
1406
|
try {
|
|
723
1407
|
rmSync(this.controlFilePath);
|
|
724
1408
|
} catch {
|
|
@@ -811,13 +1495,75 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
811
1495
|
// ==================== Private ====================
|
|
812
1496
|
async injectActionScript(page) {
|
|
813
1497
|
try {
|
|
1498
|
+
await page.evaluate(getSelectorGeneratorScript());
|
|
814
1499
|
await page.evaluate(ACTION_SIGNAL_SCRIPT);
|
|
1500
|
+
await page.evaluate(`window.__xb_action_script_src = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};`);
|
|
815
1501
|
} catch {
|
|
816
1502
|
}
|
|
817
1503
|
try {
|
|
818
1504
|
await page.evaluate(CHECKPOINT_OVERLAY_SCRIPT);
|
|
819
1505
|
} catch {
|
|
820
1506
|
}
|
|
1507
|
+
try {
|
|
1508
|
+
await page.evaluate(`
|
|
1509
|
+
(function() {
|
|
1510
|
+
var _scriptSrc = ${JSON.stringify(ACTION_SIGNAL_SCRIPT)};
|
|
1511
|
+
function injectIframe(iframe) {
|
|
1512
|
+
try {
|
|
1513
|
+
var w = iframe.contentWindow;
|
|
1514
|
+
if (!w) return;
|
|
1515
|
+
// Force re-injection: clear old flag
|
|
1516
|
+
try { delete w.__xb_action_signal; } catch(e) {}
|
|
1517
|
+
w.eval(_scriptSrc);
|
|
1518
|
+
// Tag iframe so flushIframes knows it's injected
|
|
1519
|
+
iframe.__xb_injected = true;
|
|
1520
|
+
} catch(e) {}
|
|
1521
|
+
}
|
|
1522
|
+
function watchIframe(iframe) {
|
|
1523
|
+
if (iframe.__xb_watched) return;
|
|
1524
|
+
iframe.__xb_watched = true;
|
|
1525
|
+
injectIframe(iframe);
|
|
1526
|
+
iframe.addEventListener('load', function() { injectIframe(iframe); });
|
|
1527
|
+
}
|
|
1528
|
+
// Inject into existing iframes
|
|
1529
|
+
try {
|
|
1530
|
+
var iframes = document.querySelectorAll('iframe');
|
|
1531
|
+
for (var i = 0; i < iframes.length; i++) watchIframe(iframes[i]);
|
|
1532
|
+
} catch(e) {}
|
|
1533
|
+
// Watch for dynamically inserted iframes
|
|
1534
|
+
if (!window.__xb_iframe_observer) {
|
|
1535
|
+
window.__xb_iframe_observer = new MutationObserver(function(mutations) {
|
|
1536
|
+
for (var m = 0; m < mutations.length; m++) {
|
|
1537
|
+
for (var n = 0; n < mutations[m].addedNodes.length; n++) {
|
|
1538
|
+
var node = mutations[m].addedNodes[n];
|
|
1539
|
+
if (node.tagName === 'IFRAME') {
|
|
1540
|
+
watchIframe(node);
|
|
1541
|
+
} else if (node.querySelectorAll) {
|
|
1542
|
+
var sub = node.querySelectorAll('iframe');
|
|
1543
|
+
for (var k = 0; k < sub.length; k++) watchIframe(sub[k]);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
});
|
|
1548
|
+
window.__xb_iframe_observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
1549
|
+
}
|
|
1550
|
+
// Periodic re-injection for iframes that load late or navigate internally
|
|
1551
|
+
if (!window.__xb_iframe_timer) {
|
|
1552
|
+
window.__xb_iframe_timer = setInterval(function() {
|
|
1553
|
+
try {
|
|
1554
|
+
var iframes = document.querySelectorAll('iframe');
|
|
1555
|
+
for (var i = 0; i < iframes.length; i++) {
|
|
1556
|
+
if (!iframes[i].__xb_injected) {
|
|
1557
|
+
watchIframe(iframes[i]);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} catch(e) {}
|
|
1561
|
+
}, 3000);
|
|
1562
|
+
}
|
|
1563
|
+
})();
|
|
1564
|
+
`);
|
|
1565
|
+
} catch {
|
|
1566
|
+
}
|
|
821
1567
|
}
|
|
822
1568
|
// ─── Network capture ────────────────────────────────────────────
|
|
823
1569
|
handleRequest = (request) => {
|
|
@@ -825,6 +1571,11 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
825
1571
|
if (["image", "stylesheet", "font", "manifest", "other"].includes(resourceType)) return;
|
|
826
1572
|
const url = request.url();
|
|
827
1573
|
if (url.startsWith("data:") || url.startsWith("chrome-extension://") || url.startsWith("blob:")) return;
|
|
1574
|
+
const dedupKey = request.method() + " " + url;
|
|
1575
|
+
const now = Date.now();
|
|
1576
|
+
if (this._lastRequestKey === dedupKey && now - this._lastRequestTs < 100) return;
|
|
1577
|
+
this._lastRequestKey = dedupKey;
|
|
1578
|
+
this._lastRequestTs = now;
|
|
828
1579
|
this.networkCounter++;
|
|
829
1580
|
const entry = {
|
|
830
1581
|
id: this.networkCounter,
|
|
@@ -894,22 +1645,68 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
894
1645
|
});
|
|
895
1646
|
await page.addInitScript(ACTION_SIGNAL_SCRIPT);
|
|
896
1647
|
await page.addInitScript(CHECKPOINT_OVERLAY_SCRIPT);
|
|
897
|
-
await
|
|
898
|
-
|
|
1648
|
+
await page.addInitScript(getSelectorGeneratorScript());
|
|
1649
|
+
const injectAndRetry = async () => {
|
|
1650
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
1651
|
+
try {
|
|
1652
|
+
const url = page.url();
|
|
1653
|
+
if (url && url !== "about:blank" && !url.startsWith("chrome")) {
|
|
1654
|
+
await this.injectActionScript(page);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
} catch {
|
|
1658
|
+
}
|
|
1659
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
injectAndRetry();
|
|
899
1663
|
page.on("framenavigated", this.handleFrameNavigated);
|
|
1664
|
+
page.on("request", this.handleRequest);
|
|
1665
|
+
page.on("response", this.handleResponse);
|
|
1666
|
+
page.on("filechooser", this.handleFileChooser);
|
|
1667
|
+
page.on("dialog", this.handleDialog);
|
|
900
1668
|
page.on("close", () => {
|
|
901
1669
|
this.activePages.delete(page);
|
|
902
1670
|
});
|
|
1671
|
+
page.on("framenavigated", async (frame) => {
|
|
1672
|
+
if (frame !== frame.page().mainFrame()) return;
|
|
1673
|
+
const url = frame.url();
|
|
1674
|
+
if (url && url !== "about:blank" && !url.startsWith("chrome")) {
|
|
1675
|
+
try {
|
|
1676
|
+
await this.injectActionScript(page);
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
903
1681
|
};
|
|
904
1682
|
handleFrameNavigated = (frame) => {
|
|
905
1683
|
if (frame !== frame.page().mainFrame()) return;
|
|
1684
|
+
const newUrl = frame.url();
|
|
906
1685
|
this.contextCounter++;
|
|
907
1686
|
this.contextChanges.push({
|
|
908
1687
|
id: this.contextCounter,
|
|
909
1688
|
timestamp: Date.now(),
|
|
910
1689
|
type: "navigate",
|
|
911
|
-
url:
|
|
1690
|
+
url: newUrl
|
|
912
1691
|
});
|
|
1692
|
+
const lastAction = this.actions[this.actions.length - 1];
|
|
1693
|
+
const lastActionUrl = lastAction?.url;
|
|
1694
|
+
if (newUrl && newUrl !== "about:blank" && newUrl !== lastActionUrl) {
|
|
1695
|
+
this.actionCounter++;
|
|
1696
|
+
this.actions.push({
|
|
1697
|
+
id: this.actionCounter,
|
|
1698
|
+
type: "navigation",
|
|
1699
|
+
timestamp: Date.now(),
|
|
1700
|
+
url: newUrl,
|
|
1701
|
+
pageTitle: "",
|
|
1702
|
+
element: void 0
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
const page = frame.page();
|
|
1706
|
+
if (newUrl && newUrl !== "about:blank") {
|
|
1707
|
+
this.injectActionScript(page).catch(() => {
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
913
1710
|
};
|
|
914
1711
|
handleDialog = async (dialog) => {
|
|
915
1712
|
this.checkpointCounter++;
|
|
@@ -926,6 +1723,62 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
926
1723
|
await dialog.dismiss().catch(() => {
|
|
927
1724
|
});
|
|
928
1725
|
};
|
|
1726
|
+
handleFileChooser = async (fileChooser) => {
|
|
1727
|
+
const url = this.page.url();
|
|
1728
|
+
const sel = fileChooser.selector || 'input[type="file"]';
|
|
1729
|
+
let element;
|
|
1730
|
+
try {
|
|
1731
|
+
element = await this.page.evaluate(new Function("selector", `
|
|
1732
|
+
const el = document.querySelector(selector);
|
|
1733
|
+
if (!el) return null;
|
|
1734
|
+
return window.__xb_describe(el);
|
|
1735
|
+
`), sel) ?? void 0;
|
|
1736
|
+
} catch {
|
|
1737
|
+
}
|
|
1738
|
+
let fileData = [];
|
|
1739
|
+
try {
|
|
1740
|
+
fileData = await this.page.evaluate(new Function("selector", `
|
|
1741
|
+
return new Promise(resolve => {
|
|
1742
|
+
const input = document.querySelector(selector);
|
|
1743
|
+
if (!input || !input.files || input.files.length === 0) { resolve([]); return; }
|
|
1744
|
+
const readers = [];
|
|
1745
|
+
for (let i = 0; i < input.files.length; i++) {
|
|
1746
|
+
readers.push(new Promise(r => {
|
|
1747
|
+
const reader = new FileReader();
|
|
1748
|
+
reader.onload = () => r({ name: input.files[i].name, type: input.files[i].type, size: input.files[i].size, dataUrl: reader.result });
|
|
1749
|
+
reader.onerror = () => r({ name: input.files[i].name, type: input.files[i].type, size: input.files[i].size, dataUrl: null });
|
|
1750
|
+
reader.readAsDataURL(input.files[i]);
|
|
1751
|
+
}));
|
|
1752
|
+
}
|
|
1753
|
+
Promise.all(readers).then(resolve);
|
|
1754
|
+
});
|
|
1755
|
+
`), sel);
|
|
1756
|
+
} catch {
|
|
1757
|
+
}
|
|
1758
|
+
const names = fileData.map((f) => f.name);
|
|
1759
|
+
this.actionCounter++;
|
|
1760
|
+
this.actions.push({
|
|
1761
|
+
id: this.actionCounter,
|
|
1762
|
+
type: "filechooser",
|
|
1763
|
+
timestamp: Date.now(),
|
|
1764
|
+
url,
|
|
1765
|
+
pageTitle: "",
|
|
1766
|
+
element: element || {
|
|
1767
|
+
selector: sel,
|
|
1768
|
+
strategy: "css",
|
|
1769
|
+
confidence: "high",
|
|
1770
|
+
tag: "input",
|
|
1771
|
+
text: ""
|
|
1772
|
+
},
|
|
1773
|
+
value: names.join(", ") || void 0,
|
|
1774
|
+
files: {
|
|
1775
|
+
names,
|
|
1776
|
+
count: fileData.length,
|
|
1777
|
+
isMultiple: fileChooser.isMultiple,
|
|
1778
|
+
fileData: fileData.length > 0 ? fileData : void 0
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
};
|
|
929
1782
|
// ─── Action polling ─────────────────────────────────────────────
|
|
930
1783
|
async pollActions() {
|
|
931
1784
|
const pages = [this.page, ...this.activePages];
|
|
@@ -940,17 +1793,86 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
940
1793
|
async flushPendingActions(page) {
|
|
941
1794
|
let pending = [];
|
|
942
1795
|
try {
|
|
943
|
-
pending = await page.evaluate(()
|
|
944
|
-
|
|
945
|
-
|
|
1796
|
+
pending = await page.evaluate(`(function() {
|
|
1797
|
+
var w = window;
|
|
1798
|
+
var actions = w.__xb_pending_actions || [];
|
|
946
1799
|
w.__xb_pending_actions = [];
|
|
1800
|
+
|
|
1801
|
+
// Recursively flush pending actions from same-origin iframes (including nested)
|
|
1802
|
+
function flushIframes(doc) {
|
|
1803
|
+
try {
|
|
1804
|
+
var iframes = doc.querySelectorAll('iframe');
|
|
1805
|
+
for (var i = 0; i < iframes.length; i++) {
|
|
1806
|
+
try {
|
|
1807
|
+
var iframeWin = iframes[i].contentWindow;
|
|
1808
|
+
if (!iframeWin) continue;
|
|
1809
|
+
// Ensure action script is injected into iframe
|
|
1810
|
+
if (!iframeWin.__xb_action_script_injected) {
|
|
1811
|
+
iframeWin.__xb_action_script_injected = true;
|
|
1812
|
+
try { delete iframeWin.__xb_action_signal; } catch(e) {}
|
|
1813
|
+
try { iframeWin.eval(w.__xb_action_script_src); } catch(e) {}
|
|
1814
|
+
}
|
|
1815
|
+
var iframeActions = iframeWin.__xb_pending_actions;
|
|
1816
|
+
if (Array.isArray(iframeActions) && iframeActions.length > 0) {
|
|
1817
|
+
// Get iframe position to offset coordinates
|
|
1818
|
+
var rect = iframes[i].getBoundingClientRect();
|
|
1819
|
+
for (var j = 0; j < iframeActions.length; j++) {
|
|
1820
|
+
var act = iframeActions[j];
|
|
1821
|
+
// Offset coordinates from iframe-relative to page-relative
|
|
1822
|
+
if (act.x != null) act.x = act.x + rect.left;
|
|
1823
|
+
if (act.y != null) act.y = act.y + rect.top;
|
|
1824
|
+
// Tag the action as originating from an iframe
|
|
1825
|
+
act.iframeSrc = iframes[i].src || '';
|
|
1826
|
+
actions.push(act);
|
|
1827
|
+
}
|
|
1828
|
+
iframeWin.__xb_pending_actions = [];
|
|
1829
|
+
}
|
|
1830
|
+
// Recurse into nested iframes
|
|
1831
|
+
try { flushIframes(iframeWin.document); } catch(e) {}
|
|
1832
|
+
} catch(e) {}
|
|
1833
|
+
}
|
|
1834
|
+
} catch(e) {}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
flushIframes(document);
|
|
947
1838
|
return actions;
|
|
948
|
-
});
|
|
1839
|
+
})()`);
|
|
949
1840
|
} catch {
|
|
950
1841
|
return;
|
|
951
1842
|
}
|
|
1843
|
+
try {
|
|
1844
|
+
const currentUrl = page.url();
|
|
1845
|
+
const normalize = (u) => u.replace(/\/+$/, "");
|
|
1846
|
+
if (currentUrl && currentUrl !== "about:blank" && normalize(currentUrl) !== normalize(this.lastKnownUrl)) {
|
|
1847
|
+
const hasNav = this.actions.slice(-3).some(
|
|
1848
|
+
(a) => (a.type === "navigation" || a.type === "goto") && normalize(a.url || "") === normalize(currentUrl)
|
|
1849
|
+
);
|
|
1850
|
+
if (!hasNav) {
|
|
1851
|
+
this.actionCounter++;
|
|
1852
|
+
this.actions.push({
|
|
1853
|
+
id: this.actionCounter,
|
|
1854
|
+
type: "navigation",
|
|
1855
|
+
timestamp: Date.now(),
|
|
1856
|
+
url: currentUrl,
|
|
1857
|
+
pageTitle: "",
|
|
1858
|
+
element: void 0
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
this.lastKnownUrl = currentUrl;
|
|
1862
|
+
}
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
952
1865
|
for (const raw of pending) {
|
|
953
1866
|
if (raw.ts <= this.lastActionTs) continue;
|
|
1867
|
+
if (this.cdpActionDedup && Date.now() < this.cdpActionDedup.until) {
|
|
1868
|
+
const dedup = this.cdpActionDedup;
|
|
1869
|
+
const typeMatch = raw.type === dedup.type;
|
|
1870
|
+
const valueMatch = !dedup.value || raw.value === dedup.value;
|
|
1871
|
+
const selectorMatch = !dedup.selector || raw.element?.selector && (raw.element.selector === dedup.selector || raw.element.selector.endsWith(" " + dedup.selector) || dedup.selector.endsWith(" " + raw.element.selector));
|
|
1872
|
+
if (typeMatch && valueMatch && selectorMatch) {
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
954
1876
|
this.actionCounter++;
|
|
955
1877
|
let clickContext;
|
|
956
1878
|
if (raw.type === "click" && raw.x !== void 0 && raw.y !== void 0) {
|
|
@@ -969,7 +1891,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
969
1891
|
y: raw.y,
|
|
970
1892
|
scrollX: raw.scrollX,
|
|
971
1893
|
scrollY: raw.scrollY,
|
|
972
|
-
clickContext
|
|
1894
|
+
clickContext,
|
|
1895
|
+
files: raw.files
|
|
973
1896
|
});
|
|
974
1897
|
this.lastActionTs = raw.ts;
|
|
975
1898
|
if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
|
|
@@ -989,116 +1912,87 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
989
1912
|
async captureClickContext(page, cx, cy) {
|
|
990
1913
|
await new Promise((r) => setTimeout(r, 300));
|
|
991
1914
|
try {
|
|
992
|
-
const ctx = await page.evaluate(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
'[class*="modal"]',
|
|
1015
|
-
'[class*="panel"]',
|
|
1016
|
-
'[class*="overlay"]',
|
|
1017
|
-
'[class*="sheet"]',
|
|
1018
|
-
"[data-popup]",
|
|
1019
|
-
"[data-dropdown]",
|
|
1020
|
-
"[data-menu]",
|
|
1021
|
-
'[data-popover"]',
|
|
1022
|
-
".semi-dropdown",
|
|
1023
|
-
".semi-popover",
|
|
1024
|
-
".semi-modal",
|
|
1025
|
-
".ant-dropdown",
|
|
1026
|
-
".ant-popover",
|
|
1027
|
-
".ant-modal",
|
|
1028
|
-
".el-dropdown",
|
|
1029
|
-
".el-popover",
|
|
1030
|
-
".el-dialog",
|
|
1031
|
-
".t-dropdown",
|
|
1032
|
-
".t-popup",
|
|
1033
|
-
".t-dialog"
|
|
1034
|
-
];
|
|
1035
|
-
function isNear(el, x, y, range) {
|
|
1036
|
-
try {
|
|
1037
|
-
const r = el.getBoundingClientRect();
|
|
1038
|
-
if (!r || r.width === 0 || r.height === 0) return false;
|
|
1039
|
-
return !(r.left > x + range || r.right < x - range || r.top > y + range || r.bottom < y - range);
|
|
1040
|
-
} catch {
|
|
1041
|
-
return false;
|
|
1915
|
+
const ctx = await page.evaluate(`
|
|
1916
|
+
(function() {
|
|
1917
|
+
var cx = ${cx}, cy = ${cy};
|
|
1918
|
+
var POPOVER_SELECTORS = [
|
|
1919
|
+
'[role="menu"]','[role="listbox"]','[role="dialog"]','[role="tooltip"]','[role="popover"]',
|
|
1920
|
+
'[role="combobox"]','[role="tree"]','[role="grid"]',
|
|
1921
|
+
'.popover','.popup','.dropdown','.menu','.modal','.tooltip','.panel',
|
|
1922
|
+
'[class*="popover"]','[class*="popup"]','[class*="dropdown"]','[class*="menu"]',
|
|
1923
|
+
'[class*="tooltip"]','[class*="modal"]','[class*="panel"]','[class*="overlay"]','[class*="sheet"]',
|
|
1924
|
+
'[data-popup]','[data-dropdown]','[data-menu]','[data-popover"]',
|
|
1925
|
+
'.semi-dropdown','.semi-popover','.semi-modal',
|
|
1926
|
+
'.ant-dropdown','.ant-popover','.ant-modal',
|
|
1927
|
+
'.el-dropdown','.el-popover','.el-dialog',
|
|
1928
|
+
'.t-dropdown','.t-popup','.t-dialog'
|
|
1929
|
+
];
|
|
1930
|
+
|
|
1931
|
+
function isNear(el, x, y, range) {
|
|
1932
|
+
try {
|
|
1933
|
+
var r = el.getBoundingClientRect();
|
|
1934
|
+
if (!r || r.width === 0 || r.height === 0) return false;
|
|
1935
|
+
return !(r.left > x + range || r.right < x - range || r.top > y + range || r.bottom < y - range);
|
|
1936
|
+
} catch(e) { return false; }
|
|
1042
1937
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1938
|
+
|
|
1939
|
+
var result = { appeared: [], disappeared: [], stateChanges: [] };
|
|
1940
|
+
var seenElements = new Set();
|
|
1941
|
+
|
|
1942
|
+
for (var si = 0; si < POPOVER_SELECTORS.length; si++) {
|
|
1943
|
+
try {
|
|
1944
|
+
var els = document.querySelectorAll(POPOVER_SELECTORS[si]);
|
|
1945
|
+
for (var j = 0; j < els.length; j++) {
|
|
1946
|
+
var el = els[j];
|
|
1947
|
+
if (seenElements.has(el)) continue;
|
|
1948
|
+
if (!isNear(el, cx, cy, 300)) continue;
|
|
1949
|
+
var rect = el.getBoundingClientRect();
|
|
1950
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
1951
|
+
seenElements.add(el);
|
|
1952
|
+
|
|
1953
|
+
var items = [];
|
|
1954
|
+
var children = el.querySelectorAll('a,button,[role="menuitem"],[role="option"],[role="treeitem"],li,div[class*="item"]');
|
|
1955
|
+
var seenItemTexts = new Set();
|
|
1956
|
+
for (var k = 0; k < Math.min(children.length, 30); k++) {
|
|
1957
|
+
var child = children[k];
|
|
1958
|
+
var childText = (child.textContent || '').trim().substring(0, 60);
|
|
1959
|
+
if (!childText || seenItemTexts.has(childText)) continue;
|
|
1960
|
+
seenItemTexts.add(childText);
|
|
1961
|
+
var ci = { text: childText };
|
|
1962
|
+
try { if (child.disabled || child.getAttribute('aria-disabled') === 'true') ci.disabled = true; } catch(e) {}
|
|
1963
|
+
try { if (child.tagName) ci.tag = child.tagName.toLowerCase(); } catch(e) {}
|
|
1964
|
+
try { if (child.href) ci.href = child.href.substring(0, 80); } catch(e) {}
|
|
1965
|
+
items.push(ci);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
result.appeared.push({
|
|
1969
|
+
tag: el.tagName.toLowerCase(),
|
|
1970
|
+
selector: el.id ? '#' + el.id : undefined,
|
|
1971
|
+
role: el.getAttribute('role'),
|
|
1972
|
+
text: (el.textContent || '').trim().substring(0, 100),
|
|
1973
|
+
rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
|
|
1974
|
+
items: items
|
|
1975
|
+
});
|
|
1072
1976
|
}
|
|
1073
|
-
|
|
1074
|
-
tag: el.tagName.toLowerCase(),
|
|
1075
|
-
selector: el.id ? "#" + el.id : void 0,
|
|
1076
|
-
role: el.getAttribute("role"),
|
|
1077
|
-
text: (el.textContent || "").trim().substring(0, 100),
|
|
1078
|
-
rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
|
|
1079
|
-
items
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
} catch {
|
|
1977
|
+
} catch(e) { /* skip invalid selectors */ }
|
|
1083
1978
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
result
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
}, [cx, cy]);
|
|
1979
|
+
|
|
1980
|
+
var allInteractive = document.querySelectorAll('[aria-expanded],[disabled],[aria-disabled],[aria-selected],[data-state]');
|
|
1981
|
+
for (var i = 0; i < allInteractive.length; i++) {
|
|
1982
|
+
var el2 = allInteractive[i];
|
|
1983
|
+
if (!isNear(el2, cx, cy, 200)) continue;
|
|
1984
|
+
var info = { tag: el2.tagName.toLowerCase(), text: (el2.textContent || '').trim().substring(0, 60) };
|
|
1985
|
+
try { if (el2.id) info.id = el2.id; } catch(e) {}
|
|
1986
|
+
try { if (el2.getAttribute('aria-expanded')) info.ariaExpanded = el2.getAttribute('aria-expanded'); } catch(e) {}
|
|
1987
|
+
try { if (el2.disabled || el2.getAttribute('aria-disabled') === 'true') info.disabled = true; } catch(e) {}
|
|
1988
|
+
try { if (el2.getAttribute('aria-selected')) info.ariaSelected = el2.getAttribute('aria-selected'); } catch(e) {}
|
|
1989
|
+
try { if (el2.getAttribute('data-state')) info.dataState = el2.getAttribute('data-state'); } catch(e) {}
|
|
1990
|
+
result.stateChanges.push(info);
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
return result;
|
|
1994
|
+
})()
|
|
1995
|
+
`);
|
|
1102
1996
|
if (ctx.appeared.length > 0 || ctx.stateChanges.length > 0) {
|
|
1103
1997
|
return ctx;
|
|
1104
1998
|
}
|