wip
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import type { LogContext } from '@gridpilot/automation/application/ports/LoggerContext';
|
||||
|
||||
/**
|
||||
* Integration tests for Browser Mode in PlaywrightAutomationAdapter - GREEN PHASE
|
||||
@@ -27,7 +29,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
delete process.env.NODE_ENV;
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: undefined,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
@@ -53,7 +60,7 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
afterAll(() => {
|
||||
if (unhandledRejectionHandler) {
|
||||
process.removeListener('unhandledRejection', unhandledRejectionHandler);
|
||||
(process as any).removeListener('unhandledRejection', unhandledRejectionHandler);
|
||||
unhandledRejectionHandler = null;
|
||||
}
|
||||
});
|
||||
@@ -72,7 +79,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
describe('Headless Mode Launch (NODE_ENV=production/test)', () => {
|
||||
it('should launch browser with headless: true when NODE_ENV=production', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'production',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -80,8 +92,8 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'mock',
|
||||
});
|
||||
|
||||
}, undefined, undefined);
|
||||
|
||||
const result = await adapter.connect();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
@@ -91,7 +103,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
});
|
||||
|
||||
it('should launch browser with headless: true when NODE_ENV=test', async () => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'test',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -99,8 +116,8 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'mock',
|
||||
});
|
||||
|
||||
}, undefined, undefined);
|
||||
|
||||
const result = await adapter.connect();
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
@@ -110,7 +127,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
});
|
||||
|
||||
it('should default to headless when NODE_ENV is not set', async () => {
|
||||
delete process.env.NODE_ENV;
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: undefined,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -118,8 +140,8 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'mock',
|
||||
});
|
||||
|
||||
}, undefined, undefined);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
expect(adapter.getBrowserMode()).toBe('headless');
|
||||
@@ -134,7 +156,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
});
|
||||
|
||||
it('should report NODE_ENV as source in production mode', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'production',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -142,7 +169,7 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'mock',
|
||||
});
|
||||
}, undefined, undefined);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
@@ -150,7 +177,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
});
|
||||
|
||||
it('should report NODE_ENV as source in test mode', async () => {
|
||||
process.env.NODE_ENV = 'test';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'test',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -158,7 +190,7 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'mock',
|
||||
});
|
||||
}, undefined);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
@@ -173,22 +205,26 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
});
|
||||
|
||||
it('should log browser mode configuration with NODE_ENV source in production', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
(process.env as any).NODE_ENV = 'production';
|
||||
|
||||
const logSpy: Array<{ level: string; message: string; context?: Record<string, unknown> }> = [];
|
||||
type LoggerLike = {
|
||||
debug: (msg: string, ctx?: Record<string, unknown>) => void;
|
||||
info: (msg: string, ctx?: Record<string, unknown>) => void;
|
||||
warn: (msg: string, ctx?: Record<string, unknown>) => void;
|
||||
error: (msg: string, ctx?: Record<string, unknown>) => void;
|
||||
child: () => LoggerLike;
|
||||
debug: (message: string, context?: Record<string, unknown>) => void;
|
||||
info: (message: string, context?: Record<string, unknown>) => void;
|
||||
warn: (message: string, context?: Record<string, unknown>) => void;
|
||||
error: (message: string, error?: Error, context?: Record<string, unknown>) => void;
|
||||
fatal: (message: string, error?: Error, context?: Record<string, unknown>) => void;
|
||||
child: (context: Record<string, unknown>) => LoggerLike;
|
||||
flush: () => Promise<void>;
|
||||
};
|
||||
const mockLogger: LoggerLike = {
|
||||
debug: (msg: string, ctx?: Record<string, unknown>) => logSpy.push({ level: 'debug', message: msg, context: ctx }),
|
||||
info: (msg: string, ctx?: Record<string, unknown>) => logSpy.push({ level: 'info', message: msg, context: ctx }),
|
||||
warn: (msg: string, ctx?: Record<string, unknown>) => logSpy.push({ level: 'warn', message: msg, context: ctx }),
|
||||
error: (msg: string, ctx?: Record<string, unknown>) => logSpy.push({ level: 'error', message: msg, context: ctx }),
|
||||
child: () => mockLogger,
|
||||
debug: (message: string, context?: Record<string, unknown>) => logSpy.push({ level: 'debug', message, ...(context ? { context } : {}) }),
|
||||
info: (message: string, context?: Record<string, unknown>) => logSpy.push({ level: 'info', message, ...(context ? { context } : {}) }),
|
||||
warn: (message: string, context?: Record<string, unknown>) => logSpy.push({ level: 'warn', message, ...(context ? { context } : {}) }),
|
||||
error: (message: string, error?: Error, context?: Record<string, unknown>) => logSpy.push({ level: 'error', message, ...(context ? { context } : {}) }),
|
||||
fatal: (message: string, error?: Error, context?: Record<string, unknown>) => logSpy.push({ level: 'fatal', message, ...(context ? { context } : {}) }),
|
||||
child: (context: Record<string, unknown>) => mockLogger,
|
||||
flush: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
@@ -215,7 +251,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
describe('Persistent Context', () => {
|
||||
it('should apply browser mode to persistent browser context', async () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'production',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
@@ -226,8 +267,8 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
adapter = new PlaywrightAutomationAdapter({
|
||||
mode: 'real',
|
||||
userDataDir,
|
||||
});
|
||||
|
||||
}, undefined, undefined);
|
||||
|
||||
await adapter.connect();
|
||||
|
||||
expect(adapter.getBrowserMode()).toBe('headless');
|
||||
@@ -242,7 +283,12 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
|
||||
describe('Runtime loader re-read instrumentation (test-only)', () => {
|
||||
it('reads mode from injected loader and passes headless flag to launcher accordingly', async () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
Object.defineProperty(process.env, 'NODE_ENV', {
|
||||
value: 'development',
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
const { PlaywrightAutomationAdapter } = await import(
|
||||
'packages/automation/infrastructure/adapters/automation'
|
||||
);
|
||||
@@ -293,7 +339,7 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
const r1 = await adapter.connect();
|
||||
expect(r1.success).toBe(true);
|
||||
expect(launches.length).toBeGreaterThan(0);
|
||||
expect(launches[0].opts.headless).toBe(false);
|
||||
expect((launches[0] as any).opts.headless).toBe(false);
|
||||
|
||||
// Disconnect and change loader to headless
|
||||
await adapter.disconnect();
|
||||
@@ -305,10 +351,10 @@ describe('Browser Mode Integration - GREEN Phase', () => {
|
||||
// The second recorded launch may be at index 1 if both calls used the same launcher path
|
||||
const secondLaunch = launches.slice(1).find(l => l.type === 'launch' || l.type === 'launchPersistent');
|
||||
expect(secondLaunch).toBeDefined();
|
||||
expect(secondLaunch!.opts.headless).toBe(true);
|
||||
expect(secondLaunch!.opts?.headless).toBe(true);
|
||||
|
||||
// Cleanup test hook
|
||||
AdapterWithTestLauncher.testLauncher = undefined;
|
||||
(AdapterWithTestLauncher as any).testLauncher = undefined;
|
||||
await adapter.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,8 @@ import { FixtureServer, getAllStepFixtureMappings, PlaywrightAutomationAdapter }
|
||||
declare const getComputedStyle: any;
|
||||
declare const document: any;
|
||||
|
||||
const logger = console as any;
|
||||
|
||||
describe('FixtureServer integration', () => {
|
||||
let server: FixtureServer;
|
||||
let adapter: PlaywrightAutomationAdapter;
|
||||
@@ -22,7 +24,7 @@ describe('FixtureServer integration', () => {
|
||||
headless: true,
|
||||
timeout: 5000,
|
||||
baseUrl,
|
||||
});
|
||||
}, logger);
|
||||
|
||||
const connectResult = await adapter.connect();
|
||||
expect(connectResult.success).toBe(true);
|
||||
@@ -86,7 +88,7 @@ describe('FixtureServer integration', () => {
|
||||
const disconnectedAdapter = new PlaywrightAutomationAdapter({
|
||||
headless: true,
|
||||
timeout: 1000,
|
||||
});
|
||||
}, logger);
|
||||
|
||||
const navResult = await disconnectedAdapter.navigateToPage('http://localhost:9999');
|
||||
expect(navResult.success).toBe(false);
|
||||
@@ -105,7 +107,7 @@ describe('FixtureServer integration', () => {
|
||||
it('reports connected state correctly', async () => {
|
||||
expect(adapter.isConnected()).toBe(true);
|
||||
|
||||
const newAdapter = new PlaywrightAutomationAdapter({ headless: true });
|
||||
const newAdapter = new PlaywrightAutomationAdapter({ headless: true }, logger);
|
||||
expect(newAdapter.isConnected()).toBe(false);
|
||||
|
||||
await newAdapter.connect();
|
||||
|
||||
@@ -287,7 +287,7 @@ describe('InMemorySessionRepository Integration Tests', () => {
|
||||
const inProgressSessions = await repository.findByState('IN_PROGRESS');
|
||||
|
||||
expect(inProgressSessions).toHaveLength(1);
|
||||
expect(inProgressSessions[0].id).toBe(session1.id);
|
||||
expect(inProgressSessions[0]!.id).toBe(session1.id);
|
||||
});
|
||||
|
||||
it('should return empty array when no sessions match state', async () => {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { describe, test, expect } from 'vitest'
|
||||
import type { Page } from 'playwright'
|
||||
import { PlaywrightAutomationAdapter } from 'packages/automation/infrastructure/adapters/automation'
|
||||
|
||||
describe('CarsFlow integration', () => {
|
||||
test('adapter emits panel-attached then action-started then action-complete for performAddCar', async () => {
|
||||
const adapter = new PlaywrightAutomationAdapter({})
|
||||
const adapter = new PlaywrightAutomationAdapter({}, undefined, undefined)
|
||||
const received: Array<{ type: string }> = []
|
||||
adapter.onLifecycle?.((e) => {
|
||||
received.push({ type: (e as { type: string }).type })
|
||||
adapter.onLifecycle?.((e: { type: string; actionId?: string; timestamp: number; payload?: any }) => {
|
||||
received.push({ type: e.type })
|
||||
})
|
||||
|
||||
|
||||
// Use mock page fixture: minimal object with required methods
|
||||
const mockPage = {
|
||||
waitForSelector: async () => {},
|
||||
@@ -16,7 +17,7 @@ describe('CarsFlow integration', () => {
|
||||
waitForTimeout: async () => {},
|
||||
click: async () => {},
|
||||
setDefaultTimeout: () => {},
|
||||
}
|
||||
} as unknown as Page
|
||||
|
||||
// call attachPanel which emits panel-attached and then action-started
|
||||
await adapter.attachPanel(mockPage, 'add-car')
|
||||
|
||||
@@ -45,6 +45,9 @@ describe('Overlay lifecycle (integration)', () => {
|
||||
info: (...args: unknown[]) => void;
|
||||
warn: (...args: unknown[]) => void;
|
||||
error: (...args: unknown[]) => void;
|
||||
fatal: (...args: unknown[]) => void;
|
||||
child: (...args: unknown[]) => LoggerLike;
|
||||
flush: (...args: unknown[]) => Promise<void>;
|
||||
};
|
||||
const logger = console as unknown as LoggerLike;
|
||||
|
||||
@@ -63,7 +66,7 @@ describe('Overlay lifecycle (integration)', () => {
|
||||
const ackPromise: Promise<ActionAck> = service.startAction(action);
|
||||
|
||||
expect(publisher.events.length).toBe(1);
|
||||
const first = publisher.events[0];
|
||||
const first = publisher.events[0]!;
|
||||
expect(first.type).toBe('modal-opened');
|
||||
expect(first.actionId).toBe('hosted-session');
|
||||
|
||||
@@ -84,8 +87,8 @@ describe('Overlay lifecycle (integration)', () => {
|
||||
expect(ack.id).toBe('hosted-session');
|
||||
expect(ack.status).toBe('confirmed');
|
||||
|
||||
expect(publisher.events[0].type).toBe('modal-opened');
|
||||
expect(publisher.events[0].actionId).toBe('hosted-session');
|
||||
expect(publisher.events[0]!.type).toBe('modal-opened');
|
||||
expect(publisher.events[0]!.actionId).toBe('hosted-session');
|
||||
});
|
||||
|
||||
it('emits panel-missing when cancelAction is called', async () => {
|
||||
@@ -96,6 +99,9 @@ describe('Overlay lifecycle (integration)', () => {
|
||||
info: (...args: unknown[]) => void;
|
||||
warn: (...args: unknown[]) => void;
|
||||
error: (...args: unknown[]) => void;
|
||||
fatal: (...args: unknown[]) => void;
|
||||
child: (...args: unknown[]) => LoggerLike;
|
||||
flush: (...args: unknown[]) => Promise<void>;
|
||||
};
|
||||
const logger = console as unknown as LoggerLike;
|
||||
|
||||
@@ -108,7 +114,7 @@ describe('Overlay lifecycle (integration)', () => {
|
||||
await service.cancelAction('hosted-session-cancel');
|
||||
|
||||
expect(publisher.events.length).toBe(1);
|
||||
const ev = publisher.events[0];
|
||||
const ev = publisher.events[0]!;
|
||||
expect(ev.type).toBe('panel-missing');
|
||||
expect(ev.actionId).toBe('hosted-session-cancel');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user