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/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.0.0 ║
17
- ║ AI-powered browser automation with multi-persona comparison
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");