supercov 4.12.0 → 4.12.2

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/docs/cli.md CHANGED
@@ -699,7 +699,7 @@ rest of the run and leaves their old records out:
699
699
 
700
700
  ```sh supercov-example
701
701
  npx supercov runs <suite-run> tests affected --names # after changing a mock
702
- npx supercov -- npx playwright test <those tests>
702
+ npx supercov -- <those tests alone>
703
703
  npx supercov merge
704
704
  ```
705
705
 
@@ -723,7 +723,7 @@ own:
723
723
 
724
724
  ```yaml
725
725
  # Each of two jobs:
726
- - run: npx supercov -- npx playwright test --shard=${{ matrix.shard }}/2
726
+ - run: npx supercov -- <test command for shard ${{ matrix.shard }}>
727
727
  - uses: actions/upload-artifact@v4
728
728
  with: { name: "supercov-${{ matrix.shard }}", path: .supercov/runs, include-hidden-files: true }
729
729
  # Then one job:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "4.12.0",
3
+ "version": "4.12.2",
4
4
  "description": "Coverage, security and code quality for coding agents",
5
5
  "keywords": [
6
6
  "coverage",
@@ -83,7 +83,7 @@
83
83
  "eval:skill": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag behavior trigger --allow-tools Bash Edit Write --max-cost-usd 15",
84
84
  "eval:skill-triggers": "claude plugin eval plugins/supercov --scaffold --trust-plugin --no-publish --model claude-sonnet-5 --judge-model claude-haiku-4-5 --threshold 0.66 --tag trigger --ablation none --max-cost-usd 5",
85
85
  "test:engine": "cargo fmt --all -- --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace && npm run test:runtime && npm run test:rust-assets && npm run test:engine-integrations",
86
- "test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/side-by-side.mjs node && node scripts/merge-across.mjs && node scripts/mock-reach.mjs && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/cross-origin-requests.mjs && node scripts/leaving-page.mjs && node scripts/background-by-time.mjs && node scripts/python-server-playwright.mjs && node scripts/ruby-server-playwright.mjs && node scripts/go-server-playwright.mjs && node scripts/jvm-server-playwright.mjs && node scripts/docker-server-playwright.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs",
86
+ "test:engine-integrations": "cargo build -p supercov && node scripts/html-report-integration.mjs && node scripts/rust-process-supervision.mjs && node scripts/rust-direct-node-integration.mjs && node scripts/rust-public-run-integration.mjs && node scripts/rust-embedded-runtime-integration.mjs && node scripts/rust-direct-vitest-integration.mjs && node scripts/rust-direct-jest-integration.mjs && node scripts/mocha-failure-output.mjs && node scripts/failure-output.mjs node ava tap && node scripts/side-by-side.mjs node && node scripts/merge-across.mjs && node scripts/mock-reach.mjs && node scripts/rust-direct-playwright-integration.mjs && node scripts/rust-custom-browser-playwright-integration.mjs && node scripts/cross-origin-requests.mjs && node scripts/leaving-page.mjs && node scripts/shared-page.mjs && node scripts/background-by-time.mjs && node scripts/python-server-playwright.mjs && node scripts/ruby-server-playwright.mjs && node scripts/go-server-playwright.mjs && node scripts/jvm-server-playwright.mjs && node scripts/docker-server-playwright.mjs && node scripts/rust-classic-browser-integration.mjs && node scripts/rust-top-packages-integration.mjs && node scripts/rust-generic-esbuild-integration.mjs && node scripts/rust-generic-tsc-integration.mjs && node scripts/rust-generic-build-matrix.mjs && node scripts/rust-vite-playwright-integration.mjs",
87
87
  "test:platform": "cargo test --workspace && cargo build -p supercov && node scripts/rust-process-supervision.mjs && node scripts/workspace-crash-integration.mjs",
88
88
  "test:native-package": "cargo build --release -p supercov && node scripts/native-package-integration.mjs && node scripts/native-release-set-integration.mjs",
89
89
  "test:pypi-wheel": "node scripts/pypi-wheel-integration.mjs",
@@ -120,14 +120,14 @@
120
120
  "test:windows-tls": "node scripts/windows-tls-test.mjs"
121
121
  },
122
122
  "optionalDependencies": {
123
- "@supercov/cli-darwin-arm64": "4.12.0",
124
- "@supercov/cli-darwin-x64": "4.12.0",
125
- "@supercov/cli-linux-arm64-gnu": "4.12.0",
126
- "@supercov/cli-linux-arm64-musl": "4.12.0",
127
- "@supercov/cli-linux-x64-gnu": "4.12.0",
128
- "@supercov/cli-linux-x64-musl": "4.12.0",
129
- "@supercov/cli-win32-arm64": "4.12.0",
130
- "@supercov/cli-win32-x64": "4.12.0"
123
+ "@supercov/cli-darwin-arm64": "4.12.2",
124
+ "@supercov/cli-darwin-x64": "4.12.2",
125
+ "@supercov/cli-linux-arm64-gnu": "4.12.2",
126
+ "@supercov/cli-linux-arm64-musl": "4.12.2",
127
+ "@supercov/cli-linux-x64-gnu": "4.12.2",
128
+ "@supercov/cli-linux-x64-musl": "4.12.2",
129
+ "@supercov/cli-win32-arm64": "4.12.2",
130
+ "@supercov/cli-win32-x64": "4.12.2"
131
131
  },
132
132
  "peerDependencies": {
133
133
  "@playwright/test": ">=1.55.0",
@@ -391,6 +391,42 @@ class CoveragePhaseController {
391
391
  }
392
392
  }
393
393
  }
394
+ /**
395
+ * Start the pages an earlier test left open (a page a serial describe
396
+ * opens in `beforeAll`, a worker-scoped one) with no evidence, and return
397
+ * what they held. Their documents kept every hit since they loaded, and
398
+ * each later test was credited with what the tests before it ran. What
399
+ * they held is the earlier tests' (already recorded) and work between
400
+ * tests, such as a `beforeAll` signing in: no test's. A document that
401
+ * already has one of this test's phases is left alone: a reset that
402
+ * arrives late must not drop what this test ran.
403
+ */
404
+ async takeCarriedEvidence() {
405
+ await Promise.all([...this.pendingRegistrations]);
406
+ const prefix = `${this.scope.attemptId}:`;
407
+ const take = (target) => bounded(target
408
+ .evaluate((prefix) => {
409
+ const coverageGlobal = globalThis;
410
+ if (String(coverageGlobal.__SUPERCOV_PHASE_ID__ ?? "").startsWith(prefix))
411
+ return undefined;
412
+ const getSnapshot = coverageGlobal.__SUPERCOV_COVERAGE_SNAPSHOT__;
413
+ const reset = coverageGlobal.__SUPERCOV_RESET__;
414
+ if (!getSnapshot || !reset)
415
+ return undefined;
416
+ const snapshot = JSON.stringify(getSnapshot());
417
+ reset();
418
+ return snapshot;
419
+ }, prefix)
420
+ .then((text) => (typeof text === "string" ? JSON.parse(text) : undefined))
421
+ .catch(() => undefined), WORKER_ACTIVATION_WAIT_MS);
422
+ const taken = await Promise.all(this.allPages().flatMap((page) => [
423
+ ...page.frames().map(take),
424
+ ...page.workers().map(take),
425
+ ]));
426
+ const snapshots = taken.filter((snapshot) => snapshot && snapshot !== WORKER_TIMED_OUT &&
427
+ (snapshot.hits?.length || snapshot.events?.length || snapshot.decisions?.length));
428
+ return snapshots.length > 0 ? mergeSnapshots(snapshots) : undefined;
429
+ }
394
430
  /**
395
431
  * A page is registered once, and whoever asks while that is under way
396
432
  * waits for it. Playwright announces a page to the context's listener
@@ -1458,6 +1494,25 @@ function mergeCollectedPhases(controllerPhases, fallbackPhases) {
1458
1494
  });
1459
1495
  return [...controllerPhases, ...remaining];
1460
1496
  }
1497
+ /**
1498
+ * Pool runners may cycle-restore a VM immediately after Playwright exits,
1499
+ * which can discard or overwrite the normal artifact copy. Each record is
1500
+ * written to one uniquely named, one-shot evidence file in the runner's shared
1501
+ * directory as well. No test streams into this path.
1502
+ */
1503
+ function appendEvidenceLine(serialized) {
1504
+ const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
1505
+ (GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
1506
+ ? undefined
1507
+ : GENERATED_EVIDENCE_DIRECTORY);
1508
+ if (!evidenceDirectory)
1509
+ return;
1510
+ const resolvedDirectory = resolve(process.cwd(), evidenceDirectory);
1511
+ const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
1512
+ ? appendJsonLineDurableSync
1513
+ : appendJsonLineSync;
1514
+ append(resolve(resolvedDirectory, `playwright-worker-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), serialized);
1515
+ }
1461
1516
  const instrumentedFixtures = {
1462
1517
  page: async ({ page }, use, testInfo) => {
1463
1518
  const scope = executionScope(testInfo);
@@ -1507,6 +1562,17 @@ const instrumentedFixtures = {
1507
1562
  activeController = controller;
1508
1563
  directRuntime()?.activateCoverageScope(scope);
1509
1564
  await controller.adoptTrackedContexts().catch(() => undefined);
1565
+ const carried = await controller.takeCarriedEvidence().catch(() => undefined);
1566
+ if (carried) {
1567
+ appendEvidenceLine(`${JSON.stringify({
1568
+ testId: `__supercov_background__:playwright:${scope.attemptId}`,
1569
+ test: "Playwright work between tests",
1570
+ status: "unknown",
1571
+ provenance: { runner: "background", kind: "background", source: "explicit" },
1572
+ role: "background",
1573
+ browser: [carried],
1574
+ })}\n`);
1575
+ }
1510
1576
  const serverOutput = serverEvidencePath(scope);
1511
1577
  mkdirSync(serverEvidenceDirectory(scope), { recursive: true });
1512
1578
  rmSync(serverOutput, { force: true });
@@ -1543,22 +1609,7 @@ const instrumentedFixtures = {
1543
1609
  browser,
1544
1610
  server,
1545
1611
  };
1546
- const serialized = `${JSON.stringify(payload)}\n`;
1547
- // Pool runners may cycle-restore a VM immediately after Playwright
1548
- // exits, which can discard or overwrite the normal artifact copy.
1549
- // Write one uniquely named, one-shot evidence file to the runner's
1550
- // shared directory as well. No test streams into this path.
1551
- const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
1552
- (GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
1553
- ? undefined
1554
- : GENERATED_EVIDENCE_DIRECTORY);
1555
- if (evidenceDirectory) {
1556
- const resolvedDirectory = resolve(process.cwd(), evidenceDirectory);
1557
- const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
1558
- ? appendJsonLineDurableSync
1559
- : appendJsonLineSync;
1560
- append(resolve(resolvedDirectory, `playwright-worker-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), serialized);
1561
- }
1612
+ appendEvidenceLine(`${JSON.stringify(payload)}\n`);
1562
1613
  }
1563
1614
  finally {
1564
1615
  await timed("controller.dispose", () => controller.dispose());
@@ -451,6 +451,12 @@ function persistBrowser() {
451
451
  persistBrowserListenersInstalled = true;
452
452
  try {
453
453
  addEventListener("beforeunload", handOverBrowser);
454
+ // An iframe the page removes (a client-side navigation away from the
455
+ // page that held it, an embed closed) gets no `beforeunload`: what it ran
456
+ // went with it, though the test clicked in it. Its `pagehide` reaches the
457
+ // collector, unlike a top-level document's.
458
+ if (typeof window !== "undefined" && window !== window.top)
459
+ addEventListener("pagehide", handOverBrowser);
454
460
  addEventListener("pagehide", persistBrowserNow);
455
461
  addEventListener("visibilitychange", () => {
456
462
  if (typeof document !== "undefined" && document.visibilityState === "hidden")