import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { DIContainer } from '../../../..//apps/companion/main/di-container'; import type { HostedSessionConfig } from 'apps/companion/main/automation/domain/types/HostedSessionConfig'; import { StepId } from 'apps/companion/main/automation/domain/value-objects/StepId'; import { PlaywrightAutomationAdapter } from '../../../../apps/companion/main/automation/infrastructure/automation'; describe('companion start automation - browser not connected at step 1', () => { const originalEnv = { ...process.env }; let originalTestLauncher: unknown; beforeEach(() => { process.env = { ...originalEnv, NODE_ENV: 'production' }; originalTestLauncher = (PlaywrightAutomationAdapter as typeof PlaywrightAutomationAdapter & { testLauncher?: unknown; }).testLauncher; const mockLauncher = { launch: async (_opts: unknown) => ({ newContext: async () => ({ newPage: async () => ({ setDefaultTimeout: () => {}, close: async () => {} }), close: async () => {}, }), newPage: async () => ({ setDefaultTimeout: () => {}, close: async () => {} }), close: async () => {}, }), launchPersistentContext: async (_userDataDir: string, _opts: unknown) => ({ pages: () => [{ setDefaultTimeout: () => {}, close: async () => {} }], newPage: async () => ({ setDefaultTimeout: () => {}, close: async () => {} }), close: async () => {}, }), }; (PlaywrightAutomationAdapter as typeof PlaywrightAutomationAdapter & { testLauncher?: typeof mockLauncher; }).testLauncher = mockLauncher; DIContainer.resetInstance(); }); afterEach(async () => { const container = DIContainer.getInstance(); await container.shutdown(); DIContainer.resetInstance(); (PlaywrightAutomationAdapter as typeof PlaywrightAutomationAdapter & { testLauncher?: unknown; }).testLauncher = originalTestLauncher; process.env = originalEnv; }); it('marks the session as FAILED with Step 1 (LOGIN) browser-not-connected error', async () => { const container = DIContainer.getInstance(); const startAutomationUseCase = container.getStartAutomationUseCase(); const sessionRepository = container.getSessionRepository(); const automationEngine = container.getAutomationEngine(); const connectionResult = await container.initializeBrowserConnection(); expect(connectionResult.success).toBe(true); const browserAutomation = container.getBrowserAutomation(); if (typeof (browserAutomation as { disconnect?: () => Promise }).disconnect === 'function') { await (browserAutomation as { disconnect: () => Promise }).disconnect(); } const config: HostedSessionConfig = { sessionName: 'Companion integration browser-not-connected', trackId: 'test-track', carIds: ['car-1'], }; const dto = await startAutomationUseCase.execute(config); await automationEngine.executeStep(StepId.create(1), config); const session = await waitForFailedSession(sessionRepository, dto.sessionId); expect(session).toBeDefined(); expect(session!.state!.value).toBe('FAILED'); const error = session!.errorMessage as string | undefined; expect(error).toBeDefined(); expect(error).toContain('Step 1 (Navigate to Hosted Racing page)'); expect(error).toContain('Browser not connected'); }); }); async function waitForFailedSession( sessionRepository: { findById: (id: string) => Promise<{ state?: { value?: string }; errorMessage?: unknown } | null> }, sessionId: string, timeoutMs = 5000, ): Promise<{ state?: { value?: string }; errorMessage?: unknown } | null> { const start = Date.now(); let last: any = null; // eslint-disable-next-line no-constant-condition while (true) { last = await sessionRepository.findById(sessionId); if (last && last.state && last.state.value === 'FAILED') { return last; } if (Date.now() - start >= timeoutMs) { return last; } await new Promise((resolve) => setTimeout(resolve, 100)); } }