@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.
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-SJLRCE6N.js";
23
+ } from "./chunk-Z7WC4P2M.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-HXLMMSU3.js";
23
+ } from "./chunk-OOJ2H7IL.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-CJJ564VK.js";
23
+ } from "./chunk-MSQIPXKE.js";
24
24
  import "./chunk-VJNMAWPZ.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
@@ -22,9 +22,33 @@ var DEFAULT_EXCLUDE_TYPES = [
22
22
  "mousedown"
23
23
  ];
24
24
  function filterRecording(inputPath, outputPath, excludeTypes) {
25
- const content = fs.readFileSync(inputPath, "utf-8");
26
- const recording = yaml.parse(content);
27
- const events = recording.actions || recording.events || [];
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-33UKWCIR.js");
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-BMIJ3ZJM.js");
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
- this.actions.push({
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
- const content = fs.readFileSync(filePath, "utf-8");
6
- const recording = yaml.parse(content);
7
- const events = recording.actions || recording.events || [];
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
- this.actions.push({
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-33UKWCIR.js");
4968
+ const { SessionRecorder } = await import("./session-recorder-KPU4YQAE.js");
4969
4969
  SessionRecorder.cleanup(session.name);
4970
4970
  } catch {
4971
4971
  }