diff --git a/docs/changelog.md b/docs/changelog.md index 5430936..6f10483 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,11 @@ All notable changes to this project will be documented in this file. -## [1.1.45] - Current +## [2.1.0] - Current + +### Added + +- **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 `/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). ### Fixed diff --git a/package.json b/package.json index a71e710..3a5d0b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@red-hat-developer-hub/e2e-test-utils", - "version": "1.1.45", + "version": "2.1.0", "description": "Test utilities for RHDH E2E tests", "license": "Apache-2.0", "repository": { diff --git a/src/playwright/fixtures/test.ts b/src/playwright/fixtures/test.ts index 4456227..e9d2227 100644 --- a/src/playwright/fixtures/test.ts +++ b/src/playwright/fixtures/test.ts @@ -4,6 +4,7 @@ import { LoginHelper, UIhelper } from "../helpers/index.js"; import { runOnce } from "../run-once.js"; import { $ } from "../../utils/bash.js"; import { WorkspacePaths } from "../../utils/workspace-paths.js"; +import fs from "node:fs"; import path from "path"; type RHDHDeploymentTestFixtures = { @@ -11,6 +12,8 @@ type RHDHDeploymentTestFixtures = { uiHelper: UIhelper; loginHelper: LoginHelper; autoAnnotations: void; + // eslint-disable-next-line @typescript-eslint/naming-convention + _coverageCollector: void; }; type RHDHDeploymentWorkerFixtures = { @@ -77,6 +80,34 @@ const baseTest = base.extend< }, { auto: true, scope: "test" }, ], + // eslint-disable-next-line @typescript-eslint/naming-convention + _coverageCollector: [ + async ({ page }, use, testInfo) => { + await use(); + if (process.env.E2E_COLLECT_COVERAGE !== "true") return; + try { + const coverage = await page.evaluate( + () => + ( + globalThis as unknown as { + // eslint-disable-next-line @typescript-eslint/naming-convention + __coverage__?: Record; + } + ).__coverage__, + ); + if (!coverage) return; + const dir = path.join(testInfo.project.outputDir, "coverage"); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync( + path.join(dir, `${testInfo.testId}-${Date.now()}.json`), + JSON.stringify(coverage), + ); + } catch { + // Best-effort: page may have crashed or been closed + } + }, + { auto: true, scope: "test" }, + ], }); export const test = Object.assign(baseTest, { diff --git a/src/utils/plugin-metadata.ts b/src/utils/plugin-metadata.ts index 4dc6828..5347944 100644 --- a/src/utils/plugin-metadata.ts +++ b/src/utils/plugin-metadata.ts @@ -14,12 +14,16 @@ export interface PluginMetadata { pluginConfig: Record; packageName: string; sourceFile: string; + role?: string; } interface PackageCRD { spec?: { packageName?: string; dynamicArtifact?: string; + backstage?: { + role?: string; + }; appConfigExamples?: Array<{ title?: string; content?: Record; @@ -227,6 +231,7 @@ export async function parseMetadataFile( const packagePath = parsed?.spec?.dynamicArtifact; const packageName = parsed?.spec?.packageName; const pluginConfig = parsed?.spec?.appConfigExamples?.[0]?.content; + const role = parsed?.spec?.backstage?.role; if (!packagePath) { throw new Error( @@ -244,6 +249,7 @@ export async function parseMetadataFile( pluginConfig: pluginConfig || {}, packageName, sourceFile: filePath, + role, }; } @@ -482,8 +488,14 @@ async function resolvePluginPackages( if (prOciUrls) { const prUrl = prOciUrls.get(displayName); if (prUrl) { - console.log(`[PluginMetadata] PR: ${pkg} → ${prUrl}`); - return { ...plugin, package: prUrl }; + const usesCoverage = + process.env.E2E_COLLECT_COVERAGE === "true" && + metadata.role === "frontend-plugin"; + const resolved = usesCoverage + ? prUrl.replace(/(:[^!]+)/, "$1__coverage") + : prUrl; + console.log(`[PluginMetadata] PR: ${pkg} → ${resolved}`); + return { ...plugin, package: resolved }; } }