cbrowser 6.3.2 → 6.5.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/dist/browser.d.ts +75 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1071 -0
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +435 -2
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +61 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +358 -0
- package/dist/daemon.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +209 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,11 +10,12 @@ const browser_js_1 = require("./browser.js");
|
|
|
10
10
|
const personas_js_1 = require("./personas.js");
|
|
11
11
|
const types_js_1 = require("./types.js");
|
|
12
12
|
const mcp_server_js_1 = require("./mcp-server.js");
|
|
13
|
+
const daemon_js_1 = require("./daemon.js");
|
|
13
14
|
function showHelp() {
|
|
14
15
|
console.log(`
|
|
15
16
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
16
|
-
║ CBrowser CLI v6.
|
|
17
|
-
║ AI-powered browser automation with
|
|
17
|
+
║ CBrowser CLI v6.5.0 ║
|
|
18
|
+
║ AI-powered browser automation with test coverage mapping ║
|
|
18
19
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
19
20
|
|
|
20
21
|
NAVIGATION
|
|
@@ -98,6 +99,26 @@ FLAKY TEST DETECTION (v6.3.0)
|
|
|
98
99
|
cbrowser flaky-check tests.txt --runs 10
|
|
99
100
|
cbrowser flaky-check tests.txt --runs 5 --threshold 30 --output flaky-report.json
|
|
100
101
|
|
|
102
|
+
TEST COVERAGE MAP (v6.5.0)
|
|
103
|
+
coverage <url> Generate test coverage map for a site
|
|
104
|
+
--tests <glob> Test files to analyze (default: tests/*.txt)
|
|
105
|
+
--sitemap <url> Use sitemap.xml instead of crawling
|
|
106
|
+
--max-pages <n> Max pages to crawl (default: 100)
|
|
107
|
+
--include <pattern> Only include paths matching pattern
|
|
108
|
+
--exclude <pattern> Exclude paths matching pattern
|
|
109
|
+
--min-coverage <n> Min coverage % to not flag (default: 50)
|
|
110
|
+
--html Generate HTML report
|
|
111
|
+
--output <file> Save JSON report to file
|
|
112
|
+
Examples:
|
|
113
|
+
cbrowser coverage "https://example.com" --tests "tests/*.txt"
|
|
114
|
+
cbrowser coverage "https://example.com" --sitemap "https://example.com/sitemap.xml"
|
|
115
|
+
cbrowser coverage "https://example.com" --html --output coverage.html
|
|
116
|
+
cbrowser coverage "https://example.com" --exclude "/admin" --min-coverage 70
|
|
117
|
+
|
|
118
|
+
coverage gaps <url> Show only untested pages (quick analysis)
|
|
119
|
+
--tests <glob> Test files to analyze
|
|
120
|
+
--sitemap <url> Use sitemap.xml
|
|
121
|
+
|
|
101
122
|
PERSONAS
|
|
102
123
|
persona list List all personas (built-in + custom)
|
|
103
124
|
persona create "<desc>" Create persona from natural language description
|
|
@@ -147,6 +168,27 @@ PERFORMANCE
|
|
|
147
168
|
--budget-fcp <ms> FCP budget (default: 1800)
|
|
148
169
|
--budget-cls <score> CLS budget (default: 0.1)
|
|
149
170
|
|
|
171
|
+
PERFORMANCE REGRESSION (v6.4.0)
|
|
172
|
+
perf-baseline save <url> Capture and save performance baseline
|
|
173
|
+
--name <name> Human-readable name for baseline
|
|
174
|
+
--runs <n> Number of runs to average (default: 3)
|
|
175
|
+
Examples:
|
|
176
|
+
cbrowser perf-baseline save "https://example.com" --name "homepage"
|
|
177
|
+
cbrowser perf-baseline save "https://example.com/checkout" --runs 5
|
|
178
|
+
|
|
179
|
+
perf-baseline list List all saved baselines
|
|
180
|
+
perf-baseline show <name> Show baseline details
|
|
181
|
+
perf-baseline delete <name> Delete a baseline
|
|
182
|
+
|
|
183
|
+
perf-regression <url> <baseline> Compare current performance against baseline
|
|
184
|
+
--threshold-lcp <n> Max LCP increase % (default: 20)
|
|
185
|
+
--threshold-cls <n> Max CLS increase (default: 0.1)
|
|
186
|
+
--threshold-fcp <n> Max FCP increase % (default: 20)
|
|
187
|
+
--output <file> Save JSON report to file
|
|
188
|
+
Examples:
|
|
189
|
+
cbrowser perf-regression "https://example.com" homepage
|
|
190
|
+
cbrowser perf-regression "https://example.com" homepage --threshold-lcp 30
|
|
191
|
+
|
|
150
192
|
NETWORK / HAR
|
|
151
193
|
har start Start recording HAR
|
|
152
194
|
har stop [output] Stop and save HAR file
|
|
@@ -259,6 +301,16 @@ MCP SERVER (v5.0.0)
|
|
|
259
301
|
mcp-server Start CBrowser as MCP server for Claude integration
|
|
260
302
|
Use with Claude Desktop or other MCP-compatible clients
|
|
261
303
|
|
|
304
|
+
DAEMON MODE (v6.4.0)
|
|
305
|
+
daemon start Start background daemon (keeps browser running)
|
|
306
|
+
--port <port> Daemon port (default: 9222)
|
|
307
|
+
--timeout <min> Idle timeout in minutes (default: 30)
|
|
308
|
+
daemon stop Stop the running daemon
|
|
309
|
+
daemon status Check if daemon is running
|
|
310
|
+
daemon run Run daemon in foreground (internal use)
|
|
311
|
+
Note: When daemon is running, all commands automatically connect to it
|
|
312
|
+
instead of launching a new browser - much faster for iteration!
|
|
313
|
+
|
|
262
314
|
STORAGE & CLEANUP
|
|
263
315
|
storage Show storage usage statistics
|
|
264
316
|
cleanup Clean up old files
|
|
@@ -661,6 +713,90 @@ async function main() {
|
|
|
661
713
|
await (0, mcp_server_js_1.startMcpServer)();
|
|
662
714
|
return;
|
|
663
715
|
}
|
|
716
|
+
// Daemon mode commands
|
|
717
|
+
if (command === "daemon") {
|
|
718
|
+
const subCommand = args[0];
|
|
719
|
+
const port = parseInt(options.port) || 9222;
|
|
720
|
+
switch (subCommand) {
|
|
721
|
+
case "start": {
|
|
722
|
+
console.log("🚀 Starting CBrowser daemon...");
|
|
723
|
+
const result = await (0, daemon_js_1.startDaemon)(port);
|
|
724
|
+
console.log(result.success ? `✓ ${result.message}` : `✗ ${result.message}`);
|
|
725
|
+
process.exit(result.success ? 0 : 1);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
case "stop": {
|
|
729
|
+
console.log("🛑 Stopping CBrowser daemon...");
|
|
730
|
+
const result = await (0, daemon_js_1.stopDaemon)();
|
|
731
|
+
console.log(result.success ? `✓ ${result.message}` : `✗ ${result.message}`);
|
|
732
|
+
process.exit(0);
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
case "status": {
|
|
736
|
+
const status = await (0, daemon_js_1.getDaemonStatus)();
|
|
737
|
+
console.log(status);
|
|
738
|
+
process.exit(0);
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
case "run": {
|
|
742
|
+
// Internal: run daemon in foreground
|
|
743
|
+
console.log("🔧 Running daemon in foreground mode...");
|
|
744
|
+
const browserType = options.browser === "firefox" ? "firefox"
|
|
745
|
+
: options.browser === "webkit" ? "webkit"
|
|
746
|
+
: "chromium";
|
|
747
|
+
// runDaemonServer will merge with defaults internally
|
|
748
|
+
await (0, daemon_js_1.runDaemonServer)({
|
|
749
|
+
browser: browserType,
|
|
750
|
+
headless: options.headless !== false && options.headless !== "false",
|
|
751
|
+
}, port);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
default:
|
|
755
|
+
console.error(`Unknown daemon command: ${subCommand}`);
|
|
756
|
+
console.error("Use: daemon start | daemon stop | daemon status");
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
// Check if daemon is running and use it for supported commands
|
|
762
|
+
const daemonRunning = await (0, daemon_js_1.isDaemonRunning)();
|
|
763
|
+
if (daemonRunning && ["navigate", "click", "fill", "screenshot", "extract", "run"].includes(command)) {
|
|
764
|
+
console.log("🔌 Connected to running daemon");
|
|
765
|
+
let daemonCommand = command;
|
|
766
|
+
let daemonArgs = {};
|
|
767
|
+
switch (command) {
|
|
768
|
+
case "navigate":
|
|
769
|
+
daemonArgs = { url: args[0] };
|
|
770
|
+
break;
|
|
771
|
+
case "click":
|
|
772
|
+
daemonArgs = { selector: args[0] };
|
|
773
|
+
break;
|
|
774
|
+
case "fill":
|
|
775
|
+
daemonArgs = { selector: args[0], value: args[1] };
|
|
776
|
+
break;
|
|
777
|
+
case "screenshot":
|
|
778
|
+
daemonArgs = { path: args[0] };
|
|
779
|
+
break;
|
|
780
|
+
case "extract":
|
|
781
|
+
daemonArgs = { what: args[0] };
|
|
782
|
+
break;
|
|
783
|
+
case "run":
|
|
784
|
+
daemonArgs = { command: args.join(" ") };
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
const result = await (0, daemon_js_1.sendToDaemon)(daemonCommand, daemonArgs);
|
|
788
|
+
if (result.success) {
|
|
789
|
+
console.log("✓ Command executed via daemon");
|
|
790
|
+
if (result.result) {
|
|
791
|
+
console.log(JSON.stringify(result.result, null, 2));
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
console.error(`✗ Daemon error: ${result.error}`);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
664
800
|
// Parse browser type
|
|
665
801
|
const browserType = options.browser === "firefox" ? "firefox"
|
|
666
802
|
: options.browser === "webkit" ? "webkit"
|
|
@@ -2551,6 +2687,303 @@ async function main() {
|
|
|
2551
2687
|
}
|
|
2552
2688
|
break;
|
|
2553
2689
|
}
|
|
2690
|
+
// =========================================================================
|
|
2691
|
+
// Performance Regression Detection (Tier 6 - v6.4.0)
|
|
2692
|
+
// =========================================================================
|
|
2693
|
+
case "perf-baseline": {
|
|
2694
|
+
const subcommand = args[0];
|
|
2695
|
+
const fs = await import("fs");
|
|
2696
|
+
switch (subcommand) {
|
|
2697
|
+
case "save": {
|
|
2698
|
+
const url = args[1];
|
|
2699
|
+
if (!url) {
|
|
2700
|
+
console.error("Usage: cbrowser perf-baseline save <url> [--name <name>] [--runs <n>]");
|
|
2701
|
+
process.exit(1);
|
|
2702
|
+
}
|
|
2703
|
+
console.log(`\n📊 Capturing performance baseline for: ${url}`);
|
|
2704
|
+
const baselineOptions = {
|
|
2705
|
+
headless,
|
|
2706
|
+
name: options.name,
|
|
2707
|
+
runs: options.runs ? parseInt(options.runs) : 3,
|
|
2708
|
+
};
|
|
2709
|
+
console.log(` Running ${baselineOptions.runs} measurement(s)...`);
|
|
2710
|
+
const baseline = await (0, browser_js_1.capturePerformanceBaseline)(url, baselineOptions);
|
|
2711
|
+
console.log(`\n✅ Baseline saved: ${baseline.name}`);
|
|
2712
|
+
console.log(` ID: ${baseline.id}`);
|
|
2713
|
+
console.log(` URL: ${baseline.url}`);
|
|
2714
|
+
console.log(` Timestamp: ${new Date(baseline.timestamp).toLocaleString()}`);
|
|
2715
|
+
console.log(`\n📈 Metrics (averaged over ${baseline.runsAveraged} runs):`);
|
|
2716
|
+
if (baseline.metrics.lcp !== undefined) {
|
|
2717
|
+
console.log(` LCP: ${baseline.metrics.lcp.toFixed(0)}ms (${baseline.metrics.lcpRating})`);
|
|
2718
|
+
}
|
|
2719
|
+
if (baseline.metrics.fcp !== undefined) {
|
|
2720
|
+
console.log(` FCP: ${baseline.metrics.fcp.toFixed(0)}ms`);
|
|
2721
|
+
}
|
|
2722
|
+
if (baseline.metrics.cls !== undefined) {
|
|
2723
|
+
console.log(` CLS: ${baseline.metrics.cls.toFixed(3)} (${baseline.metrics.clsRating})`);
|
|
2724
|
+
}
|
|
2725
|
+
if (baseline.metrics.ttfb !== undefined) {
|
|
2726
|
+
console.log(` TTFB: ${baseline.metrics.ttfb.toFixed(0)}ms`);
|
|
2727
|
+
}
|
|
2728
|
+
if (baseline.metrics.tti !== undefined) {
|
|
2729
|
+
console.log(` TTI: ${baseline.metrics.tti.toFixed(0)}ms`);
|
|
2730
|
+
}
|
|
2731
|
+
if (baseline.metrics.transferSize !== undefined) {
|
|
2732
|
+
console.log(` Transfer: ${(baseline.metrics.transferSize / 1024).toFixed(1)}KB`);
|
|
2733
|
+
}
|
|
2734
|
+
break;
|
|
2735
|
+
}
|
|
2736
|
+
case "list": {
|
|
2737
|
+
const baselines = (0, browser_js_1.listPerformanceBaselines)();
|
|
2738
|
+
if (baselines.length === 0) {
|
|
2739
|
+
console.log("\n📊 No performance baselines saved yet.");
|
|
2740
|
+
console.log(" Use: cbrowser perf-baseline save <url> --name <name>");
|
|
2741
|
+
}
|
|
2742
|
+
else {
|
|
2743
|
+
console.log(`\n📊 Performance Baselines (${baselines.length}):\n`);
|
|
2744
|
+
for (const b of baselines) {
|
|
2745
|
+
const date = new Date(b.timestamp).toLocaleDateString();
|
|
2746
|
+
const lcp = b.metrics.lcp ? `LCP: ${b.metrics.lcp.toFixed(0)}ms` : "";
|
|
2747
|
+
console.log(` ${b.name}`);
|
|
2748
|
+
console.log(` ID: ${b.id}`);
|
|
2749
|
+
console.log(` URL: ${b.url}`);
|
|
2750
|
+
console.log(` Date: ${date} | ${lcp}`);
|
|
2751
|
+
console.log("");
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
break;
|
|
2755
|
+
}
|
|
2756
|
+
case "show": {
|
|
2757
|
+
const name = args[1];
|
|
2758
|
+
if (!name) {
|
|
2759
|
+
console.error("Usage: cbrowser perf-baseline show <name|id>");
|
|
2760
|
+
process.exit(1);
|
|
2761
|
+
}
|
|
2762
|
+
const baseline = (0, browser_js_1.loadPerformanceBaseline)(name);
|
|
2763
|
+
if (!baseline) {
|
|
2764
|
+
console.error(`Baseline not found: ${name}`);
|
|
2765
|
+
process.exit(1);
|
|
2766
|
+
}
|
|
2767
|
+
console.log(`\n📊 Performance Baseline: ${baseline.name}`);
|
|
2768
|
+
console.log(` ID: ${baseline.id}`);
|
|
2769
|
+
console.log(` URL: ${baseline.url}`);
|
|
2770
|
+
console.log(` Timestamp: ${new Date(baseline.timestamp).toLocaleString()}`);
|
|
2771
|
+
console.log(` Runs Averaged: ${baseline.runsAveraged}`);
|
|
2772
|
+
console.log(`\n📈 Metrics:`);
|
|
2773
|
+
console.log(JSON.stringify(baseline.metrics, null, 2));
|
|
2774
|
+
console.log(`\n🖥️ Environment:`);
|
|
2775
|
+
console.log(JSON.stringify(baseline.environment, null, 2));
|
|
2776
|
+
break;
|
|
2777
|
+
}
|
|
2778
|
+
case "delete": {
|
|
2779
|
+
const name = args[1];
|
|
2780
|
+
if (!name) {
|
|
2781
|
+
console.error("Usage: cbrowser perf-baseline delete <name|id>");
|
|
2782
|
+
process.exit(1);
|
|
2783
|
+
}
|
|
2784
|
+
const deleted = (0, browser_js_1.deletePerformanceBaseline)(name);
|
|
2785
|
+
if (deleted) {
|
|
2786
|
+
console.log(`\n✅ Deleted baseline: ${name}`);
|
|
2787
|
+
}
|
|
2788
|
+
else {
|
|
2789
|
+
console.error(`Baseline not found: ${name}`);
|
|
2790
|
+
process.exit(1);
|
|
2791
|
+
}
|
|
2792
|
+
break;
|
|
2793
|
+
}
|
|
2794
|
+
default:
|
|
2795
|
+
console.error("Usage: cbrowser perf-baseline <save|list|show|delete>");
|
|
2796
|
+
console.error("");
|
|
2797
|
+
console.error("Subcommands:");
|
|
2798
|
+
console.error(" save <url> Capture and save performance baseline");
|
|
2799
|
+
console.error(" list List all saved baselines");
|
|
2800
|
+
console.error(" show <name> Show baseline details");
|
|
2801
|
+
console.error(" delete <name> Delete a baseline");
|
|
2802
|
+
process.exit(1);
|
|
2803
|
+
}
|
|
2804
|
+
break;
|
|
2805
|
+
}
|
|
2806
|
+
case "perf-regression": {
|
|
2807
|
+
const url = args[0];
|
|
2808
|
+
const baselineName = args[1];
|
|
2809
|
+
if (!url || !baselineName) {
|
|
2810
|
+
console.error("Usage: cbrowser perf-regression <url> <baseline-name> [options]");
|
|
2811
|
+
console.error("");
|
|
2812
|
+
console.error("Options:");
|
|
2813
|
+
console.error(" --threshold-lcp <n> Max LCP increase % (default: 20)");
|
|
2814
|
+
console.error(" --threshold-cls <n> Max CLS increase absolute (default: 0.1)");
|
|
2815
|
+
console.error(" --threshold-fcp <n> Max FCP increase % (default: 20)");
|
|
2816
|
+
console.error(" --threshold-ttfb <n> Max TTFB increase % (default: 30)");
|
|
2817
|
+
console.error(" --output <file> Save JSON report to file");
|
|
2818
|
+
console.error("");
|
|
2819
|
+
console.error("Examples:");
|
|
2820
|
+
console.error(" cbrowser perf-regression https://example.com homepage");
|
|
2821
|
+
console.error(" cbrowser perf-regression https://example.com homepage --threshold-lcp 30");
|
|
2822
|
+
process.exit(1);
|
|
2823
|
+
}
|
|
2824
|
+
console.log(`\n🔍 Checking for performance regressions...`);
|
|
2825
|
+
console.log(` URL: ${url}`);
|
|
2826
|
+
console.log(` Baseline: ${baselineName}`);
|
|
2827
|
+
const thresholds = {};
|
|
2828
|
+
if (options["threshold-lcp"])
|
|
2829
|
+
thresholds.lcp = parseInt(options["threshold-lcp"]);
|
|
2830
|
+
if (options["threshold-cls"])
|
|
2831
|
+
thresholds.cls = parseFloat(options["threshold-cls"]);
|
|
2832
|
+
if (options["threshold-fcp"])
|
|
2833
|
+
thresholds.fcp = parseInt(options["threshold-fcp"]);
|
|
2834
|
+
if (options["threshold-ttfb"])
|
|
2835
|
+
thresholds.ttfb = parseInt(options["threshold-ttfb"]);
|
|
2836
|
+
if (options["threshold-tti"])
|
|
2837
|
+
thresholds.tti = parseInt(options["threshold-tti"]);
|
|
2838
|
+
if (options["threshold-tbt"])
|
|
2839
|
+
thresholds.tbt = parseInt(options["threshold-tbt"]);
|
|
2840
|
+
const regressionOptions = {
|
|
2841
|
+
headless,
|
|
2842
|
+
thresholds: Object.keys(thresholds).length > 0 ? thresholds : undefined,
|
|
2843
|
+
};
|
|
2844
|
+
try {
|
|
2845
|
+
const result = await (0, browser_js_1.detectPerformanceRegression)(url, baselineName, regressionOptions);
|
|
2846
|
+
// Print formatted report
|
|
2847
|
+
const report = (0, browser_js_1.formatPerformanceRegressionReport)(result);
|
|
2848
|
+
console.log("\n" + report);
|
|
2849
|
+
// Save JSON report if requested
|
|
2850
|
+
const fs = await import("fs");
|
|
2851
|
+
if (options.output) {
|
|
2852
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2853
|
+
console.log(`\n📄 JSON report saved: ${options.output}`);
|
|
2854
|
+
}
|
|
2855
|
+
// Exit with error code if regressions found
|
|
2856
|
+
if (!result.passed) {
|
|
2857
|
+
process.exit(1);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
catch (error) {
|
|
2861
|
+
console.error(`\n❌ Error: ${error.message}`);
|
|
2862
|
+
process.exit(1);
|
|
2863
|
+
}
|
|
2864
|
+
break;
|
|
2865
|
+
}
|
|
2866
|
+
// =========================================================================
|
|
2867
|
+
// Test Coverage Map (Tier 6 - v6.5.0)
|
|
2868
|
+
// =========================================================================
|
|
2869
|
+
case "coverage": {
|
|
2870
|
+
const fs = await import("fs");
|
|
2871
|
+
const path = await import("path");
|
|
2872
|
+
const subcommand = args[0];
|
|
2873
|
+
// Simple glob function for test files
|
|
2874
|
+
function findTestFiles(pattern) {
|
|
2875
|
+
const files = [];
|
|
2876
|
+
const parts = pattern.split("/");
|
|
2877
|
+
const dir = parts.slice(0, -1).join("/") || ".";
|
|
2878
|
+
const filePattern = parts[parts.length - 1];
|
|
2879
|
+
const regex = new RegExp("^" + filePattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$");
|
|
2880
|
+
try {
|
|
2881
|
+
const dirFiles = fs.readdirSync(dir);
|
|
2882
|
+
for (const file of dirFiles) {
|
|
2883
|
+
if (regex.test(file)) {
|
|
2884
|
+
files.push(path.join(dir, file));
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
catch {
|
|
2889
|
+
// Directory doesn't exist
|
|
2890
|
+
}
|
|
2891
|
+
return files;
|
|
2892
|
+
}
|
|
2893
|
+
// Handle "coverage gaps" subcommand
|
|
2894
|
+
if (subcommand === "gaps") {
|
|
2895
|
+
const url = args[1];
|
|
2896
|
+
if (!url) {
|
|
2897
|
+
console.error("Usage: cbrowser coverage gaps <url> [--tests <glob>] [--sitemap <url>]");
|
|
2898
|
+
process.exit(1);
|
|
2899
|
+
}
|
|
2900
|
+
console.log(`\n🔍 Finding untested pages for: ${url}`);
|
|
2901
|
+
const testPattern = options.tests || "tests/*.txt";
|
|
2902
|
+
const testFiles = findTestFiles(testPattern);
|
|
2903
|
+
if (testFiles.length === 0) {
|
|
2904
|
+
console.error(`No test files found matching: ${testPattern}`);
|
|
2905
|
+
process.exit(1);
|
|
2906
|
+
}
|
|
2907
|
+
console.log(` Analyzing ${testFiles.length} test file(s)...`);
|
|
2908
|
+
const coverageOptions = {
|
|
2909
|
+
sitemapUrl: options.sitemap,
|
|
2910
|
+
maxPages: 50, // Quick mode
|
|
2911
|
+
minCoverage: 50,
|
|
2912
|
+
};
|
|
2913
|
+
const result = await (0, browser_js_1.generateCoverageMap)(url, testFiles, coverageOptions);
|
|
2914
|
+
// Show only gaps
|
|
2915
|
+
console.log(`\n🕳️ Coverage Gaps (${result.gaps.length} found):\n`);
|
|
2916
|
+
const priorityEmoji = { critical: "🚨", high: "🔴", medium: "🟡", low: "🟢" };
|
|
2917
|
+
for (const gap of result.gaps) {
|
|
2918
|
+
const emoji = priorityEmoji[gap.priority];
|
|
2919
|
+
console.log(` ${emoji} ${gap.page.path}`);
|
|
2920
|
+
console.log(` Priority: ${gap.priority} | Reason: ${gap.reason}`);
|
|
2921
|
+
}
|
|
2922
|
+
console.log(`\n📊 Coverage: ${result.analysis.coveragePercent}% (${result.analysis.testedPages}/${result.analysis.totalPages} pages)`);
|
|
2923
|
+
break;
|
|
2924
|
+
}
|
|
2925
|
+
// Main coverage command
|
|
2926
|
+
const url = subcommand;
|
|
2927
|
+
if (!url || url.startsWith("-")) {
|
|
2928
|
+
console.error("Usage: cbrowser coverage <url> [--tests <glob>] [--sitemap <url>] [--html] [--output <file>]");
|
|
2929
|
+
process.exit(1);
|
|
2930
|
+
}
|
|
2931
|
+
console.log(`\n📊 Generating test coverage map for: ${url}`);
|
|
2932
|
+
const testPattern = options.tests || "tests/*.txt";
|
|
2933
|
+
const testFiles = findTestFiles(testPattern);
|
|
2934
|
+
if (testFiles.length === 0) {
|
|
2935
|
+
console.error(`No test files found matching: ${testPattern}`);
|
|
2936
|
+
console.error("Use --tests <glob> to specify test files");
|
|
2937
|
+
process.exit(1);
|
|
2938
|
+
}
|
|
2939
|
+
console.log(` Found ${testFiles.length} test file(s)`);
|
|
2940
|
+
for (const f of testFiles.slice(0, 5)) {
|
|
2941
|
+
console.log(` - ${f}`);
|
|
2942
|
+
}
|
|
2943
|
+
if (testFiles.length > 5) {
|
|
2944
|
+
console.log(` ... and ${testFiles.length - 5} more`);
|
|
2945
|
+
}
|
|
2946
|
+
const coverageOptions = {
|
|
2947
|
+
sitemapUrl: options.sitemap,
|
|
2948
|
+
maxPages: options["max-pages"] ? parseInt(options["max-pages"]) : 100,
|
|
2949
|
+
includePattern: options.include,
|
|
2950
|
+
excludePattern: options.exclude,
|
|
2951
|
+
minCoverage: options["min-coverage"] ? parseInt(options["min-coverage"]) : 50,
|
|
2952
|
+
};
|
|
2953
|
+
if (coverageOptions.sitemapUrl) {
|
|
2954
|
+
console.log(` Using sitemap: ${coverageOptions.sitemapUrl}`);
|
|
2955
|
+
}
|
|
2956
|
+
else {
|
|
2957
|
+
console.log(` Crawling site (max ${coverageOptions.maxPages} pages)...`);
|
|
2958
|
+
}
|
|
2959
|
+
const result = await (0, browser_js_1.generateCoverageMap)(url, testFiles, coverageOptions);
|
|
2960
|
+
// Output format
|
|
2961
|
+
if (options.html) {
|
|
2962
|
+
const htmlReport = (0, browser_js_1.generateCoverageHtmlReport)(result);
|
|
2963
|
+
const outputPath = options.output || "coverage-report.html";
|
|
2964
|
+
fs.writeFileSync(outputPath, htmlReport);
|
|
2965
|
+
console.log(`\n✅ HTML report saved: ${outputPath}`);
|
|
2966
|
+
}
|
|
2967
|
+
else if (options.output && options.output.endsWith(".json")) {
|
|
2968
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2969
|
+
console.log(`\n✅ JSON report saved: ${options.output}`);
|
|
2970
|
+
}
|
|
2971
|
+
else {
|
|
2972
|
+
// Print text report
|
|
2973
|
+
const report = (0, browser_js_1.formatCoverageReport)(result);
|
|
2974
|
+
console.log(report);
|
|
2975
|
+
if (options.output) {
|
|
2976
|
+
fs.writeFileSync(options.output, report);
|
|
2977
|
+
console.log(`\n📄 Report saved: ${options.output}`);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
// Exit with error if coverage too low
|
|
2981
|
+
if (result.analysis.coveragePercent < (coverageOptions.minCoverage || 50)) {
|
|
2982
|
+
console.log(`\n⚠️ Coverage (${result.analysis.coveragePercent}%) is below threshold (${coverageOptions.minCoverage}%)`);
|
|
2983
|
+
process.exit(1);
|
|
2984
|
+
}
|
|
2985
|
+
break;
|
|
2986
|
+
}
|
|
2554
2987
|
default:
|
|
2555
2988
|
console.error(`Unknown command: ${command}`);
|
|
2556
2989
|
console.error("Run 'cbrowser help' for usage");
|