cbrowser 8.7.3 ā 8.9.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/README.md +9 -2
- package/dist/analysis/bug-hunter.js +1 -4
- package/dist/analysis/bug-hunter.js.map +1 -1
- package/dist/analysis/chaos-testing.js +4 -8
- package/dist/analysis/chaos-testing.js.map +1 -1
- package/dist/analysis/index.js +4 -20
- package/dist/analysis/index.js.map +1 -1
- package/dist/analysis/natural-language.js +4 -10
- package/dist/analysis/natural-language.js.map +1 -1
- package/dist/analysis/persona-comparison.js +6 -10
- package/dist/analysis/persona-comparison.js.map +1 -1
- package/dist/browser.d.ts +50 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +665 -131
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +138 -131
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.js +29 -40
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.js +50 -61
- package/dist/config.js.map +1 -1
- package/dist/daemon.js +28 -37
- package/dist/daemon.js.map +1 -1
- package/dist/index.js +10 -40
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-remote.js +153 -156
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.js +146 -149
- package/dist/mcp-server.js.map +1 -1
- package/dist/performance/index.js +1 -17
- package/dist/performance/index.js.map +1 -1
- package/dist/performance/metrics.js +30 -39
- package/dist/performance/metrics.js.map +1 -1
- package/dist/personas.js +32 -46
- package/dist/personas.js.map +1 -1
- package/dist/testing/coverage.js +13 -23
- package/dist/testing/coverage.js.map +1 -1
- package/dist/testing/flaky-detection.js +4 -8
- package/dist/testing/flaky-detection.js.map +1 -1
- package/dist/testing/index.js +4 -20
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/nl-test-suite.js +11 -19
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/testing/test-repair.js +9 -15
- package/dist/testing/test-repair.js.map +1 -1
- package/dist/types.js +3 -6
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +60 -0
- package/dist/version.js.map +1 -0
- package/dist/visual/ab-comparison.js +16 -22
- package/dist/visual/ab-comparison.js.map +1 -1
- package/dist/visual/cross-browser.js +17 -24
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/index.js +4 -20
- package/dist/visual/index.js.map +1 -1
- package/dist/visual/regression.js +46 -58
- package/dist/visual/regression.js.map +1 -1
- package/dist/visual/responsive.js +22 -29
- package/dist/visual/responsive.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
2
|
/**
|
|
4
3
|
* CBrowser CLI
|
|
5
4
|
*
|
|
6
5
|
* AI-powered browser automation from the command line.
|
|
7
6
|
*/
|
|
8
|
-
|
|
9
|
-
const browser_js_1 = require("./browser.js");
|
|
7
|
+
import { CBrowser } from "./browser.js";
|
|
10
8
|
// Analysis module imports
|
|
11
|
-
|
|
9
|
+
import { executeNaturalLanguage, executeNaturalLanguageScript, huntBugs, runChaosTest, comparePersonas, formatComparisonReport, findElementByIntent } from "./analysis/index.js";
|
|
12
10
|
// Testing module imports
|
|
13
|
-
|
|
11
|
+
import { parseNLInstruction, parseNLTestSuite, runNLTestSuite, formatNLTestReport, dryRunNLTestSuite, repairTestSuite, formatRepairReport, exportRepairedTest, detectFlakyTests, formatFlakyTestReport, generateCoverageMap, formatCoverageReport, generateCoverageHtmlReport } from "./testing/index.js";
|
|
14
12
|
// Performance module imports
|
|
15
|
-
|
|
13
|
+
import { capturePerformanceBaseline, listPerformanceBaselines, loadPerformanceBaseline, deletePerformanceBaseline, detectPerformanceRegression, formatPerformanceRegressionReport } from "./performance/index.js";
|
|
16
14
|
// Visual module imports
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
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";
|
|
16
|
+
import { BUILTIN_PERSONAS, loadCustomPersonas, saveCustomPersona, deleteCustomPersona, isBuiltinPersona, generatePersonaFromDescription, } from "./personas.js";
|
|
17
|
+
import { DEVICE_PRESETS, LOCATION_PRESETS } from "./types.js";
|
|
18
|
+
import { startMcpServer } from "./mcp-server.js";
|
|
19
|
+
import { startRemoteMcpServer } from "./mcp-server-remote.js";
|
|
20
|
+
import { startDaemon, stopDaemon, getDaemonStatus, isDaemonRunning, sendToDaemon, runDaemonServer } from "./daemon.js";
|
|
21
|
+
import { getStatusInfo, formatStatus, getDataDir } from "./config.js";
|
|
22
|
+
import { runCognitiveJourney, getAnthropicApiKey, setAnthropicApiKey, removeAnthropicApiKey, isApiKeyConfigured, } from "./cognitive/index.js";
|
|
23
|
+
// Version from package.json - single source of truth
|
|
24
|
+
import { VERSION } from "./version.js";
|
|
25
25
|
function showHelp() {
|
|
26
|
+
// Pad version string to maintain banner alignment
|
|
27
|
+
const versionStr = `CBrowser CLI v${VERSION}`;
|
|
28
|
+
const padding = ' '.repeat(Math.max(0, (80 - 2 - versionStr.length) / 2));
|
|
29
|
+
const rightPadding = ' '.repeat(Math.max(0, 80 - 2 - padding.length - versionStr.length));
|
|
26
30
|
console.log(`
|
|
27
31
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
28
|
-
ā
|
|
32
|
+
ā${padding}${versionStr}${rightPadding}ā
|
|
29
33
|
ā AI-powered browser automation with cross-browser visual testing ā
|
|
30
34
|
ā Semantic Versioning: https://semver.org/ ā
|
|
31
35
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
@@ -47,23 +51,31 @@ INTERACTION
|
|
|
47
51
|
EXTRACTION
|
|
48
52
|
extract <what> Extract data (links, images, headings, forms)
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
SITE EXPLORATION (Free, heuristic-based)
|
|
55
|
+
explore <persona> Quick autonomous exploration using built-in heuristics
|
|
52
56
|
--start <url> Starting URL (required)
|
|
53
57
|
--goal <goal> What to accomplish
|
|
54
|
-
--record-video Record
|
|
58
|
+
--record-video Record exploration as video
|
|
59
|
+
Note: No API key needed. Uses rule-based navigation - fast but not "smart".
|
|
55
60
|
|
|
56
|
-
COGNITIVE JOURNEY (
|
|
57
|
-
cognitive-journey
|
|
61
|
+
COGNITIVE JOURNEY (API-powered, realistic user simulation)
|
|
62
|
+
cognitive-journey Simulate a real user with emotions, patience, and abandonment
|
|
58
63
|
--persona <name> Persona name or description (default: first-timer)
|
|
59
64
|
--start <url> Starting URL (required)
|
|
60
65
|
--goal <goal> Goal statement (required)
|
|
61
66
|
--max-steps <n> Maximum steps before timeout (default: 50)
|
|
62
67
|
--max-time <s> Maximum time in seconds (default: 120)
|
|
63
|
-
--verbose Show step-by-step narration
|
|
68
|
+
--verbose Show step-by-step narration and internal monologue
|
|
64
69
|
--vision Enable vision mode - send screenshots to Claude (more accurate)
|
|
65
70
|
--output <file> Save JSON report to file
|
|
66
71
|
--html Generate HTML report
|
|
72
|
+
|
|
73
|
+
Why use this over 'explore'?
|
|
74
|
+
⢠Tracks cognitive state: patience, frustration, confusion
|
|
75
|
+
⢠Can realistically abandon: "This is too confusing, I give up"
|
|
76
|
+
⢠Makes human-like mistakes based on persona traits
|
|
77
|
+
⢠Costs API tokens but provides genuine UX insights
|
|
78
|
+
|
|
67
79
|
Examples:
|
|
68
80
|
cbrowser cognitive-journey --start "https://example.com" \\
|
|
69
81
|
--goal "Sign up for an account" --persona first-timer
|
|
@@ -553,7 +565,7 @@ EXAMPLES
|
|
|
553
565
|
npx cbrowser navigate "https://example.com" --device iphone-15
|
|
554
566
|
npx cbrowser navigate "https://example.com" --geo san-francisco
|
|
555
567
|
npx cbrowser perf audit "https://example.com" --budget-lcp 2000
|
|
556
|
-
npx cbrowser
|
|
568
|
+
npx cbrowser explore first-timer --start "https://example.com" --record-video
|
|
557
569
|
npx cbrowser cookie list --url "https://example.com"
|
|
558
570
|
`);
|
|
559
571
|
}
|
|
@@ -873,8 +885,8 @@ function generateTestSuiteHtmlReport(result) {
|
|
|
873
885
|
}
|
|
874
886
|
function parseGeoLocation(location) {
|
|
875
887
|
// Check if it's a preset
|
|
876
|
-
if (
|
|
877
|
-
return
|
|
888
|
+
if (LOCATION_PRESETS[location]) {
|
|
889
|
+
return LOCATION_PRESETS[location];
|
|
878
890
|
}
|
|
879
891
|
// Try parsing as lat,lon
|
|
880
892
|
const parts = location.split(",");
|
|
@@ -896,7 +908,7 @@ async function main() {
|
|
|
896
908
|
// MCP Server mode - runs before browser instantiation
|
|
897
909
|
if (command === "mcp-server") {
|
|
898
910
|
console.error("š Starting CBrowser MCP Server...");
|
|
899
|
-
await
|
|
911
|
+
await startMcpServer();
|
|
900
912
|
return;
|
|
901
913
|
}
|
|
902
914
|
// Remote MCP Server mode - HTTP server for claude.ai connectors
|
|
@@ -907,24 +919,14 @@ async function main() {
|
|
|
907
919
|
process.env.HOST = String(options.host);
|
|
908
920
|
if (options.stateful)
|
|
909
921
|
process.env.MCP_SESSION_MODE = "stateful";
|
|
910
|
-
await
|
|
922
|
+
await startRemoteMcpServer();
|
|
911
923
|
return;
|
|
912
924
|
}
|
|
913
925
|
// Status command - runs before browser instantiation
|
|
914
926
|
if (command === "status") {
|
|
915
|
-
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
// Try to read version from package.json at runtime
|
|
919
|
-
try {
|
|
920
|
-
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
921
|
-
if (fs.existsSync(pkgPath)) {
|
|
922
|
-
version = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
catch { }
|
|
926
|
-
const info = await (0, config_js_1.getStatusInfo)(version);
|
|
927
|
-
console.log((0, config_js_1.formatStatus)(info));
|
|
927
|
+
// VERSION is imported from version.ts which reads from package.json
|
|
928
|
+
const info = await getStatusInfo(VERSION);
|
|
929
|
+
console.log(formatStatus(info));
|
|
928
930
|
process.exit(0);
|
|
929
931
|
}
|
|
930
932
|
// Config command - runs before browser instantiation
|
|
@@ -943,12 +945,12 @@ async function main() {
|
|
|
943
945
|
console.error("ā Invalid API key format. Anthropic keys start with 'sk-ant-'");
|
|
944
946
|
process.exit(1);
|
|
945
947
|
}
|
|
946
|
-
|
|
948
|
+
setAnthropicApiKey(apiKey);
|
|
947
949
|
console.log("ā
Anthropic API key saved to ~/.cbrowser/config.json");
|
|
948
950
|
process.exit(0);
|
|
949
951
|
}
|
|
950
952
|
case "show-api-key": {
|
|
951
|
-
const key =
|
|
953
|
+
const key = getAnthropicApiKey();
|
|
952
954
|
if (key) {
|
|
953
955
|
const masked = key.slice(0, 7) + "..." + key.slice(-4);
|
|
954
956
|
console.log(`Anthropic API key: ${masked}`);
|
|
@@ -961,7 +963,7 @@ async function main() {
|
|
|
961
963
|
process.exit(0);
|
|
962
964
|
}
|
|
963
965
|
case "remove-api-key": {
|
|
964
|
-
|
|
966
|
+
removeAnthropicApiKey();
|
|
965
967
|
console.log("ā
Anthropic API key removed from config.");
|
|
966
968
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
967
969
|
console.log("ā ļø Note: ANTHROPIC_API_KEY environment variable is still set.");
|
|
@@ -975,7 +977,7 @@ async function main() {
|
|
|
975
977
|
console.error("Examples: claude-sonnet-4-20250514, claude-3-5-sonnet-20241022");
|
|
976
978
|
process.exit(1);
|
|
977
979
|
}
|
|
978
|
-
const configPath = path.join(
|
|
980
|
+
const configPath = path.join(getDataDir(), "config.json");
|
|
979
981
|
let config = {};
|
|
980
982
|
if (fs.existsSync(configPath)) {
|
|
981
983
|
try {
|
|
@@ -1077,20 +1079,20 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1077
1079
|
switch (subCommand) {
|
|
1078
1080
|
case "start": {
|
|
1079
1081
|
console.log("š Starting CBrowser daemon...");
|
|
1080
|
-
const result = await
|
|
1082
|
+
const result = await startDaemon(port);
|
|
1081
1083
|
console.log(result.success ? `ā ${result.message}` : `ā ${result.message}`);
|
|
1082
1084
|
process.exit(result.success ? 0 : 1);
|
|
1083
1085
|
break;
|
|
1084
1086
|
}
|
|
1085
1087
|
case "stop": {
|
|
1086
1088
|
console.log("š Stopping CBrowser daemon...");
|
|
1087
|
-
const result = await
|
|
1089
|
+
const result = await stopDaemon();
|
|
1088
1090
|
console.log(result.success ? `ā ${result.message}` : `ā ${result.message}`);
|
|
1089
1091
|
process.exit(0);
|
|
1090
1092
|
break;
|
|
1091
1093
|
}
|
|
1092
1094
|
case "status": {
|
|
1093
|
-
const status = await
|
|
1095
|
+
const status = await getDaemonStatus();
|
|
1094
1096
|
console.log(status);
|
|
1095
1097
|
process.exit(0);
|
|
1096
1098
|
break;
|
|
@@ -1102,7 +1104,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1102
1104
|
: options.browser === "webkit" ? "webkit"
|
|
1103
1105
|
: "chromium";
|
|
1104
1106
|
// runDaemonServer will merge with defaults internally
|
|
1105
|
-
await
|
|
1107
|
+
await runDaemonServer({
|
|
1106
1108
|
browser: browserType,
|
|
1107
1109
|
headless: options.headless !== false && options.headless !== "false",
|
|
1108
1110
|
}, port);
|
|
@@ -1116,7 +1118,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1116
1118
|
return;
|
|
1117
1119
|
}
|
|
1118
1120
|
// Check if daemon is running and use it for supported commands
|
|
1119
|
-
const daemonRunning = await
|
|
1121
|
+
const daemonRunning = await isDaemonRunning();
|
|
1120
1122
|
if (daemonRunning && ["navigate", "click", "fill", "hover", "screenshot", "extract", "run"].includes(command)) {
|
|
1121
1123
|
console.log("š Connected to running daemon");
|
|
1122
1124
|
let daemonCommand = command;
|
|
@@ -1169,7 +1171,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1169
1171
|
daemonArgs = { command: args.join(" ") };
|
|
1170
1172
|
break;
|
|
1171
1173
|
}
|
|
1172
|
-
const result = await
|
|
1174
|
+
const result = await sendToDaemon(daemonCommand, daemonArgs);
|
|
1173
1175
|
if (result.success) {
|
|
1174
1176
|
console.log("ā Command executed via daemon");
|
|
1175
1177
|
if (result.result) {
|
|
@@ -1202,7 +1204,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1202
1204
|
}
|
|
1203
1205
|
// Default to headless for CLI usage, unless explicitly set to false
|
|
1204
1206
|
const headless = options.headless !== false && options.headless !== "false";
|
|
1205
|
-
const browser = new
|
|
1207
|
+
const browser = new CBrowser({
|
|
1206
1208
|
browser: browserType,
|
|
1207
1209
|
headless,
|
|
1208
1210
|
device: options.device,
|
|
@@ -1633,7 +1635,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1633
1635
|
console.log(`ā Screenshot saved: ${file}`);
|
|
1634
1636
|
break;
|
|
1635
1637
|
}
|
|
1636
|
-
case "journey":
|
|
1638
|
+
case "journey": // Deprecated alias for 'explore'
|
|
1639
|
+
case "explore": {
|
|
1640
|
+
if (command === "journey") {
|
|
1641
|
+
console.warn("ā ļø 'journey' is deprecated. Use 'explore' instead.\n");
|
|
1642
|
+
}
|
|
1637
1643
|
const persona = args[0] || "first-timer";
|
|
1638
1644
|
const startUrl = options.start;
|
|
1639
1645
|
const goal = options.goal || "Explore the site";
|
|
@@ -1641,8 +1647,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1641
1647
|
console.error("Error: --start URL required");
|
|
1642
1648
|
process.exit(1);
|
|
1643
1649
|
}
|
|
1644
|
-
console.log(
|
|
1650
|
+
console.log(`š Starting exploration as "${persona}"...`);
|
|
1645
1651
|
console.log(` Goal: ${goal}`);
|
|
1652
|
+
console.log(` Mode: Heuristic (free, no API)`);
|
|
1646
1653
|
console.log("");
|
|
1647
1654
|
const result = await browser.journey({ persona, startUrl, goal });
|
|
1648
1655
|
console.log("");
|
|
@@ -1678,11 +1685,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1678
1685
|
// Default to comparing all built-in personas if none specified
|
|
1679
1686
|
const personaNames = personaList
|
|
1680
1687
|
? personaList.split(",").map((p) => p.trim())
|
|
1681
|
-
: Object.keys(
|
|
1688
|
+
: Object.keys(BUILTIN_PERSONAS);
|
|
1682
1689
|
const concurrency = options.concurrency
|
|
1683
1690
|
? parseInt(options.concurrency)
|
|
1684
1691
|
: 3;
|
|
1685
|
-
const comparison = await
|
|
1692
|
+
const comparison = await comparePersonas({
|
|
1686
1693
|
startUrl,
|
|
1687
1694
|
goal,
|
|
1688
1695
|
personas: personaNames,
|
|
@@ -1690,7 +1697,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1690
1697
|
headless,
|
|
1691
1698
|
});
|
|
1692
1699
|
// Print formatted report
|
|
1693
|
-
const report =
|
|
1700
|
+
const report = formatComparisonReport(comparison);
|
|
1694
1701
|
console.log(report);
|
|
1695
1702
|
// Save JSON output if requested
|
|
1696
1703
|
if (options.output) {
|
|
@@ -1719,14 +1726,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1719
1726
|
console.log("\nš Available Personas:\n");
|
|
1720
1727
|
// Built-in personas
|
|
1721
1728
|
console.log(" Built-in:");
|
|
1722
|
-
for (const [name, persona] of Object.entries(
|
|
1729
|
+
for (const [name, persona] of Object.entries(BUILTIN_PERSONAS)) {
|
|
1723
1730
|
console.log(` ${name}`);
|
|
1724
1731
|
console.log(` ${persona.description}`);
|
|
1725
1732
|
console.log(` Tech: ${persona.demographics.tech_level} | Device: ${persona.demographics.device}`);
|
|
1726
1733
|
console.log("");
|
|
1727
1734
|
}
|
|
1728
1735
|
// Custom personas
|
|
1729
|
-
const customPersonas =
|
|
1736
|
+
const customPersonas = loadCustomPersonas();
|
|
1730
1737
|
const customNames = Object.keys(customPersonas);
|
|
1731
1738
|
if (customNames.length > 0) {
|
|
1732
1739
|
console.log(" Custom:");
|
|
@@ -1761,14 +1768,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1761
1768
|
.join("-");
|
|
1762
1769
|
}
|
|
1763
1770
|
// Check if trying to overwrite built-in
|
|
1764
|
-
if (
|
|
1771
|
+
if (isBuiltinPersona(personaName)) {
|
|
1765
1772
|
console.error(`Error: Cannot overwrite built-in persona "${personaName}"`);
|
|
1766
1773
|
console.error("Use a different name with --name <name>");
|
|
1767
1774
|
process.exit(1);
|
|
1768
1775
|
}
|
|
1769
1776
|
console.log(`\nš¤ Generating persona from description...\n`);
|
|
1770
1777
|
console.log(` "${description}"\n`);
|
|
1771
|
-
const persona =
|
|
1778
|
+
const persona = generatePersonaFromDescription(personaName, description);
|
|
1772
1779
|
// Display the generated persona
|
|
1773
1780
|
console.log(`āāā Generated Persona: ${persona.name} āāā\n`);
|
|
1774
1781
|
console.log(`Description: ${persona.description}`);
|
|
@@ -1799,7 +1806,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1799
1806
|
console.log(`\nBehaviors: ${Object.keys(persona.behaviors).join(", ")}`);
|
|
1800
1807
|
}
|
|
1801
1808
|
// Save the persona
|
|
1802
|
-
const filepath =
|
|
1809
|
+
const filepath = saveCustomPersona(persona);
|
|
1803
1810
|
console.log(`\nā Persona saved: ${filepath}`);
|
|
1804
1811
|
console.log(`\nUse with: cbrowser journey ${personaName} --start <url> --goal "<goal>"`);
|
|
1805
1812
|
break;
|
|
@@ -1811,10 +1818,10 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1811
1818
|
process.exit(1);
|
|
1812
1819
|
}
|
|
1813
1820
|
// Check built-in first, then custom
|
|
1814
|
-
let persona =
|
|
1821
|
+
let persona = BUILTIN_PERSONAS[name];
|
|
1815
1822
|
let isCustom = false;
|
|
1816
1823
|
if (!persona) {
|
|
1817
|
-
const customPersonas =
|
|
1824
|
+
const customPersonas = loadCustomPersonas();
|
|
1818
1825
|
persona = customPersonas[name];
|
|
1819
1826
|
isCustom = true;
|
|
1820
1827
|
}
|
|
@@ -1833,11 +1840,11 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1833
1840
|
console.error("Usage: cbrowser persona delete <name>");
|
|
1834
1841
|
process.exit(1);
|
|
1835
1842
|
}
|
|
1836
|
-
if (
|
|
1843
|
+
if (isBuiltinPersona(name)) {
|
|
1837
1844
|
console.error(`Cannot delete built-in persona: ${name}`);
|
|
1838
1845
|
process.exit(1);
|
|
1839
1846
|
}
|
|
1840
|
-
const deleted =
|
|
1847
|
+
const deleted = deleteCustomPersona(name);
|
|
1841
1848
|
if (deleted) {
|
|
1842
1849
|
console.log(`ā Persona deleted: ${name}`);
|
|
1843
1850
|
}
|
|
@@ -1854,9 +1861,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1854
1861
|
process.exit(1);
|
|
1855
1862
|
}
|
|
1856
1863
|
// Get persona (built-in or custom)
|
|
1857
|
-
let persona =
|
|
1864
|
+
let persona = BUILTIN_PERSONAS[name];
|
|
1858
1865
|
if (!persona) {
|
|
1859
|
-
const customPersonas =
|
|
1866
|
+
const customPersonas = loadCustomPersonas();
|
|
1860
1867
|
persona = customPersonas[name];
|
|
1861
1868
|
}
|
|
1862
1869
|
if (!persona) {
|
|
@@ -1887,12 +1894,12 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
1887
1894
|
console.error("Invalid persona file: missing name or description");
|
|
1888
1895
|
process.exit(1);
|
|
1889
1896
|
}
|
|
1890
|
-
if (
|
|
1897
|
+
if (isBuiltinPersona(persona.name)) {
|
|
1891
1898
|
console.error(`Cannot import: "${persona.name}" is a built-in persona name`);
|
|
1892
1899
|
console.error("Edit the JSON file to change the name");
|
|
1893
1900
|
process.exit(1);
|
|
1894
1901
|
}
|
|
1895
|
-
const filepath =
|
|
1902
|
+
const filepath = saveCustomPersona(persona);
|
|
1896
1903
|
console.log(`ā Imported persona: ${persona.name}`);
|
|
1897
1904
|
console.log(` Saved to: ${filepath}`);
|
|
1898
1905
|
}
|
|
@@ -2226,7 +2233,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2226
2233
|
switch (subcommand) {
|
|
2227
2234
|
case "list": {
|
|
2228
2235
|
console.log("\nš± Available Device Presets:\n");
|
|
2229
|
-
for (const [name, device] of Object.entries(
|
|
2236
|
+
for (const [name, device] of Object.entries(DEVICE_PRESETS)) {
|
|
2230
2237
|
console.log(` ${name}`);
|
|
2231
2238
|
console.log(` ${device.name}`);
|
|
2232
2239
|
console.log(` ${device.viewport.width}x${device.viewport.height} @${device.deviceScaleFactor}x`);
|
|
@@ -2241,7 +2248,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2241
2248
|
console.error("Usage: cbrowser device set <name>");
|
|
2242
2249
|
process.exit(1);
|
|
2243
2250
|
}
|
|
2244
|
-
if (!
|
|
2251
|
+
if (!DEVICE_PRESETS[deviceName]) {
|
|
2245
2252
|
console.error(`Unknown device: ${deviceName}`);
|
|
2246
2253
|
console.error("Run 'cbrowser device list' to see available devices");
|
|
2247
2254
|
process.exit(1);
|
|
@@ -2263,7 +2270,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2263
2270
|
switch (subcommand) {
|
|
2264
2271
|
case "list": {
|
|
2265
2272
|
console.log("\nš Available Location Presets:\n");
|
|
2266
|
-
for (const [name, loc] of Object.entries(
|
|
2273
|
+
for (const [name, loc] of Object.entries(LOCATION_PRESETS)) {
|
|
2267
2274
|
console.log(` ${name}`);
|
|
2268
2275
|
console.log(` Lat: ${loc.latitude}, Lon: ${loc.longitude}`);
|
|
2269
2276
|
console.log("");
|
|
@@ -2528,7 +2535,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2528
2535
|
}
|
|
2529
2536
|
console.log(`šø Capturing visual baseline: ${options.name}`);
|
|
2530
2537
|
console.log(` URL: ${url}`);
|
|
2531
|
-
const baseline = await
|
|
2538
|
+
const baseline = await captureVisualBaseline(url, options.name, {
|
|
2532
2539
|
selector: options.selector,
|
|
2533
2540
|
device: options.device,
|
|
2534
2541
|
viewport: options.width || options.height ? {
|
|
@@ -2559,13 +2566,13 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2559
2566
|
console.log(`\nš Running AI visual regression test...`);
|
|
2560
2567
|
console.log(` URL: ${url}`);
|
|
2561
2568
|
console.log(` Baseline: ${baselineName}\n`);
|
|
2562
|
-
const result = await
|
|
2569
|
+
const result = await runVisualRegression(url, baselineName, {
|
|
2563
2570
|
threshold: options.threshold ? parseFloat(options.threshold) : 0.9,
|
|
2564
2571
|
sensitivity: options.sensitivity || "medium",
|
|
2565
2572
|
generateDiff: true,
|
|
2566
2573
|
});
|
|
2567
2574
|
// Print report
|
|
2568
|
-
console.log(
|
|
2575
|
+
console.log(formatVisualRegressionReport(result));
|
|
2569
2576
|
// Save JSON output if requested
|
|
2570
2577
|
if (options.output && !options.html) {
|
|
2571
2578
|
const fs = await import("fs");
|
|
@@ -2590,7 +2597,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2590
2597
|
duration: result.duration,
|
|
2591
2598
|
timestamp: new Date().toISOString(),
|
|
2592
2599
|
};
|
|
2593
|
-
const htmlReport =
|
|
2600
|
+
const htmlReport = generateVisualRegressionHtmlReport(suiteResult);
|
|
2594
2601
|
const outputPath = options.output || `visual-regression-${baselineName}-${Date.now()}.html`;
|
|
2595
2602
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2596
2603
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2616,7 +2623,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2616
2623
|
}
|
|
2617
2624
|
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2618
2625
|
console.log(`\nš Running visual regression suite: ${suite.name}`);
|
|
2619
|
-
const result = await
|
|
2626
|
+
const result = await runVisualRegressionSuite(suite, {
|
|
2620
2627
|
threshold: options.threshold ? parseFloat(options.threshold) : 0.9,
|
|
2621
2628
|
});
|
|
2622
2629
|
// Save outputs
|
|
@@ -2625,7 +2632,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2625
2632
|
console.log(`š JSON report saved to: ${options.output}`);
|
|
2626
2633
|
}
|
|
2627
2634
|
if (options.html) {
|
|
2628
|
-
const htmlReport =
|
|
2635
|
+
const htmlReport = generateVisualRegressionHtmlReport(result);
|
|
2629
2636
|
const outputPath = options.output || `visual-suite-${Date.now()}.html`;
|
|
2630
2637
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2631
2638
|
console.log(`š HTML report saved to: ${outputPath}`);
|
|
@@ -2636,7 +2643,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2636
2643
|
break;
|
|
2637
2644
|
}
|
|
2638
2645
|
case "list": {
|
|
2639
|
-
const baselines =
|
|
2646
|
+
const baselines = listVisualBaselines();
|
|
2640
2647
|
if (baselines.length === 0) {
|
|
2641
2648
|
console.log("\nNo AI visual baselines saved.\n");
|
|
2642
2649
|
console.log("Capture one with:");
|
|
@@ -2664,7 +2671,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2664
2671
|
console.error("Usage: cbrowser ai-visual show <name>");
|
|
2665
2672
|
process.exit(1);
|
|
2666
2673
|
}
|
|
2667
|
-
const baseline =
|
|
2674
|
+
const baseline = getVisualBaseline(name);
|
|
2668
2675
|
if (!baseline) {
|
|
2669
2676
|
console.error(`Baseline not found: ${name}`);
|
|
2670
2677
|
process.exit(1);
|
|
@@ -2691,7 +2698,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2691
2698
|
console.error("Usage: cbrowser ai-visual delete <name>");
|
|
2692
2699
|
process.exit(1);
|
|
2693
2700
|
}
|
|
2694
|
-
if (
|
|
2701
|
+
if (deleteVisualBaseline(name)) {
|
|
2695
2702
|
console.log(`ā
Baseline deleted: ${name}`);
|
|
2696
2703
|
}
|
|
2697
2704
|
else {
|
|
@@ -2731,14 +2738,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2731
2738
|
process.exit(1);
|
|
2732
2739
|
}
|
|
2733
2740
|
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2734
|
-
const result = await
|
|
2741
|
+
const result = await runCrossBrowserSuite(suite);
|
|
2735
2742
|
// Save outputs
|
|
2736
2743
|
if (options.output && !options.html) {
|
|
2737
2744
|
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2738
2745
|
console.log(`\nš JSON report saved to: ${options.output}`);
|
|
2739
2746
|
}
|
|
2740
2747
|
if (options.html) {
|
|
2741
|
-
const htmlReport =
|
|
2748
|
+
const htmlReport = generateCrossBrowserHtmlReport(result);
|
|
2742
2749
|
const outputPath = options.output || `cross-browser-${Date.now()}.html`;
|
|
2743
2750
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2744
2751
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2771,7 +2778,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2771
2778
|
const browsers = options.browsers
|
|
2772
2779
|
? options.browsers.split(",")
|
|
2773
2780
|
: undefined;
|
|
2774
|
-
const result = await
|
|
2781
|
+
const result = await runCrossBrowserTest(url, {
|
|
2775
2782
|
browsers,
|
|
2776
2783
|
viewport: options.width || options.height ? {
|
|
2777
2784
|
width: parseInt(options.width) || 1920,
|
|
@@ -2782,7 +2789,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2782
2789
|
sensitivity: options.sensitivity,
|
|
2783
2790
|
});
|
|
2784
2791
|
// Print report
|
|
2785
|
-
console.log("\n" +
|
|
2792
|
+
console.log("\n" + formatCrossBrowserReport(result));
|
|
2786
2793
|
// Save outputs
|
|
2787
2794
|
const fs = await import("fs");
|
|
2788
2795
|
if (options.output && !options.html) {
|
|
@@ -2802,7 +2809,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2802
2809
|
duration: result.duration,
|
|
2803
2810
|
timestamp: result.timestamp,
|
|
2804
2811
|
};
|
|
2805
|
-
const htmlReport =
|
|
2812
|
+
const htmlReport = generateCrossBrowserHtmlReport(suiteResult);
|
|
2806
2813
|
const outputPath = options.output || `cross-browser-${Date.now()}.html`;
|
|
2807
2814
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2808
2815
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2820,7 +2827,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2820
2827
|
const subcommand = args[0];
|
|
2821
2828
|
if (subcommand === "viewports") {
|
|
2822
2829
|
// List viewport presets
|
|
2823
|
-
const presets =
|
|
2830
|
+
const presets = listViewportPresets();
|
|
2824
2831
|
console.log("\nš± Available Viewport Presets\n");
|
|
2825
2832
|
console.log("ā".repeat(60));
|
|
2826
2833
|
const byType = {
|
|
@@ -2856,14 +2863,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2856
2863
|
process.exit(1);
|
|
2857
2864
|
}
|
|
2858
2865
|
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2859
|
-
const result = await
|
|
2866
|
+
const result = await runResponsiveSuite(suite);
|
|
2860
2867
|
// Save outputs
|
|
2861
2868
|
if (options.output && !options.html) {
|
|
2862
2869
|
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2863
2870
|
console.log(`\nš JSON report saved to: ${options.output}`);
|
|
2864
2871
|
}
|
|
2865
2872
|
if (options.html) {
|
|
2866
|
-
const htmlReport =
|
|
2873
|
+
const htmlReport = generateResponsiveHtmlReport(result);
|
|
2867
2874
|
const outputPath = options.output || `responsive-${Date.now()}.html`;
|
|
2868
2875
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2869
2876
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2899,14 +2906,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2899
2906
|
const viewports = options.viewports
|
|
2900
2907
|
? options.viewports.split(",")
|
|
2901
2908
|
: undefined;
|
|
2902
|
-
const result = await
|
|
2909
|
+
const result = await runResponsiveTest(url, {
|
|
2903
2910
|
viewports,
|
|
2904
2911
|
waitBeforeCapture: options.wait ? parseInt(options.wait) : undefined,
|
|
2905
2912
|
waitForSelector: options["wait-for"],
|
|
2906
2913
|
sensitivity: options.sensitivity,
|
|
2907
2914
|
});
|
|
2908
2915
|
// Print report
|
|
2909
|
-
console.log("\n" +
|
|
2916
|
+
console.log("\n" + formatResponsiveReport(result));
|
|
2910
2917
|
// Save outputs
|
|
2911
2918
|
const fs = await import("fs");
|
|
2912
2919
|
if (options.output && !options.html) {
|
|
@@ -2928,7 +2935,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2928
2935
|
duration: result.duration,
|
|
2929
2936
|
timestamp: result.timestamp,
|
|
2930
2937
|
};
|
|
2931
|
-
const htmlReport =
|
|
2938
|
+
const htmlReport = generateResponsiveHtmlReport(suiteResult);
|
|
2932
2939
|
const outputPath = options.output || `responsive-${Date.now()}.html`;
|
|
2933
2940
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2934
2941
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2959,14 +2966,14 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2959
2966
|
process.exit(1);
|
|
2960
2967
|
}
|
|
2961
2968
|
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2962
|
-
const result = await
|
|
2969
|
+
const result = await runABSuite(suite);
|
|
2963
2970
|
// Save outputs
|
|
2964
2971
|
if (options.output && !options.html) {
|
|
2965
2972
|
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2966
2973
|
console.log(`\nš JSON report saved to: ${options.output}`);
|
|
2967
2974
|
}
|
|
2968
2975
|
if (options.html) {
|
|
2969
|
-
const htmlReport =
|
|
2976
|
+
const htmlReport = generateABHtmlReport(result);
|
|
2970
2977
|
const outputPath = options.output || `ab-comparison-${Date.now()}.html`;
|
|
2971
2978
|
fs.writeFileSync(outputPath, htmlReport);
|
|
2972
2979
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -2998,7 +3005,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
2998
3005
|
console.error(" --output <file> Save report");
|
|
2999
3006
|
process.exit(1);
|
|
3000
3007
|
}
|
|
3001
|
-
const result = await
|
|
3008
|
+
const result = await runABComparison(urlA, urlB, {
|
|
3002
3009
|
labels: options["label-a"] || options["label-b"] ? {
|
|
3003
3010
|
a: options["label-a"] || "Version A",
|
|
3004
3011
|
b: options["label-b"] || "Version B",
|
|
@@ -3012,7 +3019,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3012
3019
|
sensitivity: options.sensitivity,
|
|
3013
3020
|
});
|
|
3014
3021
|
// Print report
|
|
3015
|
-
console.log("\n" +
|
|
3022
|
+
console.log("\n" + formatABReport(result));
|
|
3016
3023
|
// Save outputs
|
|
3017
3024
|
const fs = await import("fs");
|
|
3018
3025
|
if (options.output && !options.html) {
|
|
@@ -3033,7 +3040,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3033
3040
|
duration: result.duration,
|
|
3034
3041
|
timestamp: result.timestamp,
|
|
3035
3042
|
};
|
|
3036
|
-
const htmlReport =
|
|
3043
|
+
const htmlReport = generateABHtmlReport(suiteResult);
|
|
3037
3044
|
const outputPath = options.output || `ab-comparison-${Date.now()}.html`;
|
|
3038
3045
|
fs.writeFileSync(outputPath, htmlReport);
|
|
3039
3046
|
console.log(`\nš HTML report saved to: ${outputPath}`);
|
|
@@ -3325,13 +3332,13 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3325
3332
|
}
|
|
3326
3333
|
const deviceList = options.devices
|
|
3327
3334
|
? options.devices.split(",")
|
|
3328
|
-
: Object.keys(
|
|
3335
|
+
: Object.keys(DEVICE_PRESETS);
|
|
3329
3336
|
const concurrency = options.concurrency ? parseInt(options.concurrency) : 3;
|
|
3330
3337
|
console.log(`\nš Running parallel device tests...`);
|
|
3331
3338
|
console.log(` URL: ${url}`);
|
|
3332
3339
|
console.log(` Devices: ${deviceList.length}`);
|
|
3333
3340
|
console.log(` Concurrency: ${concurrency}\n`);
|
|
3334
|
-
const results = await
|
|
3341
|
+
const results = await CBrowser.parallelDevices(deviceList, async (b, device) => {
|
|
3335
3342
|
const nav = await b.navigate(url);
|
|
3336
3343
|
const screenshot = await b.screenshot();
|
|
3337
3344
|
return { title: nav.title, loadTime: nav.loadTime, screenshot };
|
|
@@ -3359,7 +3366,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3359
3366
|
console.log(`\nš Running parallel URL tests...`);
|
|
3360
3367
|
console.log(` URLs: ${urls.length}`);
|
|
3361
3368
|
console.log(` Concurrency: ${concurrency}\n`);
|
|
3362
|
-
const results = await
|
|
3369
|
+
const results = await CBrowser.parallelUrls(urls, async (b, url) => {
|
|
3363
3370
|
const nav = await b.navigate(url);
|
|
3364
3371
|
return { title: nav.title, loadTime: nav.loadTime };
|
|
3365
3372
|
}, { maxConcurrency: concurrency });
|
|
@@ -3384,7 +3391,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3384
3391
|
console.log(`\nš Running parallel performance audits...`);
|
|
3385
3392
|
console.log(` URLs: ${urls.length}`);
|
|
3386
3393
|
console.log(` Concurrency: ${concurrency}\n`);
|
|
3387
|
-
const results = await
|
|
3394
|
+
const results = await CBrowser.parallelUrls(urls, async (b, url) => {
|
|
3388
3395
|
await b.navigate(url);
|
|
3389
3396
|
return await b.getPerformanceMetrics();
|
|
3390
3397
|
}, { maxConcurrency: concurrency });
|
|
@@ -3425,7 +3432,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3425
3432
|
process.exit(1);
|
|
3426
3433
|
}
|
|
3427
3434
|
console.log(`\nš£ļø Executing: "${nlCommand}"\n`);
|
|
3428
|
-
const result = await
|
|
3435
|
+
const result = await executeNaturalLanguage(browser, nlCommand);
|
|
3429
3436
|
if (result.success) {
|
|
3430
3437
|
console.log(`ā Action: ${result.action}`);
|
|
3431
3438
|
if (result.result && typeof result.result === "object") {
|
|
@@ -3461,7 +3468,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3461
3468
|
const commands = content.split("\n").filter(line => line.trim() && !line.trim().startsWith("#"));
|
|
3462
3469
|
console.log(`\nš Executing script: ${scriptFile}`);
|
|
3463
3470
|
console.log(` Commands: ${commands.length}\n`);
|
|
3464
|
-
const results = await
|
|
3471
|
+
const results = await executeNaturalLanguageScript(browser, commands);
|
|
3465
3472
|
for (const r of results) {
|
|
3466
3473
|
if (r.success) {
|
|
3467
3474
|
console.log(`ā ${r.command}`);
|
|
@@ -3497,7 +3504,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3497
3504
|
switch (subcommand) {
|
|
3498
3505
|
case "find": {
|
|
3499
3506
|
console.log(`\nš§ Finding element: "${intent}"\n`);
|
|
3500
|
-
const result = await
|
|
3507
|
+
const result = await findElementByIntent(browser, intent);
|
|
3501
3508
|
if (result) {
|
|
3502
3509
|
console.log(`ā Found element`);
|
|
3503
3510
|
console.log(` Selector: ${result.selector}`);
|
|
@@ -3512,7 +3519,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3512
3519
|
}
|
|
3513
3520
|
case "click": {
|
|
3514
3521
|
console.log(`\nš§ Finding and clicking: "${intent}"\n`);
|
|
3515
|
-
const result = await
|
|
3522
|
+
const result = await findElementByIntent(browser, intent);
|
|
3516
3523
|
if (result) {
|
|
3517
3524
|
console.log(`ā Found: ${result.description}`);
|
|
3518
3525
|
await browser.click(result.selector);
|
|
@@ -3545,7 +3552,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3545
3552
|
console.log(` URL: ${url}`);
|
|
3546
3553
|
console.log(` Max pages: ${maxPages}`);
|
|
3547
3554
|
console.log(` Timeout: ${timeout}ms\n`);
|
|
3548
|
-
const result = await
|
|
3555
|
+
const result = await huntBugs(browser, url, { maxPages, timeout });
|
|
3549
3556
|
console.log(`š Bug Hunt Results:\n`);
|
|
3550
3557
|
console.log(` Pages visited: ${result.pagesVisited}`);
|
|
3551
3558
|
console.log(` Duration: ${result.duration}ms`);
|
|
@@ -3584,7 +3591,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3584
3591
|
console.log(`\nš Cross-Browser Diff`);
|
|
3585
3592
|
console.log(` URL: ${url}`);
|
|
3586
3593
|
console.log(` Browsers: ${browserList.join(", ")}\n`);
|
|
3587
|
-
const result = await
|
|
3594
|
+
const result = await crossBrowserDiff(url, [...browserList]);
|
|
3588
3595
|
console.log(`š Results:\n`);
|
|
3589
3596
|
console.log(` Metrics:`);
|
|
3590
3597
|
for (const [browser, metrics] of Object.entries(result.metrics)) {
|
|
@@ -3633,7 +3640,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3633
3640
|
console.log(` URL: ${url}`);
|
|
3634
3641
|
console.log(` Chaos config:`, chaosConfig);
|
|
3635
3642
|
console.log("");
|
|
3636
|
-
const result = await
|
|
3643
|
+
const result = await runChaosTest(browser, url, chaosConfig);
|
|
3637
3644
|
console.log(`š Results:\n`);
|
|
3638
3645
|
console.log(` Passed: ${result.passed ? "ā
Yes" : "ā No"}`);
|
|
3639
3646
|
console.log(` Duration: ${result.duration}ms`);
|
|
@@ -3690,7 +3697,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3690
3697
|
const steps = inlineTest.split(";").map(s => s.trim()).filter(s => s);
|
|
3691
3698
|
const testCase = {
|
|
3692
3699
|
name: "Inline Test",
|
|
3693
|
-
steps: steps.map(s =>
|
|
3700
|
+
steps: steps.map(s => parseNLInstruction(s)),
|
|
3694
3701
|
};
|
|
3695
3702
|
suite = { name: "Inline Suite", tests: [testCase] };
|
|
3696
3703
|
}
|
|
@@ -3703,7 +3710,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3703
3710
|
}
|
|
3704
3711
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
3705
3712
|
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
3706
|
-
suite =
|
|
3713
|
+
suite = parseNLTestSuite(content, suiteName);
|
|
3707
3714
|
}
|
|
3708
3715
|
console.log(`\nš Parsed ${suite.tests.length} test(s) from ${inlineTest ? "inline" : filepath}`);
|
|
3709
3716
|
for (const test of suite.tests) {
|
|
@@ -3711,7 +3718,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3711
3718
|
}
|
|
3712
3719
|
// Dry-run mode: parse and display without executing
|
|
3713
3720
|
if (options["dry-run"]) {
|
|
3714
|
-
const dryResult =
|
|
3721
|
+
const dryResult = dryRunNLTestSuite(suite);
|
|
3715
3722
|
console.log(`\nš DRY RUN - Parsed steps (no execution):\n`);
|
|
3716
3723
|
for (const test of dryResult.tests) {
|
|
3717
3724
|
console.log(` š ${test.name}`);
|
|
@@ -3815,9 +3822,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3815
3822
|
headless,
|
|
3816
3823
|
fuzzyMatch: options["fuzzy-match"] === true,
|
|
3817
3824
|
};
|
|
3818
|
-
const result = await
|
|
3825
|
+
const result = await runNLTestSuite(suite, suiteOptions);
|
|
3819
3826
|
// Print formatted report
|
|
3820
|
-
const report =
|
|
3827
|
+
const report = formatNLTestReport(result);
|
|
3821
3828
|
console.log(report);
|
|
3822
3829
|
// Save JSON output if requested
|
|
3823
3830
|
if (options.output) {
|
|
@@ -3867,7 +3874,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3867
3874
|
}
|
|
3868
3875
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
3869
3876
|
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
3870
|
-
const suite =
|
|
3877
|
+
const suite = parseNLTestSuite(content, suiteName);
|
|
3871
3878
|
console.log(`\nš Parsed ${suite.tests.length} test(s) from ${filepath}`);
|
|
3872
3879
|
for (const test of suite.tests) {
|
|
3873
3880
|
console.log(` - ${test.name}: ${test.steps.length} steps`);
|
|
@@ -3878,9 +3885,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3878
3885
|
verifyRepairs: options.verify === true,
|
|
3879
3886
|
maxRetries: options.retries ? parseInt(options.retries) : 3,
|
|
3880
3887
|
};
|
|
3881
|
-
const result = await
|
|
3888
|
+
const result = await repairTestSuite(suite, repairOptions);
|
|
3882
3889
|
// Print formatted report
|
|
3883
|
-
const report =
|
|
3890
|
+
const report = formatRepairReport(result);
|
|
3884
3891
|
console.log(report);
|
|
3885
3892
|
// Save JSON report if requested
|
|
3886
3893
|
if (options.json) {
|
|
@@ -3891,7 +3898,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3891
3898
|
if (options.output && repairOptions.autoApply) {
|
|
3892
3899
|
const repairedContent = [];
|
|
3893
3900
|
for (const testResult of result.testResults) {
|
|
3894
|
-
repairedContent.push(
|
|
3901
|
+
repairedContent.push(exportRepairedTest(testResult));
|
|
3895
3902
|
repairedContent.push("");
|
|
3896
3903
|
}
|
|
3897
3904
|
fs.writeFileSync(options.output, repairedContent.join("\n"));
|
|
@@ -3932,7 +3939,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3932
3939
|
}
|
|
3933
3940
|
const content = fs.readFileSync(filepath, "utf-8");
|
|
3934
3941
|
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
3935
|
-
const suite =
|
|
3942
|
+
const suite = parseNLTestSuite(content, suiteName);
|
|
3936
3943
|
console.log(`\nš Parsed ${suite.tests.length} test(s) from ${filepath}`);
|
|
3937
3944
|
for (const test of suite.tests) {
|
|
3938
3945
|
console.log(` - ${test.name}: ${test.steps.length} steps`);
|
|
@@ -3943,9 +3950,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3943
3950
|
flakinessThreshold: options.threshold ? parseInt(options.threshold) : 20,
|
|
3944
3951
|
delayBetweenRuns: options.delay ? parseInt(options.delay) : 500,
|
|
3945
3952
|
};
|
|
3946
|
-
const result = await
|
|
3953
|
+
const result = await detectFlakyTests(suite, flakyOptions);
|
|
3947
3954
|
// Print formatted report
|
|
3948
|
-
const report =
|
|
3955
|
+
const report = formatFlakyTestReport(result);
|
|
3949
3956
|
console.log(report);
|
|
3950
3957
|
// Save JSON report if requested
|
|
3951
3958
|
if (options.output) {
|
|
@@ -3978,7 +3985,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
3978
3985
|
runs: options.runs ? parseInt(options.runs) : 3,
|
|
3979
3986
|
};
|
|
3980
3987
|
console.log(` Running ${baselineOptions.runs} measurement(s)...`);
|
|
3981
|
-
const baseline = await
|
|
3988
|
+
const baseline = await capturePerformanceBaseline(url, baselineOptions);
|
|
3982
3989
|
console.log(`\nā
Baseline saved: ${baseline.name}`);
|
|
3983
3990
|
console.log(` ID: ${baseline.id}`);
|
|
3984
3991
|
console.log(` URL: ${baseline.url}`);
|
|
@@ -4005,7 +4012,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4005
4012
|
break;
|
|
4006
4013
|
}
|
|
4007
4014
|
case "list": {
|
|
4008
|
-
const baselines =
|
|
4015
|
+
const baselines = listPerformanceBaselines();
|
|
4009
4016
|
if (baselines.length === 0) {
|
|
4010
4017
|
console.log("\nš No performance baselines saved yet.");
|
|
4011
4018
|
console.log(" Use: cbrowser perf-baseline save <url> --name <name>");
|
|
@@ -4030,7 +4037,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4030
4037
|
console.error("Usage: cbrowser perf-baseline show <name|id>");
|
|
4031
4038
|
process.exit(1);
|
|
4032
4039
|
}
|
|
4033
|
-
const baseline =
|
|
4040
|
+
const baseline = loadPerformanceBaseline(name);
|
|
4034
4041
|
if (!baseline) {
|
|
4035
4042
|
console.error(`Baseline not found: ${name}`);
|
|
4036
4043
|
process.exit(1);
|
|
@@ -4052,7 +4059,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4052
4059
|
console.error("Usage: cbrowser perf-baseline delete <name|id>");
|
|
4053
4060
|
process.exit(1);
|
|
4054
4061
|
}
|
|
4055
|
-
const deleted =
|
|
4062
|
+
const deleted = deletePerformanceBaseline(name);
|
|
4056
4063
|
if (deleted) {
|
|
4057
4064
|
console.log(`\nā
Deleted baseline: ${name}`);
|
|
4058
4065
|
}
|
|
@@ -4124,9 +4131,9 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4124
4131
|
thresholds: Object.keys(thresholds).length > 0 ? thresholds : undefined,
|
|
4125
4132
|
};
|
|
4126
4133
|
try {
|
|
4127
|
-
const result = await
|
|
4134
|
+
const result = await detectPerformanceRegression(url, baselineName, regressionOptions);
|
|
4128
4135
|
// Print formatted report
|
|
4129
|
-
const report =
|
|
4136
|
+
const report = formatPerformanceRegressionReport(result);
|
|
4130
4137
|
console.log("\n" + report);
|
|
4131
4138
|
// Save JSON report if requested
|
|
4132
4139
|
const fs = await import("fs");
|
|
@@ -4192,7 +4199,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4192
4199
|
maxPages: 50, // Quick mode
|
|
4193
4200
|
minCoverage: 50,
|
|
4194
4201
|
};
|
|
4195
|
-
const result = await
|
|
4202
|
+
const result = await generateCoverageMap(url, testFiles, coverageOptions);
|
|
4196
4203
|
// Show only gaps
|
|
4197
4204
|
console.log(`\nš³ļø Coverage Gaps (${result.gaps.length} found):\n`);
|
|
4198
4205
|
const priorityEmoji = { critical: "šØ", high: "š“", medium: "š”", low: "š¢" };
|
|
@@ -4238,10 +4245,10 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4238
4245
|
else {
|
|
4239
4246
|
console.log(` Crawling site (max ${coverageOptions.maxPages} pages)...`);
|
|
4240
4247
|
}
|
|
4241
|
-
const result = await
|
|
4248
|
+
const result = await generateCoverageMap(url, testFiles, coverageOptions);
|
|
4242
4249
|
// Output format
|
|
4243
4250
|
if (options.html) {
|
|
4244
|
-
const htmlReport =
|
|
4251
|
+
const htmlReport = generateCoverageHtmlReport(result);
|
|
4245
4252
|
const outputPath = options.output || "coverage-report.html";
|
|
4246
4253
|
fs.writeFileSync(outputPath, htmlReport);
|
|
4247
4254
|
console.log(`\nā
HTML report saved: ${outputPath}`);
|
|
@@ -4252,7 +4259,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4252
4259
|
}
|
|
4253
4260
|
else {
|
|
4254
4261
|
// Print text report
|
|
4255
|
-
const report =
|
|
4262
|
+
const report = formatCoverageReport(result);
|
|
4256
4263
|
console.log(report);
|
|
4257
4264
|
if (options.output) {
|
|
4258
4265
|
fs.writeFileSync(options.output, report);
|
|
@@ -4271,7 +4278,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4271
4278
|
// =========================================================================
|
|
4272
4279
|
case "cognitive-journey": {
|
|
4273
4280
|
// Check API key first
|
|
4274
|
-
if (!
|
|
4281
|
+
if (!isApiKeyConfigured()) {
|
|
4275
4282
|
console.error("ā Anthropic API key not configured.");
|
|
4276
4283
|
console.error("");
|
|
4277
4284
|
console.error("Set your API key with:");
|
|
@@ -4305,7 +4312,7 @@ Documentation: https://github.com/alexandriashai/cbrowser/wiki
|
|
|
4305
4312
|
console.log(` Vision: enabled (screenshots sent to Claude)`);
|
|
4306
4313
|
console.log("");
|
|
4307
4314
|
try {
|
|
4308
|
-
const result = await
|
|
4315
|
+
const result = await runCognitiveJourney({
|
|
4309
4316
|
persona: personaName,
|
|
4310
4317
|
goal,
|
|
4311
4318
|
startUrl,
|