@xbrowser/cli 1.8.7 → 1.9.1
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-XPKM344Y.js → browser-4KRHALJ3.js} +1 -1
- package/dist/{browser-LW2MDJE4.js → browser-6WJHQDPV.js} +1 -1
- package/dist/{browser-TLIDFFEG.js → browser-C63PFGPT.js} +1 -1
- package/dist/{chunk-GJAV3QGG.js → chunk-ANVL2ID2.js} +27 -3
- package/dist/{chunk-CJJ564VK.js → chunk-MSQIPXKE.js} +1 -1
- package/dist/{chunk-HXLMMSU3.js → chunk-OOJ2H7IL.js} +1 -1
- package/dist/{chunk-2QQDTXDL.js → chunk-PIWNMC36.js} +13 -1
- package/dist/{chunk-FL6DOSWV.js → chunk-TTZNR3QP.js} +30 -2
- package/dist/{chunk-MJFYLKGL.js → chunk-X46HDAOT.js} +27 -3
- package/dist/{chunk-RMYEHTLS.js → chunk-YSCY52UJ.js} +30 -2
- package/dist/{chunk-SJLRCE6N.js → chunk-Z7WC4P2M.js} +1 -1
- package/dist/cli.js +252 -138
- package/dist/daemon-main.js +162 -249
- package/dist/{extract-RM62AJXW.js → extract-DSU2RVMN.js} +1 -1
- package/dist/{extract-O46CC533.js → extract-EUWPRSKH.js} +27 -3
- package/dist/{filter-K6FGRJQU.js → filter-3DXC6432.js} +1 -1
- package/dist/{filter-TAAYMSYI.js → filter-7YOPVPVC.js} +27 -3
- package/dist/index.d.ts +11 -1
- package/dist/index.js +252 -138
- package/dist/{plugin-singleton-SYJF6BD6.js → plugin-singleton-FCNDN3FX.js} +1 -1
- package/dist/{session-recorder-33UKWCIR.js → session-recorder-KPU4YQAE.js} +1 -1
- package/dist/{session-recorder-BMIJ3ZJM.js → session-recorder-UTULJFTE.js} +1 -1
- package/package.json +3 -2
|
@@ -22,9 +22,33 @@ var DEFAULT_EXCLUDE_TYPES = [
|
|
|
22
22
|
"mousedown"
|
|
23
23
|
];
|
|
24
24
|
function filterRecording(inputPath, outputPath, excludeTypes) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
let recording;
|
|
26
|
+
try {
|
|
27
|
+
const content = fs.readFileSync(inputPath, "utf-8");
|
|
28
|
+
recording = yaml.parse(content);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error(`Failed to read "${inputPath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
|
|
31
|
+
}
|
|
32
|
+
if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
|
|
33
|
+
throw new Error(`"${inputPath}" does not contain a valid recording (expected a YAML object with events or actions).`);
|
|
34
|
+
}
|
|
35
|
+
let rawEvents = recording.events ?? [];
|
|
36
|
+
const rawActions = recording.actions;
|
|
37
|
+
if (Array.isArray(rawActions) && rawEvents.length === 0) {
|
|
38
|
+
rawEvents = rawActions.map((a) => {
|
|
39
|
+
const action = a;
|
|
40
|
+
const element = action.element ?? {};
|
|
41
|
+
return {
|
|
42
|
+
type: action.type,
|
|
43
|
+
selector: element.selector ?? action.selector,
|
|
44
|
+
data: { ...action.data, value: action.value, key: action.key },
|
|
45
|
+
scrollX: action.scrollX,
|
|
46
|
+
scrollY: action.scrollY
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
delete recording.actions;
|
|
50
|
+
}
|
|
51
|
+
const events = rawEvents;
|
|
28
52
|
const originalCount = events.length;
|
|
29
53
|
const exclude = excludeTypes || DEFAULT_EXCLUDE_TYPES;
|
|
30
54
|
const filteredEvents = events.filter((event) => {
|
|
@@ -656,7 +656,7 @@ async function closeSessionByName(name) {
|
|
|
656
656
|
} catch {
|
|
657
657
|
}
|
|
658
658
|
try {
|
|
659
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
659
|
+
const { SessionRecorder } = await import("./session-recorder-KPU4YQAE.js");
|
|
660
660
|
SessionRecorder.cleanup(session.name);
|
|
661
661
|
} catch {
|
|
662
662
|
}
|
|
@@ -2021,7 +2021,7 @@ async function closeSessionByName(name) {
|
|
|
2021
2021
|
} catch {
|
|
2022
2022
|
}
|
|
2023
2023
|
try {
|
|
2024
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
2024
|
+
const { SessionRecorder } = await import("./session-recorder-UTULJFTE.js");
|
|
2025
2025
|
SessionRecorder.cleanup(session.name);
|
|
2026
2026
|
} catch {
|
|
2027
2027
|
}
|
|
@@ -417,12 +417,24 @@ var XBrowserPluginLoader = class {
|
|
|
417
417
|
// src/utils/plugin-singleton.ts
|
|
418
418
|
var pluginLoader = null;
|
|
419
419
|
var pluginsScanned = false;
|
|
420
|
+
function silenceSchemaWarnings(fn) {
|
|
421
|
+
if (process.env.XBROWSER_DEBUG || process.env.VITEST_WORKER_ID) return fn();
|
|
422
|
+
const originalWarn = console.warn;
|
|
423
|
+
console.warn = (...args) => {
|
|
424
|
+
const msg = typeof args[0] === "string" ? args[0] : "";
|
|
425
|
+
if (msg.includes('has no "result" schema')) return;
|
|
426
|
+
originalWarn(...args);
|
|
427
|
+
};
|
|
428
|
+
return fn().finally(() => {
|
|
429
|
+
console.warn = originalWarn;
|
|
430
|
+
});
|
|
431
|
+
}
|
|
420
432
|
async function getPluginLoader() {
|
|
421
433
|
if (!pluginLoader) {
|
|
422
434
|
pluginLoader = new XBrowserPluginLoader();
|
|
423
435
|
}
|
|
424
436
|
if (!pluginsScanned) {
|
|
425
|
-
await pluginLoader.scanAndLoad();
|
|
437
|
+
await silenceSchemaWarnings(() => pluginLoader.scanAndLoad());
|
|
426
438
|
pluginsScanned = true;
|
|
427
439
|
}
|
|
428
440
|
return pluginLoader;
|
|
@@ -1639,7 +1639,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1639
1639
|
return this.actions.length;
|
|
1640
1640
|
}
|
|
1641
1641
|
/** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
|
|
1642
|
-
recordCommandAction(action) {
|
|
1642
|
+
async recordCommandAction(action) {
|
|
1643
1643
|
const normalizedType = action.type === "cdp-fill" ? "input" : action.type === "cdp-click" ? "click" : action.type;
|
|
1644
1644
|
const recent = this.actions[this.actions.length - 1];
|
|
1645
1645
|
if (recent && Date.now() - recent.timestamp < 1500) {
|
|
@@ -1653,7 +1653,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1653
1653
|
this.actionCounter++;
|
|
1654
1654
|
const ts = Date.now();
|
|
1655
1655
|
const actionUrl = action.url && action.url !== "about:blank" ? action.url : this.lastKnownUrl || this.page.url();
|
|
1656
|
-
|
|
1656
|
+
const actionToPush = {
|
|
1657
1657
|
id: this.actionCounter,
|
|
1658
1658
|
type: action.type,
|
|
1659
1659
|
timestamp: ts,
|
|
@@ -1661,6 +1661,11 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1661
1661
|
pageTitle: "",
|
|
1662
1662
|
element: action.element || (action.selector ? { tag: "", selector: action.selector, text: "" } : void 0),
|
|
1663
1663
|
value: action.value
|
|
1664
|
+
};
|
|
1665
|
+
this.actions.push(actionToPush);
|
|
1666
|
+
actionToPush.elementScreenshot = await this.captureElementScreenshot(this.page, {
|
|
1667
|
+
type: action.type,
|
|
1668
|
+
element: actionToPush.element
|
|
1664
1669
|
});
|
|
1665
1670
|
this.lastActionTs = ts;
|
|
1666
1671
|
this.cdpActionDedup = {
|
|
@@ -2319,6 +2324,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2319
2324
|
clickContext,
|
|
2320
2325
|
files: raw.files
|
|
2321
2326
|
});
|
|
2327
|
+
const pushed = this.actions[this.actions.length - 1];
|
|
2328
|
+
pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
|
|
2322
2329
|
this.lastActionTs = raw.ts;
|
|
2323
2330
|
if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
|
|
2324
2331
|
const detected = await this.detectCheckpoints(page);
|
|
@@ -2329,6 +2336,27 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
2329
2336
|
}
|
|
2330
2337
|
}
|
|
2331
2338
|
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Capture a screenshot of the target element for key action types.
|
|
2341
|
+
* Returns a base64-encoded PNG string, or undefined if the element has no
|
|
2342
|
+
* selector or the screenshot fails (non-critical — never blocks recording).
|
|
2343
|
+
*
|
|
2344
|
+
* Only captures for: click, input, change, dblclick, and their CDP counterparts.
|
|
2345
|
+
*/
|
|
2346
|
+
async captureElementScreenshot(page, action) {
|
|
2347
|
+
const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
|
|
2348
|
+
if (!keyTypes.includes(action.type)) return;
|
|
2349
|
+
if (!action.element?.selector) return;
|
|
2350
|
+
try {
|
|
2351
|
+
const buffer = await page.locator(action.element.selector).first().screenshot({
|
|
2352
|
+
type: "png",
|
|
2353
|
+
timeout: 2e3
|
|
2354
|
+
});
|
|
2355
|
+
return buffer.toString("base64");
|
|
2356
|
+
} catch {
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2332
2360
|
/**
|
|
2333
2361
|
* After a click, wait 300ms then scan for popover/dropdown/menu elements
|
|
2334
2362
|
* near the click position. This runs server-side to avoid race conditions
|
|
@@ -2,9 +2,33 @@
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as yaml from "yaml";
|
|
4
4
|
function extractRecording(filePath) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let recording;
|
|
6
|
+
try {
|
|
7
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
8
|
+
recording = yaml.parse(content);
|
|
9
|
+
} catch (e) {
|
|
10
|
+
throw new Error(`Failed to read "${filePath}": ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`);
|
|
11
|
+
}
|
|
12
|
+
if (recording === null || typeof recording !== "object" || Array.isArray(recording)) {
|
|
13
|
+
throw new Error(`"${filePath}" does not contain a valid recording (expected a YAML object with events or actions).`);
|
|
14
|
+
}
|
|
15
|
+
let rawEvents = recording.events ?? [];
|
|
16
|
+
const rawActions = recording.actions;
|
|
17
|
+
if (Array.isArray(rawActions) && rawEvents.length === 0) {
|
|
18
|
+
rawEvents = rawActions.map((a) => {
|
|
19
|
+
const action = a;
|
|
20
|
+
const element = action.element ?? {};
|
|
21
|
+
return {
|
|
22
|
+
type: action.type,
|
|
23
|
+
selector: element.selector ?? action.selector,
|
|
24
|
+
tagName: element.tag ?? action.tagName,
|
|
25
|
+
data: { ...action.data, value: action.value, key: action.key },
|
|
26
|
+
timestamp: action.timestamp,
|
|
27
|
+
pageState: action.pageState
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const events = rawEvents;
|
|
8
32
|
const keyEvents = [];
|
|
9
33
|
const eventTypes = {};
|
|
10
34
|
for (const event of events) {
|
|
@@ -1242,7 +1242,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1242
1242
|
return this.actions.length;
|
|
1243
1243
|
}
|
|
1244
1244
|
/** Record an action triggered by a CDP command (e.g. xbrowser fill/click/goto) */
|
|
1245
|
-
recordCommandAction(action) {
|
|
1245
|
+
async recordCommandAction(action) {
|
|
1246
1246
|
const normalizedType = action.type === "cdp-fill" ? "input" : action.type === "cdp-click" ? "click" : action.type;
|
|
1247
1247
|
const recent = this.actions[this.actions.length - 1];
|
|
1248
1248
|
if (recent && Date.now() - recent.timestamp < 1500) {
|
|
@@ -1256,7 +1256,7 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1256
1256
|
this.actionCounter++;
|
|
1257
1257
|
const ts = Date.now();
|
|
1258
1258
|
const actionUrl = action.url && action.url !== "about:blank" ? action.url : this.lastKnownUrl || this.page.url();
|
|
1259
|
-
|
|
1259
|
+
const actionToPush = {
|
|
1260
1260
|
id: this.actionCounter,
|
|
1261
1261
|
type: action.type,
|
|
1262
1262
|
timestamp: ts,
|
|
@@ -1264,6 +1264,11 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1264
1264
|
pageTitle: "",
|
|
1265
1265
|
element: action.element || (action.selector ? { tag: "", selector: action.selector, text: "" } : void 0),
|
|
1266
1266
|
value: action.value
|
|
1267
|
+
};
|
|
1268
|
+
this.actions.push(actionToPush);
|
|
1269
|
+
actionToPush.elementScreenshot = await this.captureElementScreenshot(this.page, {
|
|
1270
|
+
type: action.type,
|
|
1271
|
+
element: actionToPush.element
|
|
1267
1272
|
});
|
|
1268
1273
|
this.lastActionTs = ts;
|
|
1269
1274
|
this.cdpActionDedup = {
|
|
@@ -1922,6 +1927,8 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1922
1927
|
clickContext,
|
|
1923
1928
|
files: raw.files
|
|
1924
1929
|
});
|
|
1930
|
+
const pushed = this.actions[this.actions.length - 1];
|
|
1931
|
+
pushed.elementScreenshot = await this.captureElementScreenshot(page, raw);
|
|
1925
1932
|
this.lastActionTs = raw.ts;
|
|
1926
1933
|
if (raw.type === "click" || raw.type === "navigate" || raw.type === "submit") {
|
|
1927
1934
|
const detected = await this.detectCheckpoints(page);
|
|
@@ -1932,6 +1939,27 @@ var SessionRecorder = class _SessionRecorder {
|
|
|
1932
1939
|
}
|
|
1933
1940
|
}
|
|
1934
1941
|
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Capture a screenshot of the target element for key action types.
|
|
1944
|
+
* Returns a base64-encoded PNG string, or undefined if the element has no
|
|
1945
|
+
* selector or the screenshot fails (non-critical — never blocks recording).
|
|
1946
|
+
*
|
|
1947
|
+
* Only captures for: click, input, change, dblclick, and their CDP counterparts.
|
|
1948
|
+
*/
|
|
1949
|
+
async captureElementScreenshot(page, action) {
|
|
1950
|
+
const keyTypes = ["click", "input", "change", "dblclick", "cdp-click", "cdp-fill", "filechooser"];
|
|
1951
|
+
if (!keyTypes.includes(action.type)) return;
|
|
1952
|
+
if (!action.element?.selector) return;
|
|
1953
|
+
try {
|
|
1954
|
+
const buffer = await page.locator(action.element.selector).first().screenshot({
|
|
1955
|
+
type: "png",
|
|
1956
|
+
timeout: 2e3
|
|
1957
|
+
});
|
|
1958
|
+
return buffer.toString("base64");
|
|
1959
|
+
} catch {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1935
1963
|
/**
|
|
1936
1964
|
* After a click, wait 300ms then scan for popover/dropdown/menu elements
|
|
1937
1965
|
* near the click position. This runs server-side to avoid race conditions
|
|
@@ -4965,7 +4965,7 @@ async function closeSessionByName(name) {
|
|
|
4965
4965
|
} catch {
|
|
4966
4966
|
}
|
|
4967
4967
|
try {
|
|
4968
|
-
const { SessionRecorder } = await import("./session-recorder-
|
|
4968
|
+
const { SessionRecorder } = await import("./session-recorder-KPU4YQAE.js");
|
|
4969
4969
|
SessionRecorder.cleanup(session.name);
|
|
4970
4970
|
} catch {
|
|
4971
4971
|
}
|