cbrowser 6.0.0 → 6.2.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 +150 -0
- package/dist/browser.d.ts +93 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +941 -0
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +337 -2
- package/dist/cli.js.map +1 -1
- package/dist/types.d.ts +127 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13,8 +13,8 @@ const mcp_server_js_1 = require("./mcp-server.js");
|
|
|
13
13
|
function showHelp() {
|
|
14
14
|
console.log(`
|
|
15
15
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
16
|
-
║ CBrowser CLI v6.
|
|
17
|
-
║ AI-powered browser automation with
|
|
16
|
+
║ CBrowser CLI v6.2.0 ║
|
|
17
|
+
║ AI-powered browser automation with automatic test repair ║
|
|
18
18
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
19
19
|
|
|
20
20
|
NAVIGATION
|
|
@@ -49,6 +49,44 @@ MULTI-PERSONA COMPARISON (v6.0.0)
|
|
|
49
49
|
--goal "Complete checkout" \\
|
|
50
50
|
--personas power-user,first-timer,elderly-user,mobile-user
|
|
51
51
|
|
|
52
|
+
NATURAL LANGUAGE TEST SUITES (v6.1.0)
|
|
53
|
+
test-suite <file.txt> Run tests written in plain English
|
|
54
|
+
--continue-on-failure Keep running after a test fails
|
|
55
|
+
--screenshot-on-failure Take screenshots on failure (default: true)
|
|
56
|
+
--output <file> Save JSON report to file
|
|
57
|
+
--html Generate HTML report
|
|
58
|
+
--timeout <ms> Timeout per step (default: 30000)
|
|
59
|
+
test-suite --inline "..." Run inline test (semicolon-separated steps)
|
|
60
|
+
Examples:
|
|
61
|
+
cbrowser test-suite login-flow.txt --html
|
|
62
|
+
cbrowser test-suite --inline "go to https://example.com ; click login ; verify url contains /dashboard"
|
|
63
|
+
|
|
64
|
+
Test File Format:
|
|
65
|
+
# Test: Login Flow
|
|
66
|
+
go to https://example.com
|
|
67
|
+
click the login button
|
|
68
|
+
type "user@example.com" in email field
|
|
69
|
+
type "password123" in password field
|
|
70
|
+
click submit
|
|
71
|
+
verify url contains "/dashboard"
|
|
72
|
+
|
|
73
|
+
# Test: Search
|
|
74
|
+
go to https://example.com
|
|
75
|
+
type "test query" in search box
|
|
76
|
+
click search button
|
|
77
|
+
verify page contains "results"
|
|
78
|
+
|
|
79
|
+
AI TEST REPAIR (v6.2.0)
|
|
80
|
+
repair-tests <file.txt> Analyze failing tests and suggest/apply repairs
|
|
81
|
+
--auto-apply Automatically apply the best repair
|
|
82
|
+
--verify Re-run tests after repair to verify they pass
|
|
83
|
+
--output <file> Save repaired tests to new file
|
|
84
|
+
--json <file> Save repair report as JSON
|
|
85
|
+
Examples:
|
|
86
|
+
cbrowser repair-tests broken-test.txt
|
|
87
|
+
cbrowser repair-tests tests.txt --auto-apply --verify
|
|
88
|
+
cbrowser repair-tests tests.txt --auto-apply --output fixed-tests.txt
|
|
89
|
+
|
|
52
90
|
PERSONAS
|
|
53
91
|
persona list List all personas (built-in + custom)
|
|
54
92
|
persona create "<desc>" Create persona from natural language description
|
|
@@ -431,6 +469,159 @@ function generateHtmlReport(comparison) {
|
|
|
431
469
|
</body>
|
|
432
470
|
</html>`;
|
|
433
471
|
}
|
|
472
|
+
function generateTestSuiteHtmlReport(result) {
|
|
473
|
+
const testRows = result.testResults.map((t) => {
|
|
474
|
+
const stepDetails = t.stepResults.map((s) => `
|
|
475
|
+
<tr class="${s.passed ? 'step-pass' : 'step-fail'}">
|
|
476
|
+
<td class="step-indent">${s.instruction}</td>
|
|
477
|
+
<td>${s.passed ? '✓' : '✗'}</td>
|
|
478
|
+
<td>${s.duration}ms</td>
|
|
479
|
+
<td>${s.error || '-'}</td>
|
|
480
|
+
</tr>
|
|
481
|
+
`).join('');
|
|
482
|
+
return `
|
|
483
|
+
<tr class="${t.passed ? 'success' : 'failure'}">
|
|
484
|
+
<td><strong>${t.name}</strong></td>
|
|
485
|
+
<td>${t.passed ? '✓ PASS' : '✗ FAIL'}</td>
|
|
486
|
+
<td>${(t.duration / 1000).toFixed(1)}s</td>
|
|
487
|
+
<td>${t.stepResults.length} steps</td>
|
|
488
|
+
<td>${t.error || '-'}</td>
|
|
489
|
+
</tr>
|
|
490
|
+
${stepDetails}
|
|
491
|
+
`;
|
|
492
|
+
}).join('');
|
|
493
|
+
const passRate = result.summary.passRate.toFixed(0);
|
|
494
|
+
const passColor = result.summary.passRate === 100 ? '#10b981' : result.summary.passRate >= 80 ? '#f59e0b' : '#ef4444';
|
|
495
|
+
return `<!DOCTYPE html>
|
|
496
|
+
<html lang="en">
|
|
497
|
+
<head>
|
|
498
|
+
<meta charset="UTF-8">
|
|
499
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
500
|
+
<title>Test Suite Report - ${result.name}</title>
|
|
501
|
+
<style>
|
|
502
|
+
* { box-sizing: border-box; }
|
|
503
|
+
body {
|
|
504
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
505
|
+
max-width: 1200px;
|
|
506
|
+
margin: 0 auto;
|
|
507
|
+
padding: 2rem;
|
|
508
|
+
background: #f5f5f5;
|
|
509
|
+
}
|
|
510
|
+
h1 { color: #1a1a1a; border-bottom: 3px solid #3b82f6; padding-bottom: 0.5rem; }
|
|
511
|
+
.meta { background: white; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; }
|
|
512
|
+
.meta p { margin: 0.25rem 0; }
|
|
513
|
+
table {
|
|
514
|
+
width: 100%;
|
|
515
|
+
border-collapse: collapse;
|
|
516
|
+
background: white;
|
|
517
|
+
border-radius: 8px;
|
|
518
|
+
overflow: hidden;
|
|
519
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
520
|
+
margin-bottom: 1rem;
|
|
521
|
+
}
|
|
522
|
+
th, td {
|
|
523
|
+
padding: 0.75rem 1rem;
|
|
524
|
+
text-align: left;
|
|
525
|
+
border-bottom: 1px solid #eee;
|
|
526
|
+
}
|
|
527
|
+
th { background: #1a1a1a; color: white; }
|
|
528
|
+
tr.success { background: #ecfdf5; }
|
|
529
|
+
tr.failure { background: #fef2f2; }
|
|
530
|
+
tr.step-pass { background: #f8fafc; }
|
|
531
|
+
tr.step-fail { background: #fff5f5; }
|
|
532
|
+
.step-indent { padding-left: 2rem; font-size: 0.875rem; color: #666; }
|
|
533
|
+
.summary {
|
|
534
|
+
display: grid;
|
|
535
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
536
|
+
gap: 1rem;
|
|
537
|
+
margin: 1rem 0;
|
|
538
|
+
}
|
|
539
|
+
.stat {
|
|
540
|
+
background: white;
|
|
541
|
+
padding: 1rem;
|
|
542
|
+
border-radius: 8px;
|
|
543
|
+
text-align: center;
|
|
544
|
+
}
|
|
545
|
+
.stat .value { font-size: 1.75rem; font-weight: bold; }
|
|
546
|
+
.stat .label { color: #666; font-size: 0.875rem; }
|
|
547
|
+
.pass-rate { color: ${passColor}; }
|
|
548
|
+
.failures {
|
|
549
|
+
background: #fef2f2;
|
|
550
|
+
border: 1px solid #fecaca;
|
|
551
|
+
border-radius: 8px;
|
|
552
|
+
padding: 1rem;
|
|
553
|
+
margin-top: 1rem;
|
|
554
|
+
}
|
|
555
|
+
.failures h3 { margin-top: 0; color: #dc2626; }
|
|
556
|
+
.failures ul { margin: 0; padding-left: 1.5rem; }
|
|
557
|
+
code { background: #f1f5f9; padding: 0.125rem 0.25rem; border-radius: 4px; font-size: 0.875rem; }
|
|
558
|
+
</style>
|
|
559
|
+
</head>
|
|
560
|
+
<body>
|
|
561
|
+
<h1>🧪 Natural Language Test Report</h1>
|
|
562
|
+
|
|
563
|
+
<div class="meta">
|
|
564
|
+
<p><strong>Suite:</strong> ${result.name}</p>
|
|
565
|
+
<p><strong>Timestamp:</strong> ${result.timestamp}</p>
|
|
566
|
+
<p><strong>Duration:</strong> ${(result.duration / 1000).toFixed(1)}s</p>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<div class="summary">
|
|
570
|
+
<div class="stat">
|
|
571
|
+
<div class="value pass-rate">${passRate}%</div>
|
|
572
|
+
<div class="label">Pass Rate</div>
|
|
573
|
+
</div>
|
|
574
|
+
<div class="stat">
|
|
575
|
+
<div class="value">${result.summary.passed}</div>
|
|
576
|
+
<div class="label">Passed</div>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="stat">
|
|
579
|
+
<div class="value" style="color: ${result.summary.failed > 0 ? '#ef4444' : '#10b981'};">${result.summary.failed}</div>
|
|
580
|
+
<div class="label">Failed</div>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="stat">
|
|
583
|
+
<div class="value">${result.summary.total}</div>
|
|
584
|
+
<div class="label">Total Tests</div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<table>
|
|
589
|
+
<thead>
|
|
590
|
+
<tr>
|
|
591
|
+
<th>Test / Step</th>
|
|
592
|
+
<th>Status</th>
|
|
593
|
+
<th>Duration</th>
|
|
594
|
+
<th>Steps</th>
|
|
595
|
+
<th>Error</th>
|
|
596
|
+
</tr>
|
|
597
|
+
</thead>
|
|
598
|
+
<tbody>
|
|
599
|
+
${testRows}
|
|
600
|
+
</tbody>
|
|
601
|
+
</table>
|
|
602
|
+
|
|
603
|
+
${result.summary.failed > 0 ? `
|
|
604
|
+
<div class="failures">
|
|
605
|
+
<h3>❌ Failed Tests</h3>
|
|
606
|
+
<ul>
|
|
607
|
+
${result.testResults.filter(t => !t.passed).map(t => `
|
|
608
|
+
<li>
|
|
609
|
+
<strong>${t.name}</strong>: ${t.error}
|
|
610
|
+
<ul>
|
|
611
|
+
${t.stepResults.filter(s => !s.passed).map(s => `<li><code>${s.instruction}</code> - ${s.error}</li>`).join('')}
|
|
612
|
+
</ul>
|
|
613
|
+
</li>
|
|
614
|
+
`).join('')}
|
|
615
|
+
</ul>
|
|
616
|
+
</div>
|
|
617
|
+
` : ''}
|
|
618
|
+
|
|
619
|
+
<p style="color: #999; text-align: center; margin-top: 2rem;">
|
|
620
|
+
Generated by CBrowser v6.1.0 - Natural Language Test Suites
|
|
621
|
+
</p>
|
|
622
|
+
</body>
|
|
623
|
+
</html>`;
|
|
624
|
+
}
|
|
434
625
|
function parseGeoLocation(location) {
|
|
435
626
|
// Check if it's a preset
|
|
436
627
|
if (types_js_1.LOCATION_PRESETS[location]) {
|
|
@@ -2152,6 +2343,150 @@ async function main() {
|
|
|
2152
2343
|
console.log("✓ Browser state reset (cookies, localStorage cleared)");
|
|
2153
2344
|
break;
|
|
2154
2345
|
}
|
|
2346
|
+
// =========================================================================
|
|
2347
|
+
// Natural Language Test Suites (Tier 6)
|
|
2348
|
+
// =========================================================================
|
|
2349
|
+
case "test-suite": {
|
|
2350
|
+
const filepath = args[0];
|
|
2351
|
+
const inlineTest = options.inline;
|
|
2352
|
+
if (!filepath && !inlineTest) {
|
|
2353
|
+
console.error("Usage: cbrowser test-suite <file.txt> [--continue-on-failure] [--output <report.json>]");
|
|
2354
|
+
console.error(" cbrowser test-suite --inline \"go to https://... ; click login ; verify ...\"");
|
|
2355
|
+
console.error("");
|
|
2356
|
+
console.error("Options:");
|
|
2357
|
+
console.error(" --continue-on-failure Continue running after a test fails");
|
|
2358
|
+
console.error(" --screenshot-on-failure Take screenshots on failure (default: true)");
|
|
2359
|
+
console.error(" --output <file> Save JSON report to file");
|
|
2360
|
+
console.error(" --html Generate HTML report");
|
|
2361
|
+
console.error(" --timeout <ms> Timeout per step (default: 30000)");
|
|
2362
|
+
console.error("");
|
|
2363
|
+
console.error("Test File Format:");
|
|
2364
|
+
console.error(" # Test: Login Flow");
|
|
2365
|
+
console.error(" go to https://example.com");
|
|
2366
|
+
console.error(" click the login button");
|
|
2367
|
+
console.error(" type \"user@example.com\" in email field");
|
|
2368
|
+
console.error(" verify url contains \"/dashboard\"");
|
|
2369
|
+
process.exit(1);
|
|
2370
|
+
}
|
|
2371
|
+
let suite;
|
|
2372
|
+
if (inlineTest) {
|
|
2373
|
+
// Parse inline test - semicolons separate steps
|
|
2374
|
+
const steps = inlineTest.split(";").map(s => s.trim()).filter(s => s);
|
|
2375
|
+
const testCase = {
|
|
2376
|
+
name: "Inline Test",
|
|
2377
|
+
steps: steps.map(s => (0, browser_js_1.parseNLInstruction)(s)),
|
|
2378
|
+
};
|
|
2379
|
+
suite = { name: "Inline Suite", tests: [testCase] };
|
|
2380
|
+
}
|
|
2381
|
+
else {
|
|
2382
|
+
// Load from file
|
|
2383
|
+
const fs = await import("fs");
|
|
2384
|
+
if (!fs.existsSync(filepath)) {
|
|
2385
|
+
console.error(`Test file not found: ${filepath}`);
|
|
2386
|
+
process.exit(1);
|
|
2387
|
+
}
|
|
2388
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
2389
|
+
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
2390
|
+
suite = (0, browser_js_1.parseNLTestSuite)(content, suiteName);
|
|
2391
|
+
}
|
|
2392
|
+
console.log(`\n📝 Parsed ${suite.tests.length} test(s) from ${inlineTest ? "inline" : filepath}`);
|
|
2393
|
+
for (const test of suite.tests) {
|
|
2394
|
+
console.log(` - ${test.name}: ${test.steps.length} steps`);
|
|
2395
|
+
}
|
|
2396
|
+
const suiteOptions = {
|
|
2397
|
+
stepTimeout: options.timeout ? parseInt(options.timeout) : 30000,
|
|
2398
|
+
continueOnFailure: options["continue-on-failure"] === true,
|
|
2399
|
+
screenshotOnFailure: options["screenshot-on-failure"] !== false,
|
|
2400
|
+
headless,
|
|
2401
|
+
};
|
|
2402
|
+
const result = await (0, browser_js_1.runNLTestSuite)(suite, suiteOptions);
|
|
2403
|
+
// Print formatted report
|
|
2404
|
+
const report = (0, browser_js_1.formatNLTestReport)(result);
|
|
2405
|
+
console.log(report);
|
|
2406
|
+
// Save JSON output if requested
|
|
2407
|
+
if (options.output) {
|
|
2408
|
+
const fs = await import("fs");
|
|
2409
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2410
|
+
console.log(`\n📄 JSON report saved: ${options.output}`);
|
|
2411
|
+
}
|
|
2412
|
+
// Generate HTML report if requested
|
|
2413
|
+
if (options.html) {
|
|
2414
|
+
const fs = await import("fs");
|
|
2415
|
+
const htmlReport = generateTestSuiteHtmlReport(result);
|
|
2416
|
+
const htmlPath = options.output?.replace(".json", ".html") || "test-report.html";
|
|
2417
|
+
fs.writeFileSync(htmlPath, htmlReport);
|
|
2418
|
+
console.log(`\n🌐 HTML report saved: ${htmlPath}`);
|
|
2419
|
+
}
|
|
2420
|
+
// Exit with error code if any tests failed
|
|
2421
|
+
if (result.summary.failed > 0) {
|
|
2422
|
+
process.exit(1);
|
|
2423
|
+
}
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
// =========================================================================
|
|
2427
|
+
// AI Test Repair (Tier 6 - v6.2.0)
|
|
2428
|
+
// =========================================================================
|
|
2429
|
+
case "repair-tests": {
|
|
2430
|
+
const filepath = args[0];
|
|
2431
|
+
if (!filepath) {
|
|
2432
|
+
console.error("Usage: cbrowser repair-tests <test-file.txt> [--auto-apply] [--verify] [--output <file>]");
|
|
2433
|
+
console.error("");
|
|
2434
|
+
console.error("Options:");
|
|
2435
|
+
console.error(" --auto-apply Automatically apply the best repair suggestion");
|
|
2436
|
+
console.error(" --verify Re-run repaired tests to verify they pass");
|
|
2437
|
+
console.error(" --output <file> Save repaired tests to a new file");
|
|
2438
|
+
console.error(" --json <file> Save repair report as JSON");
|
|
2439
|
+
console.error("");
|
|
2440
|
+
console.error("Examples:");
|
|
2441
|
+
console.error(" cbrowser repair-tests broken-test.txt");
|
|
2442
|
+
console.error(" cbrowser repair-tests tests.txt --auto-apply --verify");
|
|
2443
|
+
console.error(" cbrowser repair-tests tests.txt --auto-apply --output fixed-tests.txt");
|
|
2444
|
+
process.exit(1);
|
|
2445
|
+
}
|
|
2446
|
+
const fs = await import("fs");
|
|
2447
|
+
if (!fs.existsSync(filepath)) {
|
|
2448
|
+
console.error(`Test file not found: ${filepath}`);
|
|
2449
|
+
process.exit(1);
|
|
2450
|
+
}
|
|
2451
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
2452
|
+
const suiteName = filepath.split("/").pop()?.replace(/\.[^.]+$/, "") || "Test Suite";
|
|
2453
|
+
const suite = (0, browser_js_1.parseNLTestSuite)(content, suiteName);
|
|
2454
|
+
console.log(`\n📝 Parsed ${suite.tests.length} test(s) from ${filepath}`);
|
|
2455
|
+
for (const test of suite.tests) {
|
|
2456
|
+
console.log(` - ${test.name}: ${test.steps.length} steps`);
|
|
2457
|
+
}
|
|
2458
|
+
const repairOptions = {
|
|
2459
|
+
headless,
|
|
2460
|
+
autoApply: options["auto-apply"] === true,
|
|
2461
|
+
verifyRepairs: options.verify === true,
|
|
2462
|
+
maxRetries: options.retries ? parseInt(options.retries) : 3,
|
|
2463
|
+
};
|
|
2464
|
+
const result = await (0, browser_js_1.repairTestSuite)(suite, repairOptions);
|
|
2465
|
+
// Print formatted report
|
|
2466
|
+
const report = (0, browser_js_1.formatRepairReport)(result);
|
|
2467
|
+
console.log(report);
|
|
2468
|
+
// Save JSON report if requested
|
|
2469
|
+
if (options.json) {
|
|
2470
|
+
fs.writeFileSync(options.json, JSON.stringify(result, null, 2));
|
|
2471
|
+
console.log(`\n📄 JSON report saved: ${options.json}`);
|
|
2472
|
+
}
|
|
2473
|
+
// Save repaired tests if requested
|
|
2474
|
+
if (options.output && repairOptions.autoApply) {
|
|
2475
|
+
const repairedContent = [];
|
|
2476
|
+
for (const testResult of result.testResults) {
|
|
2477
|
+
repairedContent.push((0, browser_js_1.exportRepairedTest)(testResult));
|
|
2478
|
+
repairedContent.push("");
|
|
2479
|
+
}
|
|
2480
|
+
fs.writeFileSync(options.output, repairedContent.join("\n"));
|
|
2481
|
+
console.log(`\n📝 Repaired tests saved: ${options.output}`);
|
|
2482
|
+
}
|
|
2483
|
+
// Exit with status based on whether repairs were needed
|
|
2484
|
+
if (result.summary.testsWithFailures > 0 && !repairOptions.autoApply) {
|
|
2485
|
+
console.log("\n💡 Run with --auto-apply to automatically fix issues");
|
|
2486
|
+
process.exit(1);
|
|
2487
|
+
}
|
|
2488
|
+
break;
|
|
2489
|
+
}
|
|
2155
2490
|
default:
|
|
2156
2491
|
console.error(`Unknown command: ${command}`);
|
|
2157
2492
|
console.error("Run 'cbrowser help' for usage");
|