feat(logging): add professional logging with pino and headless E2E tests - Add ILogger port interface in application layer - Implement PinoLogAdapter with Electron-compatible structured logging - Add NoOpLogAdapter for testing - Wire logging into DI container and all adapters - Create 32 E2E tests for automation workflow (headless-only) - Add vitest.e2e.config.ts for E2E test configuration - All tests enforce HEADLESS mode (no headed browser allowed)
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
AutomationResult,
|
||||
} from '../../../packages/application/ports/AutomationResults';
|
||||
import { IRacingSelectorMap, getStepSelectors, getStepName, isModalStep } from './selectors/IRacingSelectorMap';
|
||||
import type { ILogger, LogContext } from '../../../application/ports/ILogger';
|
||||
import { NoOpLogAdapter } from '../logging/NoOpLogAdapter';
|
||||
|
||||
/**
|
||||
* Configuration for connecting to browser via Chrome DevTools Protocol
|
||||
@@ -55,8 +57,9 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
private page: Page | null = null;
|
||||
private config: Required<DevToolsConfig>;
|
||||
private connected: boolean = false;
|
||||
private logger: ILogger;
|
||||
|
||||
constructor(config: DevToolsConfig = {}) {
|
||||
constructor(config: DevToolsConfig = {}, logger?: ILogger) {
|
||||
this.config = {
|
||||
browserWSEndpoint: config.browserWSEndpoint ?? '',
|
||||
debuggingPort: config.debuggingPort ?? 9222,
|
||||
@@ -64,6 +67,7 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
typingDelay: config.typingDelay ?? 50,
|
||||
waitForNetworkIdle: config.waitForNetworkIdle ?? true,
|
||||
};
|
||||
this.logger = logger ?? new NoOpLogAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,17 +76,26 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.connected) {
|
||||
this.logger.debug('Already connected to browser');
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
this.logger.info('Connecting to browser via CDP', {
|
||||
debuggingPort: this.config.debuggingPort,
|
||||
hasWsEndpoint: !!this.config.browserWSEndpoint
|
||||
});
|
||||
|
||||
try {
|
||||
if (this.config.browserWSEndpoint) {
|
||||
// Connect using explicit WebSocket endpoint
|
||||
this.logger.debug('Using explicit WebSocket endpoint');
|
||||
this.browser = await puppeteer.connect({
|
||||
browserWSEndpoint: this.config.browserWSEndpoint,
|
||||
});
|
||||
} else {
|
||||
// Connect using debugging port - need to fetch endpoint first
|
||||
this.logger.debug('Fetching WebSocket endpoint from debugging port');
|
||||
const response = await fetch(`http://127.0.0.1:${this.config.debuggingPort}/json/version`);
|
||||
const data = await response.json();
|
||||
const wsEndpoint = data.webSocketDebuggerUrl;
|
||||
@@ -104,8 +117,15 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
this.page.setDefaultTimeout(this.config.defaultTimeout);
|
||||
|
||||
this.connected = true;
|
||||
const durationMs = Date.now() - startTime;
|
||||
this.logger.info('Connected to browser successfully', {
|
||||
durationMs,
|
||||
pageUrl: this.page.url(),
|
||||
totalPages: pages.length
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error('Failed to connect to browser', error instanceof Error ? error : new Error(errorMessage));
|
||||
throw new Error(`Failed to connect to browser: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
@@ -115,6 +135,7 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
* The user can continue using the browser after disconnection.
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
this.logger.info('Disconnecting from browser');
|
||||
if (this.browser) {
|
||||
// Disconnect without closing - user may still use the browser
|
||||
this.browser.disconnect();
|
||||
@@ -122,6 +143,7 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
this.page = null;
|
||||
}
|
||||
this.connected = false;
|
||||
this.logger.debug('Browser disconnected');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,6 +519,9 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
const stepNumber = stepId.value;
|
||||
const stepSelectors = getStepSelectors(stepNumber);
|
||||
const stepName = getStepName(stepNumber);
|
||||
const startTime = Date.now();
|
||||
|
||||
this.logger.info('Executing step', { stepId: stepNumber, stepName });
|
||||
|
||||
try {
|
||||
switch (stepNumber) {
|
||||
@@ -562,6 +587,7 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
return await this.executeTrackConditionsStep(stepSelectors, config);
|
||||
|
||||
default:
|
||||
this.logger.warn('Unknown step requested', { stepId: stepNumber });
|
||||
return {
|
||||
success: false,
|
||||
error: `Unknown step: ${stepNumber}`,
|
||||
@@ -570,6 +596,12 @@ export class BrowserDevToolsAdapter implements IBrowserAutomation {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const durationMs = Date.now() - startTime;
|
||||
this.logger.error('Step execution failed', error instanceof Error ? error : new Error(errorMessage), {
|
||||
stepId: stepNumber,
|
||||
stepName,
|
||||
durationMs
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
|
||||
Reference in New Issue
Block a user