cbrowser 19.4.3 ā 19.6.0
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/THIRD_PARTY_LICENSES.md +39 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +34 -7
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +147 -95
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts +5 -0
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +5 -0
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +4 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +30 -7
- package/dist/daemon.js.map +1 -1
- package/dist/device-names.d.ts +10 -0
- package/dist/device-names.d.ts.map +1 -0
- package/dist/device-names.js +16 -0
- package/dist/device-names.js.map +1 -0
- package/dist/device-presets-devtools.d.ts +3 -0
- package/dist/device-presets-devtools.d.ts.map +1 -0
- package/dist/device-presets-devtools.js +823 -0
- package/dist/device-presets-devtools.js.map +1 -0
- package/dist/devices.d.ts +15 -0
- package/dist/devices.d.ts.map +1 -0
- package/dist/devices.js +196 -0
- package/dist/devices.js.map +1 -0
- package/dist/eval-script.d.ts +29 -0
- package/dist/eval-script.d.ts.map +1 -0
- package/dist/eval-script.js +46 -0
- package/dist/eval-script.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +1 -1
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/terminal-safe.d.ts +12 -0
- package/dist/terminal-safe.d.ts.map +1 -0
- package/dist/terminal-safe.js +46 -0
- package/dist/terminal-safe.js.map +1 -0
- package/dist/testing/nl-test-suite.d.ts +7 -0
- package/dist/testing/nl-test-suite.d.ts.map +1 -1
- package/dist/testing/nl-test-suite.js +3 -1
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -99
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,8 @@ import { capturePerformanceBaseline, listPerformanceBaselines, loadPerformanceBa
|
|
|
20
20
|
// Visual module imports
|
|
21
21
|
import { captureVisualBaseline, listVisualBaselines, getVisualBaseline, deleteVisualBaseline, runVisualRegression, runVisualRegressionSuite, formatVisualRegressionReport, generateVisualRegressionHtmlReport, runCrossBrowserTest, runCrossBrowserSuite, formatCrossBrowserReport, generateCrossBrowserHtmlReport, runResponsiveTest, runResponsiveSuite, formatResponsiveReport, generateResponsiveHtmlReport, listViewportPresets, runABComparison, runABSuite, formatABReport, generateABHtmlReport, crossBrowserDiff } from "./visual/index.js";
|
|
22
22
|
import { BUILTIN_PERSONAS, loadCustomPersonas, saveCustomPersona, deleteCustomPersona, isBuiltinPersona, generatePersonaFromDescription, listAccessibilityPersonas, } from "./personas.js";
|
|
23
|
-
import {
|
|
23
|
+
import { LOCATION_PRESETS } from "./types.js";
|
|
24
|
+
import { CBROWSER_DEVICE_KEYS, DEVICE_PRESETS, resolveDevice } from "./devices.js";
|
|
24
25
|
import { startMcpServer } from "./mcp-server.js";
|
|
25
26
|
import { startRemoteMcpServer } from "./mcp-server-remote.js";
|
|
26
27
|
import { startDaemon, stopDaemon, getDaemonStatus, isDaemonRunning, sendToDaemon, runDaemonServer } from "./daemon.js";
|
|
@@ -29,6 +30,8 @@ import { getStatusInfo, formatStatus, getDataDir, getPaths } from "./config.js";
|
|
|
29
30
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
30
31
|
import { resolveCookieDomain, hostFromUrl } from "./utils.js";
|
|
31
32
|
import { dirname, join, resolve } from "node:path";
|
|
33
|
+
import { guardConsole, guardTerminalOutput } from "./terminal-safe.js";
|
|
34
|
+
import { evaluateScript } from "./eval-script.js";
|
|
32
35
|
import { VideoCaptureSession } from "./recording/engine.js";
|
|
33
36
|
import { changeSignal, parseManifest } from "./recording/types.js";
|
|
34
37
|
import { printEnterpriseStatus } from "./stealth/index.js";
|
|
@@ -298,7 +301,7 @@ COOKIE MANAGEMENT
|
|
|
298
301
|
cookie clear Clear all cookies
|
|
299
302
|
|
|
300
303
|
DEVICE EMULATION
|
|
301
|
-
device list
|
|
304
|
+
device list [filter] List device presets, one per line (filter by name)
|
|
302
305
|
device set <name> Set device emulation for session
|
|
303
306
|
Example: cbrowser device set iphone-15
|
|
304
307
|
|
|
@@ -345,6 +348,10 @@ NETWORK / HAR (need the daemon: each command is its own process)
|
|
|
345
348
|
har stop [output] Stop and save HAR file
|
|
346
349
|
network list List network requests the daemon's browser captured
|
|
347
350
|
network clear Clear that list
|
|
351
|
+
console List the daemon tab's console messages (errors, warnings, logs)
|
|
352
|
+
--filter <type> all | error | warning | log | info (default all)
|
|
353
|
+
--clear Empty the console buffer after reading
|
|
354
|
+
--json Print the raw messages as JSON
|
|
348
355
|
|
|
349
356
|
VISUAL REGRESSION (v2.5.0)
|
|
350
357
|
visual save <name> Save baseline screenshot
|
|
@@ -740,6 +747,8 @@ CLAUDE SKILL INSTALLATION & SYNC
|
|
|
740
747
|
DAEMON MODE (v6.4.0)
|
|
741
748
|
daemon start Start background daemon (keeps browser running)
|
|
742
749
|
--port <port> Daemon port (default: 9222)
|
|
750
|
+
--device <name> Device the daemon's browser emulates
|
|
751
|
+
--viewport <WxH> Page size of the daemon's browser
|
|
743
752
|
--timeout <min> Idle timeout in minutes (default: 30)
|
|
744
753
|
daemon stop Stop the running daemon
|
|
745
754
|
daemon status Check if daemon is running
|
|
@@ -791,7 +800,9 @@ STORAGE & CLEANUP
|
|
|
791
800
|
|
|
792
801
|
OPTIONS
|
|
793
802
|
--browser <type> Browser: chromium, firefox, webkit (default: chromium)
|
|
794
|
-
--device <name> Device preset: iphone-
|
|
803
|
+
--device <name> Device preset: iphone-16, pixel-9, ipad-pro-13, ... ('device list')
|
|
804
|
+
--viewport <WxH> Page size, e.g. 390x844; with --device it sets the size and
|
|
805
|
+
the device keeps its pixel ratio, touch and user agent
|
|
795
806
|
--geo <location> Location preset or lat,lon coordinates
|
|
796
807
|
--locale <locale> Browser locale (e.g., en-US, fr-FR)
|
|
797
808
|
--timezone <tz> Timezone (e.g., America/New_York)
|
|
@@ -970,10 +981,17 @@ const DRIVES_THE_PAGE = [
|
|
|
970
981
|
"keyboard", "press", "type", "evaluate", "eval", "drag", "upload",
|
|
971
982
|
"smart-click", "dismiss-overlay", "assert",
|
|
972
983
|
];
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
984
|
+
/**
|
|
985
|
+
* Flags the shared CBrowser construction consumes, for every command that uses
|
|
986
|
+
* it. One list, spread into each command's set below: when this list was copied
|
|
987
|
+
* into each set, --viewport (2026-10-21) reached some commands and was refused
|
|
988
|
+
* by evaluate and keyboard.
|
|
989
|
+
*/
|
|
990
|
+
const SHARED_BROWSER_FLAGS = [
|
|
991
|
+
"url", "browser", "device", "viewport", "headless", "persistent", "restore",
|
|
992
|
+
"locale", "timezone", "geo", "verbose",
|
|
976
993
|
];
|
|
994
|
+
const GLOBAL_FLAGS = [...SHARED_BROWSER_FLAGS, "help", "no-color", "quiet", "json"];
|
|
977
995
|
/**
|
|
978
996
|
* Declared options per command.
|
|
979
997
|
*
|
|
@@ -1141,29 +1159,24 @@ const CAPTURE_FLAGS = new Set([
|
|
|
1141
1159
|
"contact-sheet", "attention-overlay", "overlay-persona", "overlay-goal",
|
|
1142
1160
|
"after", "after-element", "after-delay",
|
|
1143
1161
|
"until-element", "until-element-gone", "until-idle", "timeout",
|
|
1144
|
-
|
|
1145
|
-
"url", "browser", "device", "headless", "persistent", "restore",
|
|
1146
|
-
"locale", "timezone", "geo", "verbose",
|
|
1162
|
+
...SHARED_BROWSER_FLAGS,
|
|
1147
1163
|
]);
|
|
1148
1164
|
/**
|
|
1149
1165
|
* Resolve `--device` to a real preset key, or exit naming the bad value.
|
|
1150
1166
|
*
|
|
1151
|
-
*
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1167
|
+
* `capture start` checks here, before it builds anything. Every other command
|
|
1168
|
+
* gets the same check from CBrowser.launch() (src/devices.ts resolveDevice);
|
|
1169
|
+
* before 2026-10-21 the browser skipped names it did not know and rendered
|
|
1170
|
+
* 1280x800 desktop. Case and spaces do not matter: "iPhone 15" is "iphone-15".
|
|
1155
1171
|
*/
|
|
1156
1172
|
function resolveDevicePreset(value) {
|
|
1157
|
-
const
|
|
1158
|
-
if (
|
|
1159
|
-
return
|
|
1160
|
-
const known = Object.keys(DEVICE_PRESETS);
|
|
1161
|
-
const stem = normalised.replace(/-?\d+.*$/, "");
|
|
1162
|
-
const close = known.filter((k) => k.startsWith(stem) && stem.length > 0);
|
|
1173
|
+
const resolved = resolveDevice(value);
|
|
1174
|
+
if ("key" in resolved)
|
|
1175
|
+
return resolved.key;
|
|
1163
1176
|
console.error(`Error: unknown --device "${value}"`);
|
|
1164
|
-
if (
|
|
1165
|
-
console.error(` Did you mean: ${
|
|
1166
|
-
console.error(`
|
|
1177
|
+
if (resolved.suggestions.length > 0)
|
|
1178
|
+
console.error(` Did you mean: ${resolved.suggestions.join(", ")}?`);
|
|
1179
|
+
console.error(` 'cbrowser device list' shows all ${Object.keys(DEVICE_PRESETS).length} presets; --viewport WIDTHxHEIGHT sets any size.`);
|
|
1167
1180
|
process.exit(1);
|
|
1168
1181
|
}
|
|
1169
1182
|
/** Reject any flag `capture start` cannot honour, naming it. */
|
|
@@ -1747,8 +1760,7 @@ function reportAutoCapture(capture) {
|
|
|
1747
1760
|
/** Flags `evaluate` understands; anything else is rejected rather than ignored. */
|
|
1748
1761
|
const EVALUATE_FLAGS = new Set([
|
|
1749
1762
|
"file", "arg", "json", "raw", "wait-for", "timeout", "expect-truthy", "force",
|
|
1750
|
-
|
|
1751
|
-
"locale", "timezone", "geo", "verbose",
|
|
1763
|
+
...SHARED_BROWSER_FLAGS,
|
|
1752
1764
|
]);
|
|
1753
1765
|
/**
|
|
1754
1766
|
* `--force` is a bare flag, but the parser gives any flag the next non-flag
|
|
@@ -1781,53 +1793,6 @@ function repeatedFlag(name) {
|
|
|
1781
1793
|
}
|
|
1782
1794
|
return values;
|
|
1783
1795
|
}
|
|
1784
|
-
/**
|
|
1785
|
-
* Evaluate a script in the page, accepting all three shapes people write.
|
|
1786
|
-
*
|
|
1787
|
-
* `--file` exists because multi-line scripts die in shell quoting, so it has to
|
|
1788
|
-
* accept real scripts - `const`, `let`, an explicit `return` - not only a
|
|
1789
|
-
* single expression. The source is compiled inside the page as a function
|
|
1790
|
-
* body: a bare expression gets an implicit return, a statement body keeps its
|
|
1791
|
-
* own, and a function/arrow literal is called with the arguments.
|
|
1792
|
-
*/
|
|
1793
|
-
async function evaluateScript(page, script, args) {
|
|
1794
|
-
const source = script.trim();
|
|
1795
|
-
// A function or arrow literal is called with the arguments directly.
|
|
1796
|
-
const isCallable = /^(async\s+)?function\b/.test(source) || /=>/.test(source);
|
|
1797
|
-
return page.evaluate((payload) => {
|
|
1798
|
-
if (payload.callable) {
|
|
1799
|
-
const fn = (0, eval)(`(${payload.src})`);
|
|
1800
|
-
return fn(...payload.a);
|
|
1801
|
-
}
|
|
1802
|
-
const compile = (src) => {
|
|
1803
|
-
// 1. A bare expression: implicit return.
|
|
1804
|
-
try {
|
|
1805
|
-
return new Function("args", `return (${src});`);
|
|
1806
|
-
}
|
|
1807
|
-
catch { /* not an expression */ }
|
|
1808
|
-
// 2. Statements with no explicit return: return the final expression,
|
|
1809
|
-
// which is what a REPL and the old one-arg form both did.
|
|
1810
|
-
if (!/\breturn\b/.test(src)) {
|
|
1811
|
-
const trimmed = src.replace(/;\s*$/, "");
|
|
1812
|
-
const split = Math.max(trimmed.lastIndexOf(";"), trimmed.lastIndexOf("\n"));
|
|
1813
|
-
if (split > -1) {
|
|
1814
|
-
const head = trimmed.slice(0, split + 1);
|
|
1815
|
-
const tail = trimmed.slice(split + 1).trim();
|
|
1816
|
-
if (tail) {
|
|
1817
|
-
try {
|
|
1818
|
-
return new Function("args", `${head}\nreturn (${tail});`);
|
|
1819
|
-
}
|
|
1820
|
-
catch { /* the tail is not an expression either */ }
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
}
|
|
1824
|
-
// 3. Statements as written. Any syntax error surfaces from here, which
|
|
1825
|
-
// is the error the user should actually see.
|
|
1826
|
-
return new Function("args", src);
|
|
1827
|
-
};
|
|
1828
|
-
return compile(payload.src)(payload.a);
|
|
1829
|
-
}, { src: source, a: args, callable: isCallable });
|
|
1830
|
-
}
|
|
1831
1796
|
/**
|
|
1832
1797
|
* Run JavaScript in the page.
|
|
1833
1798
|
*
|
|
@@ -2067,8 +2032,7 @@ function parseKeyboardToken(token, literal = false) {
|
|
|
2067
2032
|
/** Flags `keyboard` understands. */
|
|
2068
2033
|
const KEYBOARD_FLAGS = new Set([
|
|
2069
2034
|
"delay", "selector", "hold", "repeat", "text", "force",
|
|
2070
|
-
|
|
2071
|
-
"locale", "timezone", "geo", "verbose",
|
|
2035
|
+
...SHARED_BROWSER_FLAGS,
|
|
2072
2036
|
]);
|
|
2073
2037
|
/**
|
|
2074
2038
|
* Drive a keyboard sequence.
|
|
@@ -2614,6 +2578,12 @@ async function main() {
|
|
|
2614
2578
|
const outputOpts = detectOutputOptions(options);
|
|
2615
2579
|
setOutputOptions(outputOpts);
|
|
2616
2580
|
installOutputWrappers();
|
|
2581
|
+
// Page-derived text (console messages, titles, extracted text) reaches stdout in
|
|
2582
|
+
// many places; escape sequences and bidi overrides in it come out as visible
|
|
2583
|
+
// escapes instead of acting on the terminal. (2026-10-12)
|
|
2584
|
+
guardTerminalOutput(process.stdout);
|
|
2585
|
+
guardTerminalOutput(process.stderr);
|
|
2586
|
+
guardConsole();
|
|
2617
2587
|
// --json-output must never leave stdout empty. Only two commands in this file
|
|
2618
2588
|
// call emitJson, so without this every other command printed nothing at all and
|
|
2619
2589
|
// still exited 0 ā a CI step piping to jq saw empty input beside a success code.
|
|
@@ -2857,9 +2827,28 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2857
2827
|
const port = parseInt(options.port) || 9222;
|
|
2858
2828
|
switch (subCommand) {
|
|
2859
2829
|
case "start": {
|
|
2830
|
+
// The daemon's browser takes its device and size from here. Both are
|
|
2831
|
+
// checked now: a bad value inside the detached daemon would only show
|
|
2832
|
+
// up as a daemon that failed to start.
|
|
2833
|
+
const launchArgs = [];
|
|
2834
|
+
if (typeof options.device === "string")
|
|
2835
|
+
launchArgs.push("--device", resolveDevicePreset(options.device));
|
|
2836
|
+
if (typeof options.viewport === "string") {
|
|
2837
|
+
try {
|
|
2838
|
+
parseViewportList(options.viewport);
|
|
2839
|
+
}
|
|
2840
|
+
catch (e) {
|
|
2841
|
+
console.error(`Error: ${e.message}`);
|
|
2842
|
+
process.exit(1);
|
|
2843
|
+
}
|
|
2844
|
+
launchArgs.push("--viewport", options.viewport);
|
|
2845
|
+
}
|
|
2860
2846
|
console.log("š Starting CBrowser daemon...");
|
|
2861
|
-
const result = await startDaemon(port);
|
|
2847
|
+
const result = await startDaemon(port, launchArgs);
|
|
2862
2848
|
console.log(result.success ? `ā ${result.message}` : `ā ${result.message}`);
|
|
2849
|
+
if (result.alreadyRunning && launchArgs.length > 0) {
|
|
2850
|
+
console.error("ā --device and --viewport were not applied: the daemon was already running. Run cbrowser daemon stop first.");
|
|
2851
|
+
}
|
|
2863
2852
|
process.exit(result.success ? 0 : 1);
|
|
2864
2853
|
break;
|
|
2865
2854
|
}
|
|
@@ -2883,9 +2872,12 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2883
2872
|
: options.browser === "webkit" ? "webkit"
|
|
2884
2873
|
: "chromium";
|
|
2885
2874
|
// runDaemonServer will merge with defaults internally
|
|
2875
|
+
const runViewport = typeof options.viewport === "string" ? parseViewportList(options.viewport)[0] : undefined;
|
|
2886
2876
|
await runDaemonServer({
|
|
2887
2877
|
browser: browserType,
|
|
2888
2878
|
headless: options.headless !== false && options.headless !== "false",
|
|
2879
|
+
...(typeof options.device === "string" ? { device: options.device } : {}),
|
|
2880
|
+
...(runViewport ? { viewportWidth: runViewport.width, viewportHeight: runViewport.height, viewportOverride: runViewport } : {}),
|
|
2889
2881
|
}, port);
|
|
2890
2882
|
return;
|
|
2891
2883
|
}
|
|
@@ -2908,6 +2900,12 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2908
2900
|
];
|
|
2909
2901
|
if (daemonRunning && INTERACTION_COMMANDS.includes(command)) {
|
|
2910
2902
|
console.log("š Connected to running daemon");
|
|
2903
|
+
// The daemon's browser keeps the device and size it started with; a flag on
|
|
2904
|
+
// a command it runs cannot change them, so say so instead of ignoring it.
|
|
2905
|
+
if (typeof options.device === "string" || typeof options.viewport === "string") {
|
|
2906
|
+
console.error("ā --device and --viewport do not apply to commands the daemon runs: its browser keeps the device and size it started with.");
|
|
2907
|
+
console.error(" To change them: cbrowser daemon stop, then cbrowser daemon start --device <name> --viewport <WxH>");
|
|
2908
|
+
}
|
|
2911
2909
|
const daemonCommand = command;
|
|
2912
2910
|
let daemonArgs = {};
|
|
2913
2911
|
switch (command) {
|
|
@@ -2978,7 +2976,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2978
2976
|
console.error(" Re-run with --force to run it anyway.");
|
|
2979
2977
|
process.exit(1);
|
|
2980
2978
|
}
|
|
2981
|
-
|
|
2979
|
+
// The raw script: the daemon compiles it with the same evaluateScript as the
|
|
2980
|
+
// local path. This hand-built body treated a "return" anywhere - even inside
|
|
2981
|
+
// a nested function - as statements, and an IIFE's value was dropped.
|
|
2982
|
+
// (2026-10-13)
|
|
2983
|
+
daemonArgs = { script, args: [], force: true };
|
|
2982
2984
|
break;
|
|
2983
2985
|
}
|
|
2984
2986
|
case "screenshot": {
|
|
@@ -3019,7 +3021,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3019
3021
|
const result = await sendToDaemon(daemonCommand, daemonArgs);
|
|
3020
3022
|
if (result.success) {
|
|
3021
3023
|
console.log("ā Command executed via daemon");
|
|
3022
|
-
|
|
3024
|
+
// evaluate's value is the answer, so 0, false and "" print too; for other
|
|
3025
|
+
// commands an empty result means nothing to show. (2026-10-13)
|
|
3026
|
+
if (daemonCommand === "evaluate" ? result.result !== undefined : result.result) {
|
|
3023
3027
|
console.log(JSON.stringify(result.result, null, 2));
|
|
3024
3028
|
}
|
|
3025
3029
|
}
|
|
@@ -3085,7 +3089,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3085
3089
|
browser: browserType,
|
|
3086
3090
|
headless,
|
|
3087
3091
|
device: options.device,
|
|
3088
|
-
...(viewportOverride ? { viewportWidth: viewportOverride.width, viewportHeight: viewportOverride.height } : {}),
|
|
3092
|
+
...(viewportOverride ? { viewportWidth: viewportOverride.width, viewportHeight: viewportOverride.height, viewportOverride } : {}),
|
|
3089
3093
|
geolocation,
|
|
3090
3094
|
locale: options.locale,
|
|
3091
3095
|
timezone: options.timezone,
|
|
@@ -4230,14 +4234,18 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4230
4234
|
const subcommand = args[0];
|
|
4231
4235
|
switch (subcommand) {
|
|
4232
4236
|
case "list": {
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4237
|
+
// One line per preset: there are a few hundred now. An optional
|
|
4238
|
+
// filter matches the key or the display name.
|
|
4239
|
+
const filter = (args[1] ?? "").toLowerCase();
|
|
4240
|
+
const rows = Object.entries(DEVICE_PRESETS).filter(([key, d]) => !filter || key.includes(filter) || d.name.toLowerCase().includes(filter));
|
|
4241
|
+
const width = Math.max(...rows.map(([key]) => key.length), 4);
|
|
4242
|
+
console.log(`\nš± Device presets${filter ? ` matching "${filter}"` : ""}: ${rows.length}\n`);
|
|
4243
|
+
for (const [key, d] of rows) {
|
|
4244
|
+
const kind = d.isMobile ? "mobile" : "desktop";
|
|
4245
|
+
const touch = d.hasTouch ? ", touch" : "";
|
|
4246
|
+
console.log(` ${key.padEnd(width)} ${d.viewport.width}x${d.viewport.height} @${d.deviceScaleFactor}x ${kind}${touch} (${d.name})`);
|
|
4247
|
+
}
|
|
4248
|
+
console.log("\n Case and spaces do not matter: --device \"iPhone 16\" is iphone-16.");
|
|
4241
4249
|
break;
|
|
4242
4250
|
}
|
|
4243
4251
|
case "set": {
|
|
@@ -4246,15 +4254,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4246
4254
|
console.error("Usage: cbrowser device set <name>");
|
|
4247
4255
|
process.exit(1);
|
|
4248
4256
|
}
|
|
4249
|
-
|
|
4250
|
-
console.error(`Unknown device: ${deviceName}`);
|
|
4251
|
-
console.error("Run 'cbrowser device list' to see available devices");
|
|
4252
|
-
process.exit(1);
|
|
4253
|
-
}
|
|
4257
|
+
const presetKey = resolveDevicePreset(deviceName);
|
|
4254
4258
|
// Save device setting to persistent session state
|
|
4255
4259
|
const deviceBrowser = new CBrowser({ persistent: true, headless: true, verbose: false });
|
|
4256
|
-
deviceBrowser.saveDeviceSetting(
|
|
4257
|
-
console.log(`ā Device set: ${
|
|
4260
|
+
deviceBrowser.saveDeviceSetting(presetKey);
|
|
4261
|
+
console.log(`ā Device set: ${presetKey}`);
|
|
4258
4262
|
console.log(" Device emulation will apply to all subsequent commands");
|
|
4259
4263
|
break;
|
|
4260
4264
|
}
|
|
@@ -4427,6 +4431,45 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4427
4431
|
// =========================================================================
|
|
4428
4432
|
// Network
|
|
4429
4433
|
// =========================================================================
|
|
4434
|
+
case "console": {
|
|
4435
|
+
// The daemon tab's console (log, info, warning, error, uncaught exceptions,
|
|
4436
|
+
// failed resources), as get_console_messages returns it over MCP. Served by
|
|
4437
|
+
// the daemon: a fresh process has no tab and would always list nothing.
|
|
4438
|
+
const filter = typeof options.filter === "string" ? options.filter : "all";
|
|
4439
|
+
if (!["all", "error", "warning", "log", "info"].includes(filter)) {
|
|
4440
|
+
console.error(`Error: --filter must be one of all, error, warning, log, info (got "${filter}")`);
|
|
4441
|
+
process.exit(1);
|
|
4442
|
+
}
|
|
4443
|
+
if (!(await isDaemonRunning())) {
|
|
4444
|
+
console.error("Error: the console log only spans commands in the daemon's browser; each cbrowser command is");
|
|
4445
|
+
console.error(" its own process. Start the daemon first:");
|
|
4446
|
+
console.error(" cbrowser daemon start -> navigate/click... -> cbrowser console [--filter error] [--clear] [--json]");
|
|
4447
|
+
process.exit(1);
|
|
4448
|
+
}
|
|
4449
|
+
const response = await sendToDaemon("consoleMessages", { filter, clear: options.clear === true });
|
|
4450
|
+
if (!response.success) {
|
|
4451
|
+
console.error(`ā Daemon error: ${response.error}`);
|
|
4452
|
+
process.exit(1);
|
|
4453
|
+
}
|
|
4454
|
+
const out = response.result;
|
|
4455
|
+
if (options.json === true) {
|
|
4456
|
+
console.log(JSON.stringify(out, null, 2));
|
|
4457
|
+
}
|
|
4458
|
+
else if (out.count === 0) {
|
|
4459
|
+
console.log(filter === "all" ? "No console messages" : `No ${filter} console messages`);
|
|
4460
|
+
}
|
|
4461
|
+
else {
|
|
4462
|
+
console.log(`\nš„ Console (${out.count}${out.dropped ? `, ${out.dropped} older dropped` : ""}):\n`);
|
|
4463
|
+
for (const m of out.messages) {
|
|
4464
|
+
const where = m.url ? ` (${m.url}${m.lineNumber !== undefined ? `:${m.lineNumber}` : ""})` : "";
|
|
4465
|
+
console.log(` [${m.type}${m.source === "pageerror" ? ", uncaught" : ""}] ${m.text}${where}`);
|
|
4466
|
+
console.log(` on ${m.pageUrl}`);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
if (options.clear === true)
|
|
4470
|
+
console.log("ā Console cleared");
|
|
4471
|
+
process.exit(0);
|
|
4472
|
+
}
|
|
4430
4473
|
case "network": {
|
|
4431
4474
|
const subcommand = args[0];
|
|
4432
4475
|
if (subcommand !== "list" && subcommand !== "clear") {
|
|
@@ -5398,9 +5441,10 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
5398
5441
|
console.error("Usage: cbrowser parallel devices <url> [--devices <list>] [--concurrency <n>]");
|
|
5399
5442
|
process.exit(1);
|
|
5400
5443
|
}
|
|
5444
|
+
// Without --devices, cbrowser's own presets, not all few hundred.
|
|
5401
5445
|
const deviceList = options.devices
|
|
5402
|
-
? options.devices.split(",")
|
|
5403
|
-
:
|
|
5446
|
+
? options.devices.split(",").map((d) => resolveDevicePreset(d))
|
|
5447
|
+
: [...CBROWSER_DEVICE_KEYS];
|
|
5404
5448
|
const concurrency = options.concurrency ? parseInt(options.concurrency) : 3;
|
|
5405
5449
|
console.log(`\nš Running parallel device tests...`);
|
|
5406
5450
|
console.log(` URL: ${url}`);
|
|
@@ -5862,7 +5906,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
5862
5906
|
});
|
|
5863
5907
|
console.log(`\nš Step-through mode enabled. Press Enter to execute each step.\n`);
|
|
5864
5908
|
const { CBrowser } = await import("./browser.js");
|
|
5865
|
-
const stepBrowser = new CBrowser({
|
|
5909
|
+
const stepBrowser = new CBrowser({
|
|
5910
|
+
headless,
|
|
5911
|
+
...(typeof options.device === "string" ? { device: options.device } : {}),
|
|
5912
|
+
...(viewportOverride ? { viewportWidth: viewportOverride.width, viewportHeight: viewportOverride.height, viewportOverride } : {}),
|
|
5913
|
+
});
|
|
5866
5914
|
await stepBrowser.launch();
|
|
5867
5915
|
try {
|
|
5868
5916
|
const totalSteps = suite.tests.reduce((sum, t) => sum + t.steps.length, 0);
|
|
@@ -5943,6 +5991,8 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
5943
5991
|
continueOnFailure: options["continue-on-failure"] === true,
|
|
5944
5992
|
screenshotOnFailure: options["screenshot-on-failure"] !== false,
|
|
5945
5993
|
headless,
|
|
5994
|
+
device: typeof options.device === "string" ? options.device : undefined,
|
|
5995
|
+
viewport: viewportOverride,
|
|
5946
5996
|
fuzzyMatch: options["fuzzy-match"] === true,
|
|
5947
5997
|
capture: buildAutoCaptureSetting(options, `test-suite-${suite.name.replace(/[^a-zA-Z0-9-_]+/g, "-").toLowerCase()}-${Date.now()}`),
|
|
5948
5998
|
};
|
|
@@ -6517,6 +6567,8 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
6517
6567
|
maxTime,
|
|
6518
6568
|
verbose,
|
|
6519
6569
|
headless,
|
|
6570
|
+
device: typeof options.device === "string" ? options.device : undefined,
|
|
6571
|
+
viewport: viewportOverride,
|
|
6520
6572
|
vision,
|
|
6521
6573
|
customTraits, // Pass custom traits from env var
|
|
6522
6574
|
location: locationOverride, // Pass location override
|