Some checks failed
Go Benchmark / Performance Regression Check (push) Has been cancelled
Cerberus Integration / Cerberus Security Stack Integration (push) Has been cancelled
Upload Coverage to Codecov / Backend Codecov Upload (push) Has been cancelled
Upload Coverage to Codecov / Frontend Codecov Upload (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (go) (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Has been cancelled
CrowdSec Integration / CrowdSec Bouncer Integration (push) Has been cancelled
Docker Build, Publish & Test / build-and-push (push) Has been cancelled
Quality Checks / Auth Route Protection Contract (push) Has been cancelled
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Has been cancelled
Quality Checks / Backend (Go) (push) Has been cancelled
Quality Checks / Frontend (React) (push) Has been cancelled
Rate Limit integration / Rate Limiting Integration (push) Has been cancelled
Security Scan (PR) / Trivy Binary Scan (push) Has been cancelled
Supply Chain Verification (PR) / Verify Supply Chain (push) Has been cancelled
WAF integration / Coraza WAF Integration (push) Has been cancelled
Docker Build, Publish & Test / Security Scan PR Image (push) Has been cancelled
Repo Health Check / Repo health (push) Has been cancelled
History Rewrite Dry-Run / Dry-run preview for history rewrite (push) Has been cancelled
Prune Renovate Branches / prune (push) Has been cancelled
Renovate / renovate (push) Has been cancelled
Nightly Build & Package / sync-development-to-nightly (push) Has been cancelled
Nightly Build & Package / Trigger Nightly Validation Workflows (push) Has been cancelled
Nightly Build & Package / build-and-push-nightly (push) Has been cancelled
Nightly Build & Package / test-nightly-image (push) Has been cancelled
Nightly Build & Package / verify-nightly-supply-chain (push) Has been cancelled
Update GeoLite2 Checksum / update-checksum (push) Has been cancelled
Container Registry Prune / prune-ghcr (push) Has been cancelled
Container Registry Prune / prune-dockerhub (push) Has been cancelled
Container Registry Prune / summarize (push) Has been cancelled
Supply Chain Verification / Verify SBOM (push) Has been cancelled
Supply Chain Verification / Verify Release Artifacts (push) Has been cancelled
Supply Chain Verification / Verify Docker Image Supply Chain (push) Has been cancelled
Monitor Caddy Major Release / check-caddy-major (push) Has been cancelled
Weekly Nightly to Main Promotion / Verify Nightly Branch Health (push) Has been cancelled
Weekly Nightly to Main Promotion / Create Promotion PR (push) Has been cancelled
Weekly Nightly to Main Promotion / Trigger Missing Required Checks (push) Has been cancelled
Weekly Nightly to Main Promotion / Notify on Failure (push) Has been cancelled
Weekly Nightly to Main Promotion / Workflow Summary (push) Has been cancelled
290 lines
9.7 KiB
TypeScript
Executable File
290 lines
9.7 KiB
TypeScript
Executable File
import { Page, ConsoleMessage, Request } from '@playwright/test';
|
|
|
|
/**
|
|
* Diagnostic Helpers for E2E Test Debugging
|
|
*
|
|
* These helpers enable comprehensive browser console logging and state capture
|
|
* to diagnose test interruptions and failures. Use during Phase 1 investigation
|
|
* to identify root causes of browser context closures.
|
|
*
|
|
* @see docs/reports/phase1_diagnostics.md
|
|
*/
|
|
|
|
/**
|
|
* Enable comprehensive browser console logging for diagnostic purposes
|
|
* Captures console logs, page errors, request failures, and unhandled rejections
|
|
*
|
|
* @param page - Playwright Page instance
|
|
* @param options - Optional configuration for logging behavior
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* test.beforeEach(async ({ page }) => {
|
|
* enableDiagnosticLogging(page);
|
|
* // ... test setup
|
|
* });
|
|
* ```
|
|
*/
|
|
export function enableDiagnosticLogging(
|
|
page: Page,
|
|
options: {
|
|
captureConsole?: boolean;
|
|
captureErrors?: boolean;
|
|
captureRequests?: boolean;
|
|
captureDialogs?: boolean;
|
|
} = {}
|
|
): void {
|
|
const {
|
|
captureConsole = true,
|
|
captureErrors = true,
|
|
captureRequests = true,
|
|
captureDialogs = true,
|
|
} = options;
|
|
|
|
// Console messages (all levels)
|
|
if (captureConsole) {
|
|
page.on('console', (msg: ConsoleMessage) => {
|
|
const type = msg.type().toUpperCase();
|
|
const text = msg.text();
|
|
const location = msg.location();
|
|
|
|
// Special formatting for errors and warnings
|
|
if (type === 'ERROR' || type === 'WARNING') {
|
|
console.error(`[BROWSER ${type}] ${text}`);
|
|
} else {
|
|
console.log(`[BROWSER ${type}] ${text}`);
|
|
}
|
|
|
|
if (location.url) {
|
|
console.log(
|
|
` Location: ${location.url}:${location.lineNumber}:${location.columnNumber}`
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Page errors (JavaScript exceptions)
|
|
if (captureErrors) {
|
|
page.on('pageerror', (error: Error) => {
|
|
console.error('═══════════════════════════════════════════');
|
|
console.error('PAGE ERROR DETECTED');
|
|
console.error('═══════════════════════════════════════════');
|
|
console.error('Message:', error.message);
|
|
console.error('Stack:', error.stack);
|
|
console.error('Timestamp:', new Date().toISOString());
|
|
console.error('═══════════════════════════════════════════');
|
|
});
|
|
}
|
|
|
|
// Request failures (network errors)
|
|
if (captureRequests) {
|
|
page.on('requestfailed', (request: Request) => {
|
|
const failure = request.failure();
|
|
console.error('─────────────────────────────────────────');
|
|
console.error('REQUEST FAILED');
|
|
console.error('─────────────────────────────────────────');
|
|
console.error('URL:', request.url());
|
|
console.error('Method:', request.method());
|
|
console.error('Error:', failure?.errorText || 'Unknown');
|
|
console.error('Timestamp:', new Date().toISOString());
|
|
console.error('─────────────────────────────────────────');
|
|
});
|
|
}
|
|
|
|
// Unhandled promise rejections
|
|
if (captureErrors) {
|
|
page.on('console', (msg: ConsoleMessage) => {
|
|
if (msg.type() === 'error' && msg.text().includes('Unhandled')) {
|
|
console.error('╔═══════════════════════════════════════════╗');
|
|
console.error('║ UNHANDLED PROMISE REJECTION DETECTED ║');
|
|
console.error('╚═══════════════════════════════════════════╝');
|
|
console.error(msg.text());
|
|
console.error('Timestamp:', new Date().toISOString());
|
|
}
|
|
});
|
|
}
|
|
|
|
// Dialog events (if supported)
|
|
if (captureDialogs) {
|
|
page.on('dialog', async (dialog) => {
|
|
console.log(`[DIALOG] Type: ${dialog.type()}, Message: ${dialog.message()}`);
|
|
console.log(`[DIALOG] Timestamp: ${new Date().toISOString()}`);
|
|
// Auto-dismiss to prevent blocking
|
|
await dialog.dismiss();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Capture page state snapshot for debugging
|
|
* Logs current URL, title, and HTML content length
|
|
*
|
|
* @param page - Playwright Page instance
|
|
* @param label - Descriptive label for this snapshot
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* await capturePageState(page, 'Before dialog open');
|
|
* // ... perform action
|
|
* await capturePageState(page, 'After dialog close');
|
|
* ```
|
|
*/
|
|
export async function capturePageState(page: Page, label: string): Promise<void> {
|
|
const url = page.url();
|
|
const title = await page.title();
|
|
const html = await page.content();
|
|
|
|
console.log(`\n========== PAGE STATE: ${label} ==========`);
|
|
console.log(`URL: ${url}`);
|
|
console.log(`Title: ${title}`);
|
|
console.log(`HTML Length: ${html.length} characters`);
|
|
console.log(`Timestamp: ${new Date().toISOString()}`);
|
|
console.log(`===========================================\n`);
|
|
}
|
|
|
|
/**
|
|
* Track dialog lifecycle events for resource leak detection
|
|
* Logs when dialogs open and close to identify cleanup issues
|
|
*
|
|
* @param page - Playwright Page instance
|
|
* @param dialogSelector - Selector for the dialog element
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* test('dialog test', async ({ page }) => {
|
|
* const tracker = trackDialogLifecycle(page, '[role="dialog"]');
|
|
*
|
|
* await openDialog(page);
|
|
* await closeDialog(page);
|
|
*
|
|
* tracker.stop();
|
|
* });
|
|
* ```
|
|
*/
|
|
export function trackDialogLifecycle(
|
|
page: Page,
|
|
dialogSelector: string = '[role="dialog"]'
|
|
): { stop: () => void } {
|
|
let dialogCount = 0;
|
|
let isRunning = true;
|
|
|
|
const checkDialog = async () => {
|
|
if (!isRunning) return;
|
|
|
|
const dialogCount = await page.locator(dialogSelector).count();
|
|
|
|
if (dialogCount > 0) {
|
|
console.log(`[DIALOG LIFECYCLE] ${dialogCount} dialog(s) detected on page`);
|
|
console.log(`[DIALOG LIFECYCLE] Timestamp: ${new Date().toISOString()}`);
|
|
}
|
|
|
|
setTimeout(() => checkDialog(), 1000);
|
|
};
|
|
|
|
// Start monitoring
|
|
checkDialog();
|
|
|
|
return {
|
|
stop: () => {
|
|
isRunning = false;
|
|
console.log('[DIALOG LIFECYCLE] Tracking stopped');
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Monitor browser context health during test execution
|
|
* Detects when browser context is closed unexpectedly
|
|
*
|
|
* @param page - Playwright Page instance
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* test.beforeEach(async ({ page }) => {
|
|
* monitorBrowserContext(page);
|
|
* });
|
|
* ```
|
|
*/
|
|
export function monitorBrowserContext(page: Page): void {
|
|
const context = page.context();
|
|
const browser = context.browser();
|
|
|
|
context.on('close', () => {
|
|
console.error('╔═══════════════════════════════════════════╗');
|
|
console.error('║ BROWSER CONTEXT CLOSED UNEXPECTEDLY ║');
|
|
console.error('╚═══════════════════════════════════════════╝');
|
|
console.error('Timestamp:', new Date().toISOString());
|
|
console.error('This may indicate a resource leak or crash.');
|
|
});
|
|
|
|
if (browser) {
|
|
browser.on('disconnected', () => {
|
|
console.error('╔═══════════════════════════════════════════╗');
|
|
console.error('║ BROWSER DISCONNECTED UNEXPECTEDLY ║');
|
|
console.error('╚═══════════════════════════════════════════╝');
|
|
console.error('Timestamp:', new Date().toISOString());
|
|
});
|
|
}
|
|
|
|
page.on('close', () => {
|
|
console.warn('[PAGE CLOSED]', new Date().toISOString());
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Performance monitoring helper
|
|
* Tracks test execution time and identifies slow operations
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* test('my test', async ({ page }) => {
|
|
* const perf = startPerformanceMonitoring('My Test');
|
|
*
|
|
* perf.mark('Dialog open start');
|
|
* await openDialog(page);
|
|
* perf.mark('Dialog open end');
|
|
*
|
|
* perf.measure('Dialog open', 'Dialog open start', 'Dialog open end');
|
|
* perf.report();
|
|
* });
|
|
* ```
|
|
*/
|
|
export function startPerformanceMonitoring(testName: string) {
|
|
const startTime = performance.now();
|
|
const marks: Map<string, number> = new Map();
|
|
const measures: Array<{ name: string; duration: number }> = [];
|
|
|
|
return {
|
|
mark(name: string): void {
|
|
marks.set(name, performance.now());
|
|
console.log(`[PERF MARK] ${name} at ${marks.get(name)! - startTime}ms`);
|
|
},
|
|
|
|
measure(name: string, startMark: string, endMark: string): void {
|
|
const start = marks.get(startMark);
|
|
const end = marks.get(endMark);
|
|
|
|
if (start !== undefined && end !== undefined) {
|
|
const duration = end - start;
|
|
measures.push({ name, duration });
|
|
console.log(`[PERF MEASURE] ${name}: ${duration.toFixed(2)}ms`);
|
|
} else {
|
|
console.warn(`[PERF WARN] Missing marks for measure: ${name}`);
|
|
}
|
|
},
|
|
|
|
report(): void {
|
|
const totalTime = performance.now() - startTime;
|
|
|
|
console.log('\n========== PERFORMANCE REPORT ==========');
|
|
console.log(`Test: ${testName}`);
|
|
console.log(`Total Duration: ${totalTime.toFixed(2)}ms`);
|
|
console.log('\nMeasurements:');
|
|
measures.forEach(({ name, duration }) => {
|
|
console.log(` ${name}: ${duration.toFixed(2)}ms`);
|
|
});
|
|
console.log('=========================================\n');
|
|
},
|
|
};
|
|
}
|