@xbrowser/cli 1.9.5 → 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 +22 -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 +12 -8
- package/dist/index.d.ts +24 -3
- package/dist/index.js +21 -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
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
startDaemonProcess,
|
|
45
45
|
stopDaemonProcess,
|
|
46
46
|
version
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-YAUG3NSI.js";
|
|
48
48
|
import {
|
|
49
49
|
generateBashScript,
|
|
50
50
|
generateJSScript,
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
} from "./chunk-ANVL2ID2.js";
|
|
62
62
|
import {
|
|
63
63
|
SessionRecorder
|
|
64
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-CH5ZQV64.js";
|
|
65
65
|
import {
|
|
66
66
|
addKnownIssue,
|
|
67
67
|
getKnowledgePath,
|
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
resolveLaunchOpts,
|
|
87
87
|
saveSessionDiskMeta,
|
|
88
88
|
setActivePage
|
|
89
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-5JSOGINQ.js";
|
|
90
90
|
import "./chunk-VJNMAWPZ.js";
|
|
91
91
|
import "./chunk-TNEN6VQ2.js";
|
|
92
92
|
import {
|
|
@@ -7406,7 +7406,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7406
7406
|
}
|
|
7407
7407
|
let targetPageOverride = null;
|
|
7408
7408
|
if (_target && extraOpts?.cdpEndpoint) {
|
|
7409
|
-
const { findTargetPage } = await import("./browser-
|
|
7409
|
+
const { findTargetPage } = await import("./browser-ZPQP6VW7.js");
|
|
7410
7410
|
targetPageOverride = await findTargetPage(extraOpts.cdpEndpoint, _target);
|
|
7411
7411
|
if (!targetPageOverride) {
|
|
7412
7412
|
return errorResult(`Target "${_target}" not found. Use 'xbrowser targets --cdp ${extraOpts.cdpEndpoint}' to list available pages.`);
|
|
@@ -7423,7 +7423,7 @@ async function executeCommand(commandName, params, sessionName = "default", extr
|
|
|
7423
7423
|
params = result.data;
|
|
7424
7424
|
}
|
|
7425
7425
|
if (command.scope !== "cli" && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
7426
|
-
const { forwardExec } = await import("./daemon-client-
|
|
7426
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
7427
7427
|
const result = await forwardExec(commandName, params, sessionName, extraOpts?.cdpEndpoint);
|
|
7428
7428
|
if (result) return result;
|
|
7429
7429
|
}
|
|
@@ -8868,7 +8868,7 @@ var PluginInstaller = class {
|
|
|
8868
8868
|
import { outputFormatter, OutputFormatter } from "@dyyz1993/xcli-core";
|
|
8869
8869
|
var formatter = new OutputFormatter();
|
|
8870
8870
|
function outputResult(result, mode = "text") {
|
|
8871
|
-
if (typeof result === "object" && result !== null) {
|
|
8871
|
+
if (mode !== "json" && mode !== "yaml" && typeof result === "object" && result !== null) {
|
|
8872
8872
|
const r = result;
|
|
8873
8873
|
if (r.success === false) {
|
|
8874
8874
|
outputError(r.message ? String(r.message) : "Unknown error");
|
|
@@ -10882,7 +10882,7 @@ async function handlePlugin(args, options, mode) {
|
|
|
10882
10882
|
} catch {
|
|
10883
10883
|
}
|
|
10884
10884
|
try {
|
|
10885
|
-
const { daemonPing } = await import("./daemon-client-
|
|
10885
|
+
const { daemonPing } = await import("./daemon-client-CMRAWXTN.js");
|
|
10886
10886
|
if (await daemonPing()) {
|
|
10887
10887
|
await fetch("http://localhost:9224/rpc", {
|
|
10888
10888
|
method: "POST",
|
|
@@ -11040,7 +11040,8 @@ async function handleRecord(args, options, mode) {
|
|
|
11040
11040
|
const url = options.url;
|
|
11041
11041
|
const sessionName = options.session || "default";
|
|
11042
11042
|
const cdpEndpoint = options.cdp;
|
|
11043
|
-
const
|
|
11043
|
+
const stream = options.stream || "clean";
|
|
11044
|
+
const result = await forwardRecordStart(sessionName, url, cdpEndpoint, stream);
|
|
11044
11045
|
if (!result.ok) {
|
|
11045
11046
|
outputError(String(result.error || "Failed to start recording"));
|
|
11046
11047
|
return;
|
|
@@ -11335,7 +11336,15 @@ async function handleReplay(args, options, mode) {
|
|
|
11335
11336
|
const absPath = await import("path").then((p) => p.resolve(filePath));
|
|
11336
11337
|
const result = await forwardReplay(absPath, sessionName, slowMo);
|
|
11337
11338
|
if (!result.ok) {
|
|
11338
|
-
|
|
11339
|
+
if (mode === "json" || mode === "yaml") {
|
|
11340
|
+
outputResult({
|
|
11341
|
+
...result,
|
|
11342
|
+
success: false,
|
|
11343
|
+
message: result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"
|
|
11344
|
+
}, mode);
|
|
11345
|
+
} else {
|
|
11346
|
+
outputError(String(result.errors ? result.errors.map((e) => e.error).join("; ") : result.error || "Replay failed"));
|
|
11347
|
+
}
|
|
11339
11348
|
return;
|
|
11340
11349
|
}
|
|
11341
11350
|
outputResult(result, mode);
|
|
@@ -11439,7 +11448,7 @@ async function handleFilter(args, _mode, options) {
|
|
|
11439
11448
|
}
|
|
11440
11449
|
}
|
|
11441
11450
|
async function handleGeneratePlugin(sessionName, pluginName, outputDir) {
|
|
11442
|
-
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-
|
|
11451
|
+
const { SessionRecorder: SessionRecorder2 } = await import("./session-recorder-RFOPX4SF.js");
|
|
11443
11452
|
const { readSiteKnowledge: readSiteKnowledge2, toMarkdown } = await import("./site-knowledge-SYC6VCDB.js");
|
|
11444
11453
|
const { mkdirSync: mkdirSync11, writeFileSync: writeFileSync13 } = await import("fs");
|
|
11445
11454
|
const { join: join14 } = await import("path");
|
|
@@ -13376,7 +13385,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13376
13385
|
}
|
|
13377
13386
|
const needsBrowser = cmdEntry.scope === "page" || cmdEntry.scope === "browser";
|
|
13378
13387
|
if (needsBrowser && !process.env.XBROWSER_DAEMON_WORKER) {
|
|
13379
|
-
const { forwardExec } = await import("./daemon-client-
|
|
13388
|
+
const { forwardExec } = await import("./daemon-client-CMRAWXTN.js");
|
|
13380
13389
|
const userTimeout = typeof params.timeout === "number" && params.timeout > 0 ? params.timeout * 1e3 + 3e4 : void 0;
|
|
13381
13390
|
const result = await forwardExec(`${command}.${subCommand}`, params, sessionName, cdpEndpoint, userTimeout);
|
|
13382
13391
|
const resultData = result && typeof result === "object" && "data" in result ? result.data : void 0;
|
|
@@ -13416,7 +13425,7 @@ Run "xbrowser ${command} ${subCommand} --help" to see available parameters.`
|
|
|
13416
13425
|
const targetPage = pages[cmdTabIndex];
|
|
13417
13426
|
await targetPage.bringToFront().catch(() => {
|
|
13418
13427
|
});
|
|
13419
|
-
const { setActivePage: setActivePage2 } = await import("./browser-
|
|
13428
|
+
const { setActivePage: setActivePage2 } = await import("./browser-ZPQP6VW7.js");
|
|
13420
13429
|
setActivePage2(session, targetPage);
|
|
13421
13430
|
}
|
|
13422
13431
|
}
|
|
@@ -56,15 +56,17 @@ var SessionReplayer = class {
|
|
|
56
56
|
await this.replayTrajectory(action.trajectory);
|
|
57
57
|
}
|
|
58
58
|
await this.replayAction(action);
|
|
59
|
+
if (action.type !== "resize" && action.type !== "clipboard" && action.type !== "visibility") {
|
|
60
|
+
try {
|
|
61
|
+
await this.page.waitForLoadState("domcontentloaded", this.opts.stepTimeout);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
59
65
|
success++;
|
|
60
66
|
} catch (e) {
|
|
61
67
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
62
68
|
this.opts.onError?.(action, err);
|
|
63
|
-
|
|
64
|
-
skipped++;
|
|
65
|
-
} else {
|
|
66
|
-
failed++;
|
|
67
|
-
}
|
|
69
|
+
failed++;
|
|
68
70
|
}
|
|
69
71
|
if (i < actions.length - 1) {
|
|
70
72
|
await new Promise((r) => setTimeout(r, this.opts.stepDelay));
|
|
@@ -78,41 +80,36 @@ var SessionReplayer = class {
|
|
|
78
80
|
const timeout = this.opts.stepTimeout;
|
|
79
81
|
switch (action.type) {
|
|
80
82
|
case "navigation":
|
|
81
|
-
await page.goto(action.url, { waitUntil: "
|
|
83
|
+
await page.goto(action.url, { waitUntil: "load", timeout });
|
|
82
84
|
break;
|
|
83
85
|
case "goto":
|
|
84
|
-
await page.goto(action.url, { waitUntil: "
|
|
86
|
+
await page.goto(action.url, { waitUntil: "load", timeout });
|
|
85
87
|
break;
|
|
86
88
|
case "click":
|
|
87
89
|
case "cdp-click": {
|
|
88
|
-
const selector = this.
|
|
89
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
90
|
+
const selector = await this.resolveAndWait(action);
|
|
90
91
|
await page.click(selector, { timeout });
|
|
91
92
|
break;
|
|
92
93
|
}
|
|
93
94
|
case "input": {
|
|
94
|
-
const selector = this.
|
|
95
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
95
|
+
const selector = await this.resolveAndWait(action);
|
|
96
96
|
await page.fill(selector, action.value ?? "", { timeout });
|
|
97
97
|
break;
|
|
98
98
|
}
|
|
99
99
|
case "cdp-fill": {
|
|
100
|
-
const selector = this.
|
|
101
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
100
|
+
const selector = await this.resolveAndWait(action);
|
|
102
101
|
await page.fill(selector, action.value ?? "", { timeout });
|
|
103
102
|
break;
|
|
104
103
|
}
|
|
105
104
|
case "change": {
|
|
106
|
-
const selector = this.
|
|
107
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
105
|
+
const selector = await this.resolveAndWait(action);
|
|
108
106
|
if (action.value) {
|
|
109
107
|
await page.selectOption(selector, action.value);
|
|
110
108
|
}
|
|
111
109
|
break;
|
|
112
110
|
}
|
|
113
111
|
case "filechooser": {
|
|
114
|
-
const selector = this.
|
|
115
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
112
|
+
const selector = await this.resolveAndWait(action);
|
|
116
113
|
const files = this.resolveFiles(action);
|
|
117
114
|
if (files.length > 0) {
|
|
118
115
|
await page.setInputFiles(selector, files);
|
|
@@ -155,11 +152,8 @@ var SessionReplayer = class {
|
|
|
155
152
|
break;
|
|
156
153
|
}
|
|
157
154
|
case "hover": {
|
|
158
|
-
const selector = this.
|
|
159
|
-
|
|
160
|
-
await page.waitForSelector(selector, { state: "visible", timeout });
|
|
161
|
-
await page.hover(selector);
|
|
162
|
-
}
|
|
155
|
+
const selector = await this.resolveAndWait(action);
|
|
156
|
+
await page.hover(selector);
|
|
163
157
|
break;
|
|
164
158
|
}
|
|
165
159
|
case "drag": {
|
|
@@ -199,10 +193,9 @@ var SessionReplayer = class {
|
|
|
199
193
|
break;
|
|
200
194
|
}
|
|
201
195
|
case "focus": {
|
|
202
|
-
const selector = this.
|
|
203
|
-
if (
|
|
204
|
-
await page.
|
|
205
|
-
await page.click(selector, { timeout });
|
|
196
|
+
const selector = await this.resolveAndWait(action);
|
|
197
|
+
if (action.focus?.focusType === "focus") {
|
|
198
|
+
await page.locator(selector).focus();
|
|
206
199
|
}
|
|
207
200
|
break;
|
|
208
201
|
}
|
|
@@ -210,10 +203,16 @@ var SessionReplayer = class {
|
|
|
210
203
|
break;
|
|
211
204
|
}
|
|
212
205
|
case "submit": {
|
|
213
|
-
const selector = this.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
206
|
+
const selector = await this.resolveAndWait(action);
|
|
207
|
+
await page.evaluate((sel) => {
|
|
208
|
+
const form = document.querySelector(sel);
|
|
209
|
+
if (!form) return;
|
|
210
|
+
if (typeof form.requestSubmit === "function") {
|
|
211
|
+
form.requestSubmit();
|
|
212
|
+
} else {
|
|
213
|
+
form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
|
|
214
|
+
}
|
|
215
|
+
}, selector);
|
|
217
216
|
break;
|
|
218
217
|
}
|
|
219
218
|
case "scroll": {
|
|
@@ -239,12 +238,10 @@ var SessionReplayer = class {
|
|
|
239
238
|
await page.mouse.move(x, y);
|
|
240
239
|
}
|
|
241
240
|
}
|
|
242
|
-
/** Resolve the best selector for an action */
|
|
241
|
+
/** Resolve the best selector for an action (primary selector only) */
|
|
243
242
|
resolveSelector(action) {
|
|
244
243
|
const el = action.element;
|
|
245
244
|
if (!el) return "";
|
|
246
|
-
if (el.textFallback) {
|
|
247
|
-
}
|
|
248
245
|
if (el.selector) {
|
|
249
246
|
return el.selector;
|
|
250
247
|
}
|
|
@@ -253,6 +250,41 @@ var SessionReplayer = class {
|
|
|
253
250
|
}
|
|
254
251
|
return "";
|
|
255
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* X2: Wait for an element using the best available selector, with
|
|
255
|
+
* confidence-based fallback support.
|
|
256
|
+
*
|
|
257
|
+
* Returns the first matching selector, or throws if none match.
|
|
258
|
+
* Fallback order:
|
|
259
|
+
* 1. Primary CSS selector (always tried first)
|
|
260
|
+
* 2. textFallback selector (for low-confidence selectors when primary fails)
|
|
261
|
+
* 3. Tag-based fallback (last resort)
|
|
262
|
+
*/
|
|
263
|
+
async resolveAndWait(action) {
|
|
264
|
+
const el = action.element;
|
|
265
|
+
if (!el) throw new Error("No element metadata");
|
|
266
|
+
const page = this.page;
|
|
267
|
+
const timeout = this.opts.stepTimeout;
|
|
268
|
+
const candidates = [];
|
|
269
|
+
if (el.selector) candidates.push(el.selector);
|
|
270
|
+
if (el.confidence === "low" && el.textFallback?.selector) {
|
|
271
|
+
if (!candidates.includes(el.textFallback.selector)) {
|
|
272
|
+
candidates.push(el.textFallback.selector);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (el.tag && !candidates.includes(el.tag)) {
|
|
276
|
+
candidates.push(el.tag);
|
|
277
|
+
}
|
|
278
|
+
if (candidates.length === 0) throw new Error("No selector available for element");
|
|
279
|
+
for (const sel of candidates) {
|
|
280
|
+
try {
|
|
281
|
+
await page.waitForSelector(sel, { state: "visible", timeout });
|
|
282
|
+
return sel;
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
throw new Error(`Element not found, tried: ${candidates.join(", ")}`);
|
|
287
|
+
}
|
|
256
288
|
/** Resolve file payloads from a filechooser action */
|
|
257
289
|
resolveFiles(action) {
|
|
258
290
|
if (!action.files?.fileData) return [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xbrowser/cli",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.6",
|
|
4
4
|
"description": "Browser automation CLI for web scraping, headless browsing, SEO analysis, and AI agent workflows. A command-line alternative to Playwright, Puppeteer, and Selenium.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|