import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { DIContainer } from '../../../apps/companion/main/di-container'; import { StepId } from 'apps/companion/main/automation/domain/value-objects/StepId'; import type { HostedSessionConfig } from 'apps/companion/main/automation/domain/types/HostedSessionConfig'; import { PlaywrightAutomationAdapter } from 'core/automation/infrastructure//automation'; describe('Companion UI - hosted workflow via fixture-backed real stack', () => { let container: DIContainer; let adapter: PlaywrightAutomationAdapter; let sessionId: string; let originalEnv: string | undefined; let originalFixtureFlag: string | undefined; beforeAll(async () => { originalEnv = process.env.NODE_ENV; originalFixtureFlag = process.env.COMPANION_FIXTURE_HOSTED; Object.defineProperty(process.env, 'NODE_ENV', { value: 'test', writable: true, enumerable: true, configurable: true }); process.env.COMPANION_FIXTURE_HOSTED = '1'; DIContainer.resetInstance(); container = DIContainer.getInstance(); const connection = await container.initializeBrowserConnection(); expect(connection.success).toBe(true); const browserAutomation = container.getBrowserAutomation(); expect(browserAutomation).toBeInstanceOf(PlaywrightAutomationAdapter); adapter = browserAutomation as PlaywrightAutomationAdapter; expect(adapter.isConnected()).toBe(true); expect(adapter.getPage()).not.toBeNull(); }, 120000); afterAll(async () => { await container.shutdown(); Object.defineProperty(process.env, 'NODE_ENV', { value: originalEnv, writable: true, enumerable: true, configurable: true }); process.env.COMPANION_FIXTURE_HOSTED = originalFixtureFlag; }); async function waitForFinalSession(deadlineMs: number) { const repo = container.getSessionRepository(); const deadline = Date.now() + deadlineMs; let finalSession = null; // eslint-disable-next-line no-constant-condition while (true) { const sessions = await repo.findAll(); finalSession = sessions[0] ?? null; if (finalSession && (finalSession.state.isStoppedAtStep18() || finalSession.state.isCompleted())) { break; } if (Date.now() > deadline) { throw new Error('Timed out waiting for hosted workflow to complete via companion DI stack'); } await new Promise((resolve) => setTimeout(resolve, 250)); } return finalSession; } it( 'drives AutomationEngineAdapter via DI over fixtures and shows overlay progress', async () => { const startUseCase = container.getStartAutomationUseCase(); const repo = container.getSessionRepository(); const config: HostedSessionConfig = { sessionName: 'Companion E2E - fixture hosted workflow', serverName: 'Companion Fixture Server', password: 'companion', adminPassword: 'admin-companion', maxDrivers: 20, trackId: 'spa', carIds: ['dallara-f3'], weatherType: 'dynamic', timeOfDay: 'afternoon', sessionDuration: 60, practiceLength: 10, qualifyingLength: 10, warmupLength: 5, raceLength: 30, startType: 'standing', restarts: 'single-file', damageModel: 'realistic', trackState: 'auto' }; const dto = await startUseCase.execute(config); expect(dto.state).toBe('PENDING'); expect(dto.currentStep).toBe(1); sessionId = dto.sessionId; const session = await repo.findById(sessionId); expect(session).not.toBeNull(); expect(session!.state.isPending()).toBe(true); await adapter.navigateToPage('http://localhost:3456/'); const engine = container.getAutomationEngine(); await engine.executeStep(StepId.create(1), config); const page = adapter.getPage(); expect(page).not.toBeNull(); await page!.waitForSelector('#gridpilot-overlay', { state: 'attached', timeout: 30000 }); const startingText = await page!.textContent('#gridpilot-action'); expect(startingText ?? '').not.toEqual(''); let reachedStep7OrBeyond = false; const deadlineForProgress = Date.now() + 60000; while (Date.now() < deadlineForProgress) { const updated = await repo.findById(sessionId); if (updated && updated.currentStep.value >= 7) { reachedStep7OrBeyond = true; break; } await new Promise((resolve) => setTimeout(resolve, 250)); } expect(reachedStep7OrBeyond).toBe(true); const overlayStepText = await page!.textContent('#gridpilot-step-text'); const overlayBody = (overlayStepText ?? '').trim().toLowerCase(); expect(overlayBody.length).toBeGreaterThan(0); const finalSession = await waitForFinalSession(60000); expect(finalSession.state.isStoppedAtStep18() || finalSession.state.isCompleted()).toBe(true); expect(finalSession.errorMessage).toBeUndefined(); const progressState = finalSession.state.value; expect(['STOPPED_AT_STEP_18', 'COMPLETED']).toContain(progressState); }, 180000 ); });