Files
gridpilot.gg/tests/e2e/companion/companion-ui-full-workflow.e2e.test.ts
2025-12-16 11:09:13 +01:00

147 lines
5.1 KiB
TypeScript

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { DIContainer } from '../../../apps/companion/main/di-container';
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
import type { HostedSessionConfig } from '@gridpilot/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
);
});