pcbjam/tests/e2e/utils/test-utils.ts
Viktor Vaczi 26181bb929 Refactor test logging into shared utilities
- Extract common logging code to tests/e2e/utils/
  - fixtures.ts: Playwright fixture with automatic log capture
  - test-utils.ts: Logging utilities and helper functions
- Each test now automatically captures:
  - Console logs with timestamps and log levels
  - Page errors with full stack traces
- Log files written per test to tests/logs/
  - <test-name>.log for all console output
  - <test-name>.errors.log only when errors occur
- Update all 13 spec files to use shared fixtures
- Update README with test structure and logging docs
- Net reduction of ~700 lines by removing duplicated code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:54:54 +01:00

94 lines
2.7 KiB
TypeScript

import { Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
export const MAIN_CANVAS = '#canvas';
export const LOGS_DIR = path.join(__dirname, '..', '..', 'logs');
export interface TestLogger {
consoleLogs: string[];
errors: string[];
cleanup: () => void;
}
// Ensure logs directory exists
export function ensureLogsDir() {
if (!fs.existsSync(LOGS_DIR)) {
fs.mkdirSync(LOGS_DIR, { recursive: true });
}
}
// Setup logging for a test - captures console and errors
export function setupTestLogger(page: Page): TestLogger {
const consoleLogs: string[] = [];
const errors: string[] = [];
const consoleHandler = (msg: any) => {
const type = msg.type();
const text = msg.text();
const timestamp = new Date().toISOString();
consoleLogs.push(`[${timestamp}] [${type.toUpperCase()}] ${text}`);
};
const errorHandler = (err: Error) => {
const timestamp = new Date().toISOString();
const stack = err.stack || 'No stack trace available';
errors.push(`[${timestamp}] [ERROR] ${err.message}\n${stack}`);
};
page.on('console', consoleHandler);
page.on('pageerror', errorHandler);
const cleanup = () => {
page.off('console', consoleHandler);
page.off('pageerror', errorHandler);
};
return { consoleLogs, errors, cleanup };
}
// Write logs to files after test completion
export function writeTestLogs(testName: string, logger: TestLogger) {
ensureLogsDir();
// Sanitize test name for filesystem
const safeTestName = testName
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
// Always write console log file
const logFile = path.join(LOGS_DIR, `${safeTestName}.log`);
fs.writeFileSync(logFile, logger.consoleLogs.join('\n'));
// Only write error file if there are errors (excluding favicon)
const realErrors = logger.errors.filter(e => !e.includes('favicon'));
if (realErrors.length > 0) {
const errorFile = path.join(LOGS_DIR, `${safeTestName}.errors.log`);
fs.writeFileSync(errorFile, realErrors.join('\n\n'));
}
}
// Helper to wait for app initialization
export async function waitForApp(page: Page, timeout = 30000) {
await page.waitForSelector(MAIN_CANVAS, { state: 'visible', timeout });
await page.waitForTimeout(500);
}
// Helper to try loading app with fallback
export async function tryLoadApp(page: Page, timeout = 15000): Promise<boolean> {
try {
await waitForApp(page, timeout);
return true;
} catch {
return false;
}
}
// Get canvas bounding box helper
export async function getCanvasBox(page: Page) {
const canvas = page.locator(MAIN_CANVAS);
const box = await canvas.boundingBox();
if (!box) throw new Error('Canvas not found');
return box;
}