Files
Felipe Cardoso 96df7edf88 Refactor useAuth hook, settings components, and docs for formatting and readability improvements
- Consolidated multi-line arguments into single lines where appropriate in `useAuth`.
- Improved spacing and readability in data processing across components (`ProfileSettingsForm`, `PasswordChangeForm`, `SessionCard`).
- Applied consistent table and markdown formatting in design system docs (e.g., `README.md`, `08-ai-guidelines.md`, `00-quick-start.md`).
- Updated code snippets to ensure adherence to Prettier rules and streamlined JSX structures.
2025-11-10 11:03:45 +01:00

277 lines
6.9 KiB
TypeScript

/**
* E2E Coverage Helpers
*
* Utilities for collecting code coverage during Playwright E2E tests.
* Supports both V8 coverage (Chromium-only) and Istanbul instrumentation.
*
* Usage in E2E tests:
*
* ```typescript
* import { startCoverage, stopAndSaveCoverage } from './helpers/coverage';
*
* test.describe('My Tests', () => {
* test.beforeEach(async ({ page }) => {
* await startCoverage(page);
* await page.goto('/');
* });
*
* test.afterEach(async ({ page }, testInfo) => {
* await stopAndSaveCoverage(page, testInfo.title);
* });
*
* test('my test', async ({ page }) => {
* // Your test code...
* });
* });
* ```
*/
import type { Page } from '@playwright/test';
import fs from 'fs/promises';
import path from 'path';
/**
* Check if coverage collection is enabled via environment variable
*/
export function isCoverageEnabled(): boolean {
return process.env.E2E_COVERAGE === 'true';
}
/**
* Start collecting V8 coverage for a page
*
* @param page - Playwright page instance
* @param options - Coverage options
*/
export async function startCoverage(
page: Page,
options?: {
resetOnNavigation?: boolean;
includeRawScriptCoverage?: boolean;
}
) {
if (!isCoverageEnabled()) {
return;
}
try {
await page.coverage.startJSCoverage({
resetOnNavigation: options?.resetOnNavigation ?? false,
// @ts-expect-error - includeRawScriptCoverage is not in official types but supported by Playwright
includeRawScriptCoverage: options?.includeRawScriptCoverage ?? false,
});
} catch (error) {
console.warn('⚠️ Failed to start coverage:', error);
}
}
/**
* Stop coverage collection and save to file
*
* @param page - Playwright page instance
* @param testName - Name of the test (used for filename)
*/
export async function stopAndSaveCoverage(page: Page, testName: string) {
if (!isCoverageEnabled()) {
return;
}
try {
const coverage = await page.coverage.stopJSCoverage();
if (coverage.length === 0) {
console.warn('⚠️ No coverage collected for:', testName);
return;
}
// Save V8 coverage
await saveV8Coverage(coverage, testName);
} catch (error) {
console.warn('⚠️ Failed to stop/save coverage for', testName, ':', error);
}
}
/**
* Save V8 coverage data to disk
*
* @param coverage - V8 coverage data
* @param testName - Test name for the filename
*/
async function saveV8Coverage(coverage: any[], testName: string) {
const coverageDir = path.join(process.cwd(), 'coverage-e2e', 'raw');
await fs.mkdir(coverageDir, { recursive: true });
const filename = sanitizeFilename(testName);
const filepath = path.join(coverageDir, `${filename}.json`);
await fs.writeFile(filepath, JSON.stringify(coverage, null, 2));
}
/**
* Collect Istanbul coverage from browser window object
*
* Use this if you're using Istanbul instrumentation instead of V8 coverage.
* Requires babel-plugin-istanbul or similar instrumentation.
*
* @param page - Playwright page instance
* @param testName - Name of the test
*/
export async function saveIstanbulCoverage(page: Page, testName: string) {
if (!isCoverageEnabled()) {
return;
}
try {
// Extract coverage from window.__coverage__ (set by Istanbul instrumentation)
const coverage = await page.evaluate(() => (window as any).__coverage__);
if (!coverage) {
console.warn('⚠️ No Istanbul coverage found for:', testName);
console.warn(' Make sure babel-plugin-istanbul is configured');
return;
}
// Save Istanbul coverage
const coverageDir = path.join(process.cwd(), 'coverage-e2e', '.nyc_output');
await fs.mkdir(coverageDir, { recursive: true });
const filename = sanitizeFilename(testName);
const filepath = path.join(coverageDir, `${filename}.json`);
await fs.writeFile(filepath, JSON.stringify(coverage, null, 2));
} catch (error) {
console.warn('⚠️ Failed to save Istanbul coverage for', testName, ':', error);
}
}
/**
* Combined coverage helper for test hooks
*
* Automatically uses V8 coverage if available, falls back to Istanbul
*
* Usage in beforeEach/afterEach:
* ```typescript
* test.beforeEach(async ({ page }) => {
* await withCoverage.start(page);
* });
*
* test.afterEach(async ({ page }, testInfo) => {
* await withCoverage.stop(page, testInfo.title);
* });
* ```
*/
export const withCoverage = {
/**
* Start coverage collection (V8 approach)
*/
async start(page: Page) {
await startCoverage(page);
},
/**
* Stop coverage and save (tries V8, then Istanbul)
*/
async stop(page: Page, testName: string) {
if (!isCoverageEnabled()) {
return;
}
// Try V8 coverage first
try {
const v8Coverage = await page.coverage.stopJSCoverage();
if (v8Coverage && v8Coverage.length > 0) {
await saveV8Coverage(v8Coverage, testName);
return;
}
} catch {
// V8 coverage not available, try Istanbul
}
// Fall back to Istanbul coverage
await saveIstanbulCoverage(page, testName);
},
};
/**
* Sanitize test name for use as filename
*
* @param name - Test name
* @returns Sanitized filename
*/
function sanitizeFilename(name: string): string {
return name
.replace(/[^a-z0-9\s-]/gi, '') // Remove special chars
.replace(/\s+/g, '_') // Replace spaces with underscores
.toLowerCase()
.substring(0, 100); // Limit length
}
/**
* Get coverage statistics (for debugging)
*
* @param page - Playwright page instance
* @returns Coverage statistics
*/
export async function getCoverageStats(page: Page): Promise<{
v8Available: boolean;
istanbulAvailable: boolean;
istanbulFileCount?: number;
}> {
const stats = {
v8Available: false,
istanbulAvailable: false,
istanbulFileCount: undefined as number | undefined,
};
// Check V8 coverage
try {
await page.coverage.startJSCoverage();
await page.coverage.stopJSCoverage();
stats.v8Available = true;
} catch {
stats.v8Available = false;
}
// Check Istanbul coverage
try {
const coverage = await page.evaluate(() => (window as any).__coverage__);
if (coverage) {
stats.istanbulAvailable = true;
stats.istanbulFileCount = Object.keys(coverage).length;
}
} catch {
stats.istanbulAvailable = false;
}
return stats;
}
/**
* Example usage in a test file:
*
* ```typescript
* import { test, expect } from '@playwright/test';
* import { withCoverage } from './helpers/coverage';
*
* test.describe('Homepage Tests', () => {
* test.beforeEach(async ({ page }) => {
* await withCoverage.start(page);
* await page.goto('/');
* });
*
* test.afterEach(async ({ page }, testInfo) => {
* await withCoverage.stop(page, testInfo.title);
* });
*
* test('displays header', async ({ page }) => {
* await expect(page.getByRole('heading')).toBeVisible();
* });
* });
* ```
*
* Then run with:
* ```bash
* E2E_COVERAGE=true npm run test:e2e
* ```
*/