@nhonh/qabot 1.2.0 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nhonh/qabot",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "AI-powered universal QA automation tool. Import any project, AI analyzes and runs tests across all layers.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -421,29 +421,27 @@ async function generateE2EReport(
421
421
  result,
422
422
  ) {
423
423
  try {
424
- const reporter = new ReportGenerator(config);
425
- const tests = result.tests || [];
426
- const results = {
424
+ const tests = await parsePlaywrightJsonReport(projectDir);
425
+
426
+ const passed = tests.filter((t) => t.status === "passed").length;
427
+ const failed = tests.filter((t) => t.status === "failed").length;
428
+ const skipped = tests.filter((t) => t.status === "skipped").length;
429
+ const total = tests.length;
430
+
431
+ const reportData = {
427
432
  summary: {
428
- totalTests: result.passed + result.failed + result.skipped,
429
- totalPassed: result.passed,
430
- totalFailed: result.failed,
431
- totalSkipped: result.skipped,
432
- overallPassRate:
433
- result.passed + result.failed + result.skipped > 0
434
- ? Math.round(
435
- (result.passed /
436
- (result.passed + result.failed + result.skipped)) *
437
- 100,
438
- )
439
- : 0,
433
+ totalTests: total || result.passed + result.failed + result.skipped,
434
+ totalPassed: total ? passed : result.passed,
435
+ totalFailed: total ? failed : result.failed,
436
+ totalSkipped: total ? skipped : result.skipped,
437
+ overallPassRate: total > 0 ? Math.round((passed / total) * 100) : 0,
440
438
  totalDuration: result.duration,
441
439
  byLayer: {
442
440
  e2e: {
443
- total: result.passed + result.failed + result.skipped,
444
- passed: result.passed,
445
- failed: result.failed,
446
- skipped: result.skipped,
441
+ total: total || result.passed + result.failed + result.skipped,
442
+ passed: total ? passed : result.passed,
443
+ failed: total ? failed : result.failed,
444
+ skipped: total ? skipped : result.skipped,
447
445
  },
448
446
  },
449
447
  },
@@ -453,17 +451,13 @@ async function generateE2EReport(
453
451
  layer: "e2e",
454
452
  feature,
455
453
  tests,
456
- summary: {
457
- total: result.passed + result.failed + result.skipped,
458
- passed: result.passed,
459
- failed: result.failed,
460
- skipped: result.skipped,
461
- },
454
+ summary: { total, passed, failed, skipped },
462
455
  },
463
456
  ],
464
457
  };
465
458
 
466
- const reportPaths = await reporter.generate(results, {
459
+ const reporter = new ReportGenerator(config);
460
+ const reportPaths = await reporter.generate(reportData, {
467
461
  feature,
468
462
  environment: env,
469
463
  projectName: config.project?.name || "unknown",
@@ -472,7 +466,81 @@ async function generateE2EReport(
472
466
  });
473
467
 
474
468
  logger.info(`Report: ${chalk.underline(reportPaths.htmlPath)}`);
469
+ } catch (err) {
470
+ logger.dim(`Report generation failed: ${err.message}`);
471
+ }
472
+ }
473
+
474
+ async function parsePlaywrightJsonReport(projectDir) {
475
+ const jsonPath = path.join(
476
+ projectDir,
477
+ "qabot-reports",
478
+ "playwright",
479
+ "results.json",
480
+ );
481
+ if (!(await fileExists(jsonPath))) return [];
482
+
483
+ try {
484
+ const raw = await readFile(jsonPath, "utf-8");
485
+ const data = JSON.parse(raw);
486
+ const tests = [];
487
+ flattenPwSuites(data.suites || [], tests);
488
+ return tests;
475
489
  } catch {
476
- /* best-effort */
490
+ return [];
491
+ }
492
+ }
493
+
494
+ function flattenPwSuites(suites, tests) {
495
+ for (const suite of suites) {
496
+ for (const spec of suite.specs || []) {
497
+ for (const t of spec.tests || []) {
498
+ const lastResult = t.results?.[t.results.length - 1];
499
+ const annotations = t.annotations || [];
500
+ const skipAnnotation = annotations.find(
501
+ (a) => a.type === "skip" || a.type === "fixme",
502
+ );
503
+
504
+ let status;
505
+ if (
506
+ t.expectedStatus === "skipped" ||
507
+ lastResult?.status === "skipped"
508
+ ) {
509
+ status = "skipped";
510
+ } else if (lastResult?.status === "passed") {
511
+ status = "passed";
512
+ } else {
513
+ status = "failed";
514
+ }
515
+
516
+ const screenshots = (lastResult?.attachments || [])
517
+ .filter((a) => a.contentType?.startsWith("image/"))
518
+ .map((a) => a.path);
519
+
520
+ tests.push({
521
+ name: spec.title,
522
+ suite: suite.title,
523
+ file: suite.file || spec.file || "",
524
+ status,
525
+ duration: lastResult?.duration || 0,
526
+ error: lastResult?.error
527
+ ? {
528
+ message: lastResult.error.message || "",
529
+ stack: lastResult.error.stack || "",
530
+ expected: lastResult.error.expected,
531
+ actual: lastResult.error.actual,
532
+ }
533
+ : null,
534
+ skipReason:
535
+ skipAnnotation?.description ||
536
+ (status === "skipped"
537
+ ? "Test was skipped (conditional skip or missing prerequisite)"
538
+ : null),
539
+ screenshots,
540
+ retries: (t.results?.length || 1) - 1,
541
+ });
542
+ }
543
+ }
544
+ if (suite.suites) flattenPwSuites(suite.suites, tests);
477
545
  }
478
546
  }
@@ -1,4 +1,4 @@
1
- export const VERSION = "1.2.0";
1
+ export const VERSION = "1.2.1";
2
2
  export const TOOL_NAME = "qabot";
3
3
 
4
4
  export const PROJECT_TYPES = [