@xbrowser/cli 1.9.4 → 1.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{browser-6WJHQDPV.js → browser-ALPLJNH2.js} +1 -1
- package/dist/{browser-4KRHALJ3.js → browser-JXIJF4VL.js} +1 -1
- package/dist/{browser-C63PFGPT.js → browser-ZPQP6VW7.js} +1 -1
- package/dist/{chunk-OOJ2H7IL.js → chunk-37SHZSUR.js} +1 -1
- package/dist/{chunk-MSQIPXKE.js → chunk-5JSOGINQ.js} +1 -1
- package/dist/{chunk-Z7WC4P2M.js → chunk-74S7UUI2.js} +1 -1
- package/dist/{chunk-TTZNR3QP.js → chunk-C4O73DQL.js} +165 -50
- package/dist/{chunk-YSCY52UJ.js → chunk-CH5ZQV64.js} +165 -50
- package/dist/{chunk-53LRNYPG.js → chunk-YAUG3NSI.js} +2 -2
- package/dist/cli.js +38 -13
- package/dist/{daemon-client-PZX2KOLQ.js → daemon-client-4HJENICO.js} +2 -2
- package/dist/{daemon-client-TOUDMIY5.js → daemon-client-CMRAWXTN.js} +1 -1
- package/dist/daemon-main.js +28 -8
- package/dist/index.d.ts +24 -3
- package/dist/index.js +37 -12
- package/dist/{session-recorder-UTULJFTE.js → session-recorder-7GXI42FO.js} +1 -1
- package/dist/{session-recorder-KPU4YQAE.js → session-recorder-RFOPX4SF.js} +1 -1
- package/dist/{session-replayer-RWFOWH3F.js → session-replayer-3HEIJBJ2.js} +65 -33
- package/package.json +1 -1
|
@@ -65,16 +65,28 @@ function getSelectorGeneratorScript() {
|
|
|
65
65
|
if (isUnique(root, s)) return { selector: s, strategy: 'id+tag', confidence: 'high' };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
// [data-*] \u2014 other data attributes beyond known testids
|
|
80
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
81
|
+
var a = el.attributes[ai];
|
|
82
|
+
if (!a.name.startsWith('data-')) continue;
|
|
83
|
+
if (STABLE_ATTRS.indexOf(a.name) !== -1) continue;
|
|
84
|
+
if (!a.value || a.value.length > 60 || a.value.indexOf('http') === 0) continue;
|
|
85
|
+
var s = '[' + a.name + '="' + esc(a.value) + '"]';
|
|
86
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'data-attr', confidence: 'high' };
|
|
87
|
+
s = tag + s;
|
|
88
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'data-attr+tag', confidence: 'high' };
|
|
89
|
+
}
|
|
78
90
|
|
|
79
91
|
// [name]
|
|
80
92
|
var name = el.getAttribute('name');
|
|
@@ -83,14 +95,35 @@ function getSelectorGeneratorScript() {
|
|
|
83
95
|
if (isUnique(root, s)) return { selector: s, strategy: 'name', confidence: 'high' };
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
// [aria-label]
|
|
99
|
+
var aria = el.getAttribute('aria-label');
|
|
100
|
+
if (aria) {
|
|
101
|
+
var s = '[aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
102
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label', confidence: 'high' };
|
|
103
|
+
s = tag + s;
|
|
104
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'aria-label+tag', confidence: 'high' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// [role][name] combo
|
|
108
|
+
var role = el.getAttribute('role');
|
|
109
|
+
if (role) {
|
|
110
|
+
var name2 = el.getAttribute('name');
|
|
111
|
+
if (name2) {
|
|
112
|
+
var s = '[role="' + role + '"][name="' + esc(name2) + '"]';
|
|
113
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role+name', confidence: 'high' };
|
|
114
|
+
}
|
|
115
|
+
// [role][aria-label] combo
|
|
116
|
+
if (aria) {
|
|
117
|
+
var s = '[role="' + role + '"][aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
118
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role+aria-label', confidence: 'high' };
|
|
119
|
+
}
|
|
120
|
+
// [role] alone
|
|
121
|
+
var text2 = (el.textContent || '').trim();
|
|
122
|
+
if (text2) {
|
|
123
|
+
var s = '[role="' + role + '"]';
|
|
124
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'role', confidence: 'high' };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
94
127
|
|
|
95
128
|
// [placeholder]
|
|
96
129
|
var ph = el.getAttribute('placeholder');
|
|
@@ -113,11 +146,12 @@ function getSelectorGeneratorScript() {
|
|
|
113
146
|
if (isUnique(root, s)) return { selector: s, strategy: 'title', confidence: 'high' };
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
149
|
+
// Unique attribute (skip URL-like, long, and common attrs handled above)
|
|
150
|
+
// Note: non-testid data-* attrs are already tried in [data-*] section above
|
|
151
|
+
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};
|
|
152
|
+
for (var ai = 0; ai < el.attributes.length; ai++) {
|
|
153
|
+
var a = el.attributes[ai];
|
|
154
|
+
if (skipAttr[a.name] || a.name.startsWith('data-') || a.name.startsWith('aria-')) continue;
|
|
121
155
|
if (a.value && a.value.length > 2 && a.value.length <= 60) {
|
|
122
156
|
var s = tag + '[' + a.name + '="' + esc(a.value) + '"]';
|
|
123
157
|
if (isUnique(root, s)) return { selector: s, strategy: 'attribute', confidence: 'medium' };
|
|
@@ -154,10 +188,16 @@ function getSelectorGeneratorScript() {
|
|
|
154
188
|
var s = tag + '.' + esc(cls[i]) + '.' + esc(cls[j]);
|
|
155
189
|
if (isUnique(root, s)) return { selector: s, strategy: 'tag+class-combo', confidence: 'medium' };
|
|
156
190
|
}
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// tag[aria-label] fallback \u2014 aria-label exists but wasn't globally unique
|
|
195
|
+
if (aria) {
|
|
196
|
+
var s = tag + '[aria-label="' + esc(aria.substring(0, 60)) + '"]';
|
|
197
|
+
if (isUnique(root, s)) return { selector: s, strategy: 'tag+aria-label', confidence: 'medium' };
|
|
198
|
+
}
|
|
159
199
|
|
|
160
|
-
|
|
200
|
+
// Parent scope
|
|
161
201
|
var parent = el.parentElement;
|
|
162
202
|
if (parent && parent !== root) {
|
|
163
203
|
if (parent.id) {
|
|
@@ -1224,12 +1264,28 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1224
1264
|
activePages = /* @__PURE__ */ new Set();
|
|
1225
1265
|
lastKnownUrl = "";
|
|
1226
1266
|
// Track URL to detect real navigation changes
|
|
1227
|
-
/** Dedup
|
|
1228
|
-
cdpActionDedup
|
|
1267
|
+
/** Dedup map: key = normalizedType|tag|value → expiration timestamp.
|
|
1268
|
+
* Replaces old single-entry cdpActionDedup for bidirectional dedup. */
|
|
1269
|
+
dedupMap = /* @__PURE__ */ new Map();
|
|
1270
|
+
dedupActionCount = 0;
|
|
1271
|
+
// counter for periodic cleanup
|
|
1229
1272
|
/** Network dedup: last request key for short-window dedup */
|
|
1230
1273
|
_lastRequestKey = "";
|
|
1231
1274
|
_lastRequestTs = 0;
|
|
1232
1275
|
_isRecording = false;
|
|
1276
|
+
streamMode = "clean";
|
|
1277
|
+
/** Ambient action types: filtered out in clean mode */
|
|
1278
|
+
static AMBIENT_TYPES = /* @__PURE__ */ new Set([
|
|
1279
|
+
"hover",
|
|
1280
|
+
"focus",
|
|
1281
|
+
"scroll",
|
|
1282
|
+
"visibility",
|
|
1283
|
+
"resize",
|
|
1284
|
+
"clipboard",
|
|
1285
|
+
"touch",
|
|
1286
|
+
"contextmenu",
|
|
1287
|
+
"drag"
|
|
1288
|
+
]);
|
|
1233
1289
|
constructor(context, page, sessionName) {
|
|
1234
1290
|
this.context = context;
|
|
1235
1291
|
this.page = page;
|
|
@@ -1243,13 +1299,10 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1243
1299
|
}
|
|
1244
1300
|
/** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
|
|
1245
1301
|
async recordCommandAction(action) {
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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) {
|
|
1302
|
+
const dedupFromAction = this.dedupKey(action.type, action.selector, action.element?.tag, action.value);
|
|
1303
|
+
if (dedupFromAction) {
|
|
1304
|
+
const expires = this.dedupMap.get(dedupFromAction);
|
|
1305
|
+
if (expires && Date.now() < expires) {
|
|
1253
1306
|
return;
|
|
1254
1307
|
}
|
|
1255
1308
|
}
|
|
@@ -1271,18 +1324,26 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1271
1324
|
element: actionToPush.element
|
|
1272
1325
|
});
|
|
1273
1326
|
this.lastActionTs = ts;
|
|
1274
|
-
this.
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1327
|
+
this.dedupMap.set(dedupFromAction, Date.now() + 2e3);
|
|
1328
|
+
this.dedupActionCount++;
|
|
1329
|
+
if (this.dedupActionCount % 200 === 0) {
|
|
1330
|
+
const now = Date.now();
|
|
1331
|
+
for (const [k, v] of this.dedupMap) {
|
|
1332
|
+
if (now >= v) this.dedupMap.delete(k);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1280
1335
|
if (action.url && action.url !== "about:blank") {
|
|
1281
1336
|
this.lastKnownUrl = action.url;
|
|
1282
1337
|
} else if (action.type === "goto" && action.value && action.value !== "about:blank") {
|
|
1283
1338
|
this.lastKnownUrl = action.value;
|
|
1284
1339
|
}
|
|
1285
1340
|
}
|
|
1341
|
+
/** Generate dedup key for an action (normalized type + selector/tag + value). */
|
|
1342
|
+
dedupKey(type, selector, tag, value) {
|
|
1343
|
+
const normType = type === "cdp-click" ? "click" : type === "cdp-fill" ? "input" : type === "cdp-eval" ? "eval" : type;
|
|
1344
|
+
const selOrTag = selector || tag || "";
|
|
1345
|
+
return `${normType}|${selOrTag}|${value || ""}`;
|
|
1346
|
+
}
|
|
1286
1347
|
get networkCount() {
|
|
1287
1348
|
return this.network.length;
|
|
1288
1349
|
}
|
|
@@ -1320,7 +1381,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1320
1381
|
return join(this.recordingsDir, ".stop");
|
|
1321
1382
|
}
|
|
1322
1383
|
// ─── Start ──────────────────────────────────────────────────────
|
|
1323
|
-
async start(url) {
|
|
1384
|
+
async start(url, options) {
|
|
1324
1385
|
if (this._isRecording) throw new Error("Already recording");
|
|
1325
1386
|
this._isRecording = true;
|
|
1326
1387
|
this.startedAt = Date.now();
|
|
@@ -1329,6 +1390,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1329
1390
|
this.contextChanges = [];
|
|
1330
1391
|
this.checkpoints = [];
|
|
1331
1392
|
this.checkpointCounter = 0;
|
|
1393
|
+
this.streamMode = options?.stream ?? "clean";
|
|
1332
1394
|
this.lastKnownUrl = this.page.url();
|
|
1333
1395
|
await this.page.addInitScript(getSelectorGeneratorScript());
|
|
1334
1396
|
await this.page.addInitScript(ACTION_SIGNAL_SCRIPT);
|
|
@@ -1897,15 +1959,16 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1897
1959
|
}
|
|
1898
1960
|
for (const raw of pending) {
|
|
1899
1961
|
if (raw.ts <= this.lastActionTs) continue;
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
const selectorMatch = !dedup.selector || raw.element?.selector && (raw.element.selector === dedup.selector || raw.element.selector.endsWith(" " + dedup.selector) || dedup.selector.endsWith(" " + raw.element.selector));
|
|
1905
|
-
if (typeMatch && valueMatch && selectorMatch) {
|
|
1962
|
+
const dedupFromSignal = this.dedupKey(raw.type, raw.element?.selector, raw.element?.tag, raw.value);
|
|
1963
|
+
if (dedupFromSignal) {
|
|
1964
|
+
const expires = this.dedupMap.get(dedupFromSignal);
|
|
1965
|
+
if (expires && Date.now() < expires) {
|
|
1906
1966
|
continue;
|
|
1907
1967
|
}
|
|
1908
1968
|
}
|
|
1969
|
+
if (this.streamMode === "clean" && _SessionRecorder.AMBIENT_TYPES.has(raw.type)) {
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1909
1972
|
this.actionCounter++;
|
|
1910
1973
|
let clickContext;
|
|
1911
1974
|
if (raw.type === "click" && raw.x !== void 0 && raw.y !== void 0) {
|
|
@@ -1930,6 +1993,18 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1930
1993
|
const pushed = this.actions[this.actions.length - 1];
|
|
1931
1994
|
pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
|
|
1932
1995
|
this.lastActionTs = raw.ts;
|
|
1996
|
+
const dedupKey = this.dedupKey(raw.type, raw.element?.selector, raw.element?.tag, raw.value);
|
|
1997
|
+
if (dedupKey) {
|
|
1998
|
+
this.dedupMap.set(dedupKey, Date.now() + 2e3);
|
|
1999
|
+
this.dedupActionCount++;
|
|
2000
|
+
}
|
|
2001
|
+
const verifyTypes = /* @__PURE__ */ new Set(["click", "submit", "input", "change", "cdp-click", "cdp-fill"]);
|
|
2002
|
+
if (verifyTypes.has(raw.type)) {
|
|
2003
|
+
const signals = await this.verifyAction(page, pushed).catch(() => []);
|
|
2004
|
+
if (signals.length > 0) {
|
|
2005
|
+
pushed.__signals = signals;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
1933
2008
|
if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
|
|
1934
2009
|
const detected = await this.detectCheckpoints(page);
|
|
1935
2010
|
for (const cp of detected) {
|
|
@@ -2112,6 +2187,44 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2112
2187
|
return [];
|
|
2113
2188
|
}
|
|
2114
2189
|
}
|
|
2190
|
+
// ─── Verification: collect success signals after key actions ──────
|
|
2191
|
+
/**
|
|
2192
|
+
* After a key action (click/submit/input), check for success signals:
|
|
2193
|
+
* URL changes, recent successful network responses, dialog appearances.
|
|
2194
|
+
*/
|
|
2195
|
+
async verifyAction(page, action) {
|
|
2196
|
+
const signals = [];
|
|
2197
|
+
try {
|
|
2198
|
+
const currentUrl = page.url();
|
|
2199
|
+
if (currentUrl && currentUrl !== "about:blank" && currentUrl !== action.url) {
|
|
2200
|
+
signals.push({ type: "url_change", value: currentUrl, label: `URL changed to ${currentUrl}` });
|
|
2201
|
+
}
|
|
2202
|
+
} catch {
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
const recentNetwork = this.network.filter(
|
|
2206
|
+
(n) => n.timestamp > action.timestamp - 100 && n.timestamp < action.timestamp + 5e3 && n.status >= 200 && n.status < 400
|
|
2207
|
+
);
|
|
2208
|
+
for (const net of recentNetwork.slice(0, 3)) {
|
|
2209
|
+
signals.push({
|
|
2210
|
+
type: "network_success",
|
|
2211
|
+
value: `${net.status}`,
|
|
2212
|
+
label: `${net.method} ${net.path} \u2192 ${net.status}`
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
try {
|
|
2218
|
+
const recentDialog = [...this.checkpoints].reverse().find(
|
|
2219
|
+
(cp) => cp.type === "dialog" && Math.abs(cp.timestamp - action.timestamp) < 3e3
|
|
2220
|
+
);
|
|
2221
|
+
if (recentDialog) {
|
|
2222
|
+
signals.push({ type: "dialog", value: recentDialog.type, label: recentDialog.hint });
|
|
2223
|
+
}
|
|
2224
|
+
} catch {
|
|
2225
|
+
}
|
|
2226
|
+
return signals;
|
|
2227
|
+
}
|
|
2115
2228
|
// ─── Periodic disk flush ────────────────────────────────────────
|
|
2116
2229
|
flushToDisk() {
|
|
2117
2230
|
const data = this.buildData();
|
|
@@ -2182,7 +2295,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2182
2295
|
return false;
|
|
2183
2296
|
};
|
|
2184
2297
|
const meaningfulNetwork = data.network.filter((n) => !isNoiseNetwork(n));
|
|
2185
|
-
const filtered = data.actions
|
|
2298
|
+
const filtered = data.actions;
|
|
2186
2299
|
const groups = [];
|
|
2187
2300
|
let current = null;
|
|
2188
2301
|
for (const action of filtered) {
|
|
@@ -2209,6 +2322,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2209
2322
|
);
|
|
2210
2323
|
const matchedInputs = inputAction ? this.matchActionToNetwork(inputAction, nearbyNetwork) : [];
|
|
2211
2324
|
const clickMatches = primary.type === "click" && primary.element?.text ? this.matchActionToNetwork(primary, nearbyNetwork) : [];
|
|
2325
|
+
const signals = primary.__signals || [];
|
|
2212
2326
|
steps.push({
|
|
2213
2327
|
step: steps.length + 1,
|
|
2214
2328
|
ref: getRef(primary),
|
|
@@ -2218,7 +2332,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2218
2332
|
responseBody: n.responseBody && JSON.stringify(n.responseBody).length > 1e3 ? "[truncated, " + JSON.stringify(n.responseBody).length + " bytes]" : n.responseBody
|
|
2219
2333
|
})),
|
|
2220
2334
|
contextChanges: nearbyContext,
|
|
2221
|
-
matchedInputs: [...matchedInputs, ...clickMatches]
|
|
2335
|
+
matchedInputs: [...matchedInputs, ...clickMatches],
|
|
2336
|
+
signals
|
|
2222
2337
|
});
|
|
2223
2338
|
}
|
|
2224
2339
|
return {
|
|
@@ -300,8 +300,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
300
300
|
async function forwardNetworkInspect(sessionName, id) {
|
|
301
301
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
302
302
|
}
|
|
303
|
-
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
304
|
-
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
303
|
+
async function forwardRecordStart(session, url, cdpEndpoint, stream) {
|
|
304
|
+
return rpcCall("record:start", { session, url, cdpEndpoint, stream }, 15e3);
|
|
305
305
|
}
|
|
306
306
|
async function forwardRecordStop(session, output) {
|
|
307
307
|
const params = { session };
|
package/dist/cli.js
CHANGED
|
@@ -27,10 +27,10 @@ import {
|
|
|
27
27
|
startDaemonProcess,
|
|
28
28
|
stopDaemonProcess,
|
|
29
29
|
version
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-YAUG3NSI.js";
|
|
31
31
|
import {
|
|
32
32
|
SessionRecorder
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-CH5ZQV64.js";
|
|
34
34
|
import {
|
|
35
35
|
addKnownIssue,
|
|
36
36
|
getKnowledgePath,
|
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
resolveLaunchOpts,
|
|
55
55
|
saveSessionDiskMeta,
|
|
56
56
|
setActivePage
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-74S7UUI2.js";
|
|
58
58
|
import "./chunk-TNEN6VQ2.js";
|
|
59
59
|
import {
|
|
60
60
|
errMsg
|
|
@@ -6333,6 +6333,22 @@ async function checkPluginLoginRequired(options) {
|
|
|
6333
6333
|
const requiresLogin = command.requiresLogin === true || site.config?.requiresLogin === true || loginConfig?.requiresLogin === true || command.loginRequired === "required";
|
|
6334
6334
|
if (!requiresLogin) return { ok: true };
|
|
6335
6335
|
const pluginName = site.name || "plugin";
|
|
6336
|
+
const siteConfig = site.config;
|
|
6337
|
+
const configIsLogin = siteConfig?.isLogin;
|
|
6338
|
+
if (configIsLogin) {
|
|
6339
|
+
try {
|
|
6340
|
+
const loggedIn = await configIsLogin(ctx);
|
|
6341
|
+
if (loggedIn) return { ok: true };
|
|
6342
|
+
return buildLoginRequired({
|
|
6343
|
+
plugin: pluginName,
|
|
6344
|
+
command: commandName,
|
|
6345
|
+
reason: "config.isLogin returned false",
|
|
6346
|
+
sessionName,
|
|
6347
|
+
loginConfig
|
|
6348
|
+
});
|
|
6349
|
+
} catch {
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6336
6352
|
if (typeof site.isLoggedIn === "function") {
|
|
6337
6353
|
try {
|
|
6338
6354
|
const loggedIn = await site.isLoggedIn(ctx);
|
|
@@ -7042,7 +7058,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7042
7058
|
}
|
|
7043
7059
|
let targetPageOverride = null;
|
|
7044
7060
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7045
|
-
const { findTargetPage } = await import("./browser-
|
|
7061
|
+
const { findTargetPage } = await import("./browser-JXIJF4VL.js");
|
|
7046
7062
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7047
7063
|
if (!targetPageOverride) {
|
|
7048
7064
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7059,7 +7075,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7059
7075
|
params = result.data;
|
|
7060
7076
|
}
|
|
7061
7077
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7062
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7078
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
7063
7079
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7064
7080
|
if (result) return result;
|
|
7065
7081
|
}
|
|
@@ -8471,7 +8487,7 @@ var PluginInstaller = class {
|
|
|
8471
8487
|
import { outputFormatter, OutputFormatter } from "@dyyz1993/xcli-core";
|
|
8472
8488
|
var formatter = new OutputFormatter();
|
|
8473
8489
|
function outputResult(result, mode = "text") {
|
|
8474
|
-
if (typeof result === "object" && result !== null) {
|
|
8490
|
+
if (mode !== "json" && mode !== "yaml" && typeof result === "object" && result !== null) {
|
|
8475
8491
|
const r = result;
|
|
8476
8492
|
if (r.success === false) {
|
|
8477
8493
|
outputError(r.message ? String(r.message) : "Unknown error");
|
|
@@ -10480,7 +10496,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10480
10496
|
} catch {
|
|
10481
10497
|
}
|
|
10482
10498
|
try {
|
|
10483
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10499
|
+
const { daemonPing } = await import("./daemon-client-CMRAWXTN.js");
|
|
10484
10500
|
if (await daemonPing()) {
|
|
10485
10501
|
await fetch("http://localhost:9224/rpc", {
|
|
10486
10502
|
method: "POST",
|
|
@@ -10638,7 +10654,8 @@ async function handleRecord(args, options, mode) {
|
|
|
10638
10654
|
const url = options.url;
|
|
10639
10655
|
const sessionName = options.session || "default";
|
|
10640
10656
|
const cdpEndpoint = options.cdp;
|
|
10641
|
-
const
|
|
10657
|
+
const stream = options.stream || "clean";
|
|
10658
|
+
const result = await forwardRecordStart(sessionName, url, cdpEndpoint, stream);
|
|
10642
10659
|
if (!result.ok) {
|
|
10643
10660
|
outputError(String(result.error || "Failed to start recording"));
|
|
10644
10661
|
return;
|
|
@@ -10933,7 +10950,15 @@ async function handleReplay(args, options, mode) {
|
|
|
10933
10950
|
const absPath = await import("path").then((p) => p.resolve(filePath));
|
|
10934
10951
|
const result = await forwardReplay(absPath, sessionName, slowMo);
|
|
10935
10952
|
if (!result.ok) {
|
|
10936
|
-
|
|
10953
|
+
if (mode === "json" || mode === "yaml") {
|
|
10954
|
+
outputResult({
|
|
10955
|
+
...result,
|
|
10956
|
+
success: false,
|
|
10957
|
+
message: result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"
|
|
10958
|
+
}, mode);
|
|
10959
|
+
} else {
|
|
10960
|
+
outputError(String(result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"));
|
|
10961
|
+
}
|
|
10937
10962
|
return;
|
|
10938
10963
|
}
|
|
10939
10964
|
outputResult(result, mode);
|
|
@@ -11037,7 +11062,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11037
11062
|
}
|
|
11038
11063
|
}
|
|
11039
11064
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11040
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11065
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RFOPX4SF.js");
|
|
11041
11066
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11042
11067
|
const { mkdirSync: mkdirSync10, writeFileSync: writeFileSync12 } = await import("fs");
|
|
11043
11068
|
const { join: join13 } = await import("path");
|
|
@@ -12974,7 +12999,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
12974
12999
|
}
|
|
12975
13000
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
12976
13001
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
12977
|
-
const { forwardExec } = await import("./daemon-client-
|
|
13002
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
12978
13003
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
12979
13004
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
12980
13005
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13014,7 +13039,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13014
13039
|
const targetPage = pages[cmdTabIndex];
|
|
13015
13040
|
await targetPage.bringToFront().catch(() => {
|
|
13016
13041
|
});
|
|
13017
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13042
|
+
const { setActivePage: setActivePage2 } = await import("./browser-JXIJF4VL.js");
|
|
13018
13043
|
setActivePage2(session, targetPage);
|
|
13019
13044
|
}
|
|
13020
13045
|
}
|
|
@@ -13290,7 +13315,7 @@ async function main() {
|
|
|
13290
13315
|
const command = process.argv[2];
|
|
13291
13316
|
const isLongRunning = command === "preview" || command === "serve";
|
|
13292
13317
|
if (!isLongRunning) {
|
|
13293
|
-
const { ensureProcessCanExit } = await import("./browser-
|
|
13318
|
+
const { ensureProcessCanExit } = await import("./browser-JXIJF4VL.js");
|
|
13294
13319
|
await ensureProcessCanExit().catch(() => {
|
|
13295
13320
|
});
|
|
13296
13321
|
process.exit(process.exitCode || exitCode);
|
|
@@ -179,8 +179,8 @@ async function forwardNetworkExport(sessionName, id, lang) {
|
|
|
179
179
|
async function forwardNetworkInspect(sessionName, id) {
|
|
180
180
|
return rpcCall("network:inspect", { session: sessionName, id }, 1e4);
|
|
181
181
|
}
|
|
182
|
-
async function forwardRecordStart(session, url, cdpEndpoint) {
|
|
183
|
-
return rpcCall("record:start", { session, url, cdpEndpoint }, 15e3);
|
|
182
|
+
async function forwardRecordStart(session, url, cdpEndpoint, stream) {
|
|
183
|
+
return rpcCall("record:start", { session, url, cdpEndpoint, stream }, 15e3);
|
|
184
184
|
}
|
|
185
185
|
async function forwardRecordStop(session, output) {
|
|
186
186
|
const params = { session };
|
package/dist/daemon-main.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./chunk-VEDJ5XSQ.js";
|
|
14
14
|
import {
|
|
15
15
|
SessionRecorder
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-C4O73DQL.js";
|
|
17
17
|
import {
|
|
18
18
|
closeEphemeralContext,
|
|
19
19
|
closeSessionByName,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveLaunchOpts,
|
|
30
30
|
saveSessionDiskMeta,
|
|
31
31
|
setActivePage
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-37SHZSUR.js";
|
|
33
33
|
import "./chunk-VJNMAWPZ.js";
|
|
34
34
|
import "./chunk-TNEN6VQ2.js";
|
|
35
35
|
import {
|
|
@@ -5850,6 +5850,22 @@ async function checkPluginLoginRequired(options) {
|
|
|
5850
5850
|
const requiresLogin = command.requiresLogin === true || site.config?.requiresLogin === true || loginConfig?.requiresLogin === true || command.loginRequired === "required";
|
|
5851
5851
|
if (!requiresLogin) return { ok: true };
|
|
5852
5852
|
const pluginName = site.name || "plugin";
|
|
5853
|
+
const siteConfig = site.config;
|
|
5854
|
+
const configIsLogin = siteConfig?.isLogin;
|
|
5855
|
+
if (configIsLogin) {
|
|
5856
|
+
try {
|
|
5857
|
+
const loggedIn = await configIsLogin(ctx);
|
|
5858
|
+
if (loggedIn) return { ok: true };
|
|
5859
|
+
return buildLoginRequired({
|
|
5860
|
+
plugin: pluginName,
|
|
5861
|
+
command: commandName,
|
|
5862
|
+
reason: "config.isLogin returned false",
|
|
5863
|
+
sessionName,
|
|
5864
|
+
loginConfig
|
|
5865
|
+
});
|
|
5866
|
+
} catch {
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5853
5869
|
if (typeof site.isLoggedIn === "function") {
|
|
5854
5870
|
try {
|
|
5855
5871
|
const loggedIn = await site.isLoggedIn(ctx);
|
|
@@ -6559,7 +6575,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6559
6575
|
}
|
|
6560
6576
|
let targetPageOverride = null;
|
|
6561
6577
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
6562
|
-
const { findTargetPage } = await import("./browser-
|
|
6578
|
+
const { findTargetPage } = await import("./browser-ALPLJNH2.js");
|
|
6563
6579
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
6564
6580
|
if (!targetPageOverride) {
|
|
6565
6581
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -6576,7 +6592,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
6576
6592
|
params = result.data;
|
|
6577
6593
|
}
|
|
6578
6594
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
6579
|
-
const { forwardExec } = await import("./daemon-client-
|
|
6595
|
+
const { forwardExec } = await import("./daemon-client-4HJENICO.js");
|
|
6580
6596
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
6581
6597
|
if (result) return result;
|
|
6582
6598
|
}
|
|
@@ -7997,6 +8013,7 @@ function createRPCHandler() {
|
|
|
7997
8013
|
const sessionName = params.session || "default";
|
|
7998
8014
|
const url = params.url;
|
|
7999
8015
|
const cdpEndpoint = params.cdpEndpoint;
|
|
8016
|
+
const stream = params.stream || "clean";
|
|
8000
8017
|
if (activeRecorders.has(sessionName)) {
|
|
8001
8018
|
return { ok: false, error: "Recording already in progress for session: " + sessionName };
|
|
8002
8019
|
}
|
|
@@ -8028,7 +8045,7 @@ function createRPCHandler() {
|
|
|
8028
8045
|
}
|
|
8029
8046
|
try {
|
|
8030
8047
|
const recorder = new SessionRecorder(session.context, session.page, sessionName);
|
|
8031
|
-
await recorder.start(url);
|
|
8048
|
+
await recorder.start(url, { stream });
|
|
8032
8049
|
activeRecorders.set(sessionName, recorder);
|
|
8033
8050
|
return { ok: true, session: sessionName, startUrl: url || session.page.url() };
|
|
8034
8051
|
} catch (e) {
|
|
@@ -8179,7 +8196,8 @@ function createRPCHandler() {
|
|
|
8179
8196
|
const isNewFormat = Array.isArray(parsed.actions);
|
|
8180
8197
|
if (isNewFormat) {
|
|
8181
8198
|
try {
|
|
8182
|
-
const
|
|
8199
|
+
const replayErrors = [];
|
|
8200
|
+
const { SessionReplayer } = await import("./session-replayer-3HEIJBJ2.js");
|
|
8183
8201
|
const replayer = new SessionReplayer({
|
|
8184
8202
|
page: session.page,
|
|
8185
8203
|
stepDelay: slowMo * 500,
|
|
@@ -8187,7 +8205,9 @@ function createRPCHandler() {
|
|
|
8187
8205
|
console.log(`[replay] Step ${index + 1}/${total}: ${action.type} ${action.element?.selector || action.url || ""}`);
|
|
8188
8206
|
},
|
|
8189
8207
|
onError: (action, error) => {
|
|
8190
|
-
|
|
8208
|
+
const msg = `[${action?.type || "unknown"}] ${error?.message || String(error)}`;
|
|
8209
|
+
console.error("[replay] Error at step:", msg);
|
|
8210
|
+
replayErrors.push({ eventIndex: -1, error: msg });
|
|
8191
8211
|
}
|
|
8192
8212
|
});
|
|
8193
8213
|
if (!Array.isArray(parsed.actions) || typeof parsed.startUrl !== "string") {
|
|
@@ -8203,7 +8223,7 @@ function createRPCHandler() {
|
|
|
8203
8223
|
duration,
|
|
8204
8224
|
eventsPlayed: result2.success,
|
|
8205
8225
|
totalEvents: result2.success + result2.failed + result2.skipped,
|
|
8206
|
-
errors:
|
|
8226
|
+
errors: replayErrors
|
|
8207
8227
|
};
|
|
8208
8228
|
} catch (e) {
|
|
8209
8229
|
const msg = e instanceof Error ? e.message : String(e);
|