Skip to content

Commit 4434981

Browse files
feat: add auto-fixture for Istanbul E2E coverage collection (#95)
* feat: add auto-fixture for Istanbul E2E coverage collection - Adds _coverageCollector automatic fixture to test object - Collects window.__coverage__ from browser after each test - Writes per-test JSON files to <outputDir>/coverage/ - Enabled via E2E_COLLECT_COVERAGE=1 - Zero overhead when disabled (no page.evaluate or fs operations) - Designed for use with instrumented dynamic plugin builds This enables automatic E2E coverage collection for all workspaces in rhdh-plugin-export-overlays without modifying any test files. * feat: auto-swap to __coverage images for frontend plugins in PR mode Implements automatic image tag swap from 'pr_XXX__version' to 'pr_XXX__version__coverage' when E2E_COLLECT_COVERAGE=1 is set and the plugin is a frontend-plugin. Changes: 1. Add 'role' field to PluginMetadata and PackageCRD types 2. Parse spec.backstage.role in parseMetadataFile() 3. Swap to __coverage tag in resolvePluginPackages() when: - GIT_PR_NUMBER is set (PR mode) - E2E_COLLECT_COVERAGE=1 - Plugin role is 'frontend-plugin' The regex preserves OCI alias: plugin:tag!alias → plugin:tag__coverage!alias Only affects PR checks. Nightly mode, {{inherit}}, and local dev paths are unchanged. Backend plugins skip the swap (no browser coverage). Implements: #95 (comment) * fix: use "true" instead of "1" for E2E_COLLECT_COVERAGE env var For consistency with other boolean env vars in the codebase (like E2E_NIGHTLY_MODE, USE_NEW_FRONTEND_SYSTEM), change from checking "1" to checking "true". Updated in: - src/playwright/fixtures/test.ts (_coverageCollector fixture) - src/utils/plugin-metadata.ts (coverage image swap) - docs/changelog.md (documentation) Addresses: #95 (comment) * chore: bump version to 1.1.46 Main branch was updated to 1.1.45, so this PR needs to bump to 1.1.46. * Apply suggestions from code review Co-authored-by: Subhash Khileri <subhashkhileri2@gmail.com> --------- Co-authored-by: Subhash Khileri <subhashkhileri2@gmail.com>
1 parent 93d7ff2 commit 4434981

4 files changed

Lines changed: 51 additions & 4 deletions

File tree

docs/changelog.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [1.1.45] - Current
5+
## [2.1.0] - Current
6+
7+
### Added
8+
9+
- **E2E coverage collection auto-fixture**: New `_coverageCollector` automatic fixture collects Istanbul coverage (`window.__coverage__`) from the browser after each test and writes per-test JSON files to `<outputDir>/coverage/`. Enabled via `E2E_COLLECT_COVERAGE=true`. Zero overhead when disabled — no `page.evaluate` call or fs operations. Designed for use with instrumented dynamic plugin builds (nyc instrument).
610

711
### Fixed
812

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@red-hat-developer-hub/e2e-test-utils",
3-
"version": "1.1.45",
3+
"version": "2.1.0",
44
"description": "Test utilities for RHDH E2E tests",
55
"license": "Apache-2.0",
66
"repository": {

src/playwright/fixtures/test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import { LoginHelper, UIhelper } from "../helpers/index.js";
44
import { runOnce } from "../run-once.js";
55
import { $ } from "../../utils/bash.js";
66
import { WorkspacePaths } from "../../utils/workspace-paths.js";
7+
import fs from "node:fs";
78
import path from "path";
89

910
type RHDHDeploymentTestFixtures = {
1011
rhdh: RHDHDeployment;
1112
uiHelper: UIhelper;
1213
loginHelper: LoginHelper;
1314
autoAnnotations: void;
15+
// eslint-disable-next-line @typescript-eslint/naming-convention
16+
_coverageCollector: void;
1417
};
1518

1619
type RHDHDeploymentWorkerFixtures = {
@@ -77,6 +80,34 @@ const baseTest = base.extend<
7780
},
7881
{ auto: true, scope: "test" },
7982
],
83+
// eslint-disable-next-line @typescript-eslint/naming-convention
84+
_coverageCollector: [
85+
async ({ page }, use, testInfo) => {
86+
await use();
87+
if (process.env.E2E_COLLECT_COVERAGE !== "true") return;
88+
try {
89+
const coverage = await page.evaluate(
90+
() =>
91+
(
92+
globalThis as unknown as {
93+
// eslint-disable-next-line @typescript-eslint/naming-convention
94+
__coverage__?: Record<string, unknown>;
95+
}
96+
).__coverage__,
97+
);
98+
if (!coverage) return;
99+
const dir = path.join(testInfo.project.outputDir, "coverage");
100+
fs.mkdirSync(dir, { recursive: true });
101+
fs.writeFileSync(
102+
path.join(dir, `${testInfo.testId}-${Date.now()}.json`),
103+
JSON.stringify(coverage),
104+
);
105+
} catch {
106+
// Best-effort: page may have crashed or been closed
107+
}
108+
},
109+
{ auto: true, scope: "test" },
110+
],
80111
});
81112

82113
export const test = Object.assign(baseTest, {

src/utils/plugin-metadata.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ export interface PluginMetadata {
1414
pluginConfig: Record<string, unknown>;
1515
packageName: string;
1616
sourceFile: string;
17+
role?: string;
1718
}
1819

1920
interface PackageCRD {
2021
spec?: {
2122
packageName?: string;
2223
dynamicArtifact?: string;
24+
backstage?: {
25+
role?: string;
26+
};
2327
appConfigExamples?: Array<{
2428
title?: string;
2529
content?: Record<string, unknown>;
@@ -227,6 +231,7 @@ export async function parseMetadataFile(
227231
const packagePath = parsed?.spec?.dynamicArtifact;
228232
const packageName = parsed?.spec?.packageName;
229233
const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content;
234+
const role = parsed?.spec?.backstage?.role;
230235

231236
if (!packagePath) {
232237
throw new Error(
@@ -244,6 +249,7 @@ export async function parseMetadataFile(
244249
pluginConfig: pluginConfig || {},
245250
packageName,
246251
sourceFile: filePath,
252+
role,
247253
};
248254
}
249255

@@ -482,8 +488,14 @@ async function resolvePluginPackages(
482488
if (prOciUrls) {
483489
const prUrl = prOciUrls.get(displayName);
484490
if (prUrl) {
485-
console.log(`[PluginMetadata] PR: ${pkg}${prUrl}`);
486-
return { ...plugin, package: prUrl };
491+
const usesCoverage =
492+
process.env.E2E_COLLECT_COVERAGE === "true" &&
493+
metadata.role === "frontend-plugin";
494+
const resolved = usesCoverage
495+
? prUrl.replace(/(:[^!]+)/, "$1__coverage")
496+
: prUrl;
497+
console.log(`[PluginMetadata] PR: ${pkg}${resolved}`);
498+
return { ...plugin, package: resolved };
487499
}
488500
}
489501

0 commit comments

Comments
 (0)