import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { PlaywrightAutomationAdapter, FixtureServer, } from 'packages/infrastructure/adapters/automation'; import { InMemorySessionRepository } from 'packages/infrastructure/repositories/InMemorySessionRepository'; import { MockAutomationEngineAdapter } from 'packages/infrastructure/adapters/automation/engine/MockAutomationEngineAdapter'; import { MockBrowserAutomationAdapter } from 'packages/infrastructure/adapters/automation/engine/MockBrowserAutomationAdapter'; import { StartAutomationSessionUseCase } from 'packages/application/use-cases/StartAutomationSessionUseCase'; import { StepId } from 'packages/domain/value-objects/StepId'; describe('Workflow – hosted session end-to-end (fixture-backed)', () => { let server: FixtureServer; let adapter: PlaywrightAutomationAdapter; let baseUrl: string; beforeAll(async () => { server = new FixtureServer(); const info = await server.start(); baseUrl = info.url; adapter = new PlaywrightAutomationAdapter( { headless: true, timeout: 10_000, baseUrl, mode: 'mock', }, ); const connectResult = await adapter.connect(); expect(connectResult.success).toBe(true); }); afterAll(async () => { await adapter.disconnect(); await server.stop(); }); function createFixtureEngine() { const repository = new InMemorySessionRepository(); const engine = new MockAutomationEngineAdapter(adapter, repository); const useCase = new StartAutomationSessionUseCase(engine, adapter, repository); return { repository, engine, useCase }; } it('runs 1–17 from use case and stops automation at manual Track Conditions (STOPPED_AT_STEP_18)', async () => { const { repository, engine, useCase } = createFixtureEngine(); const config: any = { sessionName: 'Fixture E2E – full workflow', trackId: 'spa', carIds: ['dallara-f3'], }; const dto = await useCase.execute(config); expect(dto.state).toBe('PENDING'); expect(dto.currentStep).toBe(1); await engine.executeStep(StepId.create(1), config); const deadline = Date.now() + 60_000; let finalSession = null; // Poll repository until automation loop completes // MockAutomationEngineAdapter drives the step orchestrator internally. // Session should end in STOPPED_AT_STEP_18 after completing automated step 17. // eslint-disable-next-line no-constant-condition while (true) { const sessions = await repository.findAll(); finalSession = sessions[0] ?? null; if (finalSession && finalSession.state.isStoppedAtStep18()) { break; } if (Date.now() > deadline) { throw new Error('Timed out waiting for automation workflow to complete'); } await new Promise((resolve) => setTimeout(resolve, 250)); } expect(finalSession).not.toBeNull(); expect(finalSession!.state.isStoppedAtStep18()).toBe(true); expect(finalSession!.currentStep.value).toBe(17); expect(finalSession!.startedAt).toBeInstanceOf(Date); expect(finalSession!.completedAt).toBeInstanceOf(Date); expect(finalSession!.errorMessage).toBeUndefined(); }); it('marks session as FAILED on mid-flow automation error with diagnostics', async () => { const repository = new InMemorySessionRepository(); const failingAdapter = new MockBrowserAutomationAdapter({ simulateFailures: true, failureRate: 1.0, }); await failingAdapter.connect(); const engine = new MockAutomationEngineAdapter( failingAdapter as any, repository, ); const useCase = new StartAutomationSessionUseCase( engine, failingAdapter as any, repository, ); const config: any = { sessionName: 'Fixture E2E – failure workflow', trackId: 'spa', carIds: ['dallara-f3'], }; const dto = await useCase.execute(config); expect(dto.state).toBe('PENDING'); expect(dto.currentStep).toBe(1); await engine.executeStep(StepId.create(1), config); const deadline = Date.now() + 30_000; let finalSession = null; // Poll for failure state // eslint-disable-next-line no-constant-condition while (true) { const sessions = await repository.findAll(); finalSession = sessions[0] ?? null; if (finalSession && finalSession.state.isFailed()) { break; } if (Date.now() > deadline) { throw new Error('Timed out waiting for automation workflow to fail'); } await new Promise((resolve) => setTimeout(resolve, 200)); } await failingAdapter.disconnect(); expect(finalSession).not.toBeNull(); expect(finalSession!.state.isFailed()).toBe(true); expect(finalSession!.errorMessage).toBeDefined(); }); });