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:
2025-11-22 01:02:38 +01:00
parent 7eae6e3bd4
commit 3a60ffae91
13 changed files with 1287 additions and 14 deletions

View File

@@ -9,6 +9,8 @@ import {
ModalResult,
} from '../../../packages/application/ports/AutomationResults';
import { StepId } from '../../../packages/domain/value-objects/StepId';
import type { ILogger } from '../../../application/ports/ILogger';
import { NoOpLogAdapter } from '../logging/NoOpLogAdapter';
export interface NutJsConfig {
mouseSpeed?: number;
@@ -20,27 +22,43 @@ export interface NutJsConfig {
export class NutJsAutomationAdapter implements IBrowserAutomation {
private config: Required<NutJsConfig>;
private connected: boolean = false;
private logger: ILogger;
constructor(config: NutJsConfig = {}) {
constructor(config: NutJsConfig = {}, logger?: ILogger) {
this.config = {
mouseSpeed: config.mouseSpeed ?? 1000,
keyboardDelay: config.keyboardDelay ?? 50,
screenResolution: config.screenResolution ?? { width: 1920, height: 1080 },
defaultTimeout: config.defaultTimeout ?? 30000,
};
this.logger = logger ?? new NoOpLogAdapter();
mouse.config.mouseSpeed = this.config.mouseSpeed;
keyboard.config.autoDelayMs = this.config.keyboardDelay;
}
async connect(): Promise<AutomationResult> {
const startTime = Date.now();
this.logger.info('Initializing nut.js OS-level automation');
try {
await screen.width();
await screen.height();
const width = await screen.width();
const height = await screen.height();
this.connected = true;
const durationMs = Date.now() - startTime;
this.logger.info('nut.js automation connected', {
durationMs,
screenWidth: width,
screenHeight: height,
mouseSpeed: this.config.mouseSpeed,
keyboardDelay: this.config.keyboardDelay
});
return { success: true };
} catch (error) {
return { success: false, error: `Screen access failed: ${error}` };
const errorMsg = `Screen access failed: ${error}`;
this.logger.error('Failed to initialize nut.js', error instanceof Error ? error : new Error(errorMsg));
return { success: false, error: errorMsg };
}
}
@@ -136,7 +154,9 @@ export class NutJsAutomationAdapter implements IBrowserAutomation {
}
async disconnect(): Promise<void> {
this.logger.info('Disconnecting nut.js automation');
this.connected = false;
this.logger.debug('nut.js disconnected');
}
isConnected(): boolean {
@@ -145,10 +165,14 @@ export class NutJsAutomationAdapter implements IBrowserAutomation {
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResult> {
const stepNumber = stepId.value;
const startTime = Date.now();
this.logger.info('Executing step via OS-level automation', { stepId: stepNumber });
try {
switch (stepNumber) {
case 1:
this.logger.debug('Skipping login step - user pre-authenticated', { stepId: stepNumber });
return {
success: true,
metadata: {
@@ -159,6 +183,7 @@ export class NutJsAutomationAdapter implements IBrowserAutomation {
};
case 18:
this.logger.info('Safety stop at final step', { stepId: stepNumber });
return {
success: true,
metadata: {
@@ -168,7 +193,9 @@ export class NutJsAutomationAdapter implements IBrowserAutomation {
},
};
default:
default: {
const durationMs = Date.now() - startTime;
this.logger.info('Step executed successfully', { stepId: stepNumber, durationMs });
return {
success: true,
metadata: {
@@ -177,8 +204,14 @@ export class NutJsAutomationAdapter implements IBrowserAutomation {
config,
},
};
}
}
} catch (error) {
const durationMs = Date.now() - startTime;
this.logger.error('Step execution failed', error instanceof Error ? error : new Error(String(error)), {
stepId: stepNumber,
durationMs
});
return {
success: false,
error: String(error),