import { Given, When, Then, Before, After } from '@cucumber/cucumber'; import { expect } from 'vitest'; import { AutomationSession } from '../../../src/packages/domain/entities/AutomationSession'; import { StartAutomationSessionUseCase } from '../../../src/packages/application/use-cases/StartAutomationSessionUseCase'; import { MockBrowserAutomationAdapter } from '../../../src/infrastructure/adapters/automation/MockBrowserAutomationAdapter'; import { InMemorySessionRepository } from '../../../src/infrastructure/repositories/InMemorySessionRepository'; import { StepId } from '../../../src/packages/domain/value-objects/StepId'; interface TestContext { sessionRepository: InMemorySessionRepository; browserAutomation: MockBrowserAutomationAdapter; startAutomationUseCase: StartAutomationSessionUseCase; currentSession: AutomationSession | null; sessionConfig: any; error: Error | null; startTime: number; } Before(function (this: TestContext) { this.sessionRepository = new InMemorySessionRepository(); this.browserAutomation = new MockBrowserAutomationAdapter(); this.startAutomationUseCase = new StartAutomationSessionUseCase( {} as any, // Mock automation engine this.browserAutomation, this.sessionRepository ); this.currentSession = null; this.sessionConfig = {}; this.error = null; this.startTime = 0; }); After(function (this: TestContext) { this.currentSession = null; this.sessionConfig = {}; this.error = null; }); Given('the companion app is running', function (this: TestContext) { expect(this.browserAutomation).toBeDefined(); }); Given('I am authenticated with iRacing', function (this: TestContext) { // Mock authentication state expect(true).toBe(true); }); Given('I have a valid session configuration', function (this: TestContext) { this.sessionConfig = { sessionName: 'Test Race Session', trackId: 'spa', carIds: ['dallara-f3'], }; }); Given('I have a session configuration with:', function (this: TestContext, dataTable: any) { const rows = dataTable.rawTable.slice(1); this.sessionConfig = {}; rows.forEach(([field, value]: [string, string]) => { if (field === 'carIds') { this.sessionConfig[field] = value.split(',').map(v => v.trim()); } else { this.sessionConfig[field] = value; } }); }); Given('I have started an automation session', async function (this: TestContext) { this.sessionConfig = { sessionName: 'Test Race', trackId: 'spa', carIds: ['dallara-f3'], }; this.currentSession = AutomationSession.create(this.sessionConfig); this.currentSession.start(); await this.sessionRepository.save(this.currentSession); }); Given('the automation has reached step {int}', async function (this: TestContext, stepNumber: number) { expect(this.currentSession).toBeDefined(); for (let i = 2; i <= stepNumber; i++) { this.currentSession!.transitionToStep(StepId.create(i)); } await this.sessionRepository.update(this.currentSession!); }); Given('the automation has progressed to step {int}', async function (this: TestContext, stepNumber: number) { expect(this.currentSession).toBeDefined(); for (let i = 2; i <= stepNumber; i++) { this.currentSession!.transitionToStep(StepId.create(i)); } await this.sessionRepository.update(this.currentSession!); }); Given('the automation is at step {int}', async function (this: TestContext, stepNumber: number) { expect(this.currentSession).toBeDefined(); for (let i = 2; i <= stepNumber; i++) { this.currentSession!.transitionToStep(StepId.create(i)); } await this.sessionRepository.update(this.currentSession!); }); Given('the session is in progress', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.isInProgress()).toBe(true); }); When('I start the automation session', async function (this: TestContext) { try { const result = await this.startAutomationUseCase.execute(this.sessionConfig); this.currentSession = await this.sessionRepository.findById(result.sessionId); this.startTime = Date.now(); } catch (error) { this.error = error as Error; } }); When('I attempt to start the automation session', async function (this: TestContext) { try { const result = await this.startAutomationUseCase.execute(this.sessionConfig); this.currentSession = await this.sessionRepository.findById(result.sessionId); } catch (error) { this.error = error as Error; } }); When('the automation progresses through all {int} steps', async function (this: TestContext, stepCount: number) { expect(this.currentSession).toBeDefined(); this.currentSession!.start(); for (let i = 2; i <= stepCount; i++) { this.currentSession!.transitionToStep(StepId.create(i)); await this.browserAutomation.executeStep(StepId.create(i), this.sessionConfig); } }); When('the automation transitions to step {int}', async function (this: TestContext, stepNumber: number) { expect(this.currentSession).toBeDefined(); this.currentSession!.transitionToStep(StepId.create(stepNumber)); await this.sessionRepository.update(this.currentSession!); }); When('the {string} modal appears', async function (this: TestContext, modalName: string) { // Simulate modal appearance expect(this.currentSession).toBeDefined(); expect(this.currentSession!.isAtModalStep()).toBe(true); }); When('I pause the automation', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); this.currentSession!.pause(); await this.sessionRepository.update(this.currentSession!); }); When('I resume the automation', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); this.currentSession!.resume(); await this.sessionRepository.update(this.currentSession!); }); When('a browser automation error occurs', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); this.currentSession!.fail('Browser automation failed at step 8'); await this.sessionRepository.update(this.currentSession!); }); When('I attempt to skip directly to step {int}', function (this: TestContext, targetStep: number) { expect(this.currentSession).toBeDefined(); try { this.currentSession!.transitionToStep(StepId.create(targetStep)); } catch (error) { this.error = error as Error; } }); When('I attempt to move back to step {int}', function (this: TestContext, targetStep: number) { expect(this.currentSession).toBeDefined(); try { this.currentSession!.transitionToStep(StepId.create(targetStep)); } catch (error) { this.error = error as Error; } }); When('the automation reaches step {int}', async function (this: TestContext, stepNumber: number) { expect(this.currentSession).toBeDefined(); for (let i = 2; i <= stepNumber; i++) { this.currentSession!.transitionToStep(StepId.create(i)); } await this.sessionRepository.update(this.currentSession!); }); When('the application restarts', function (this: TestContext) { // Simulate app restart by keeping repository but clearing session reference const sessionId = this.currentSession!.id; this.currentSession = null; // Recover session this.sessionRepository.findById(sessionId).then(session => { this.currentSession = session; }); }); When('I attempt to start another automation session', async function (this: TestContext) { const newConfig = { sessionName: 'Second Race', trackId: 'monza', carIds: ['porsche-911-gt3'], }; try { await this.startAutomationUseCase.execute(newConfig); } catch (error) { this.error = error as Error; } }); When('the automation runs for {int} seconds', async function (this: TestContext, seconds: number) { expect(this.currentSession).toBeDefined(); // Simulate time passage await new Promise(resolve => setTimeout(resolve, seconds * 1000)); }); When('I query the session status', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); const retrieved = await this.sessionRepository.findById(this.currentSession!.id); this.currentSession = retrieved; }); Then('the session should be created with state {string}', function (this: TestContext, expectedState: string) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.value).toBe(expectedState); }); Then('the current step should be {int}', function (this: TestContext, expectedStep: number) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.value).toBe(expectedStep); }); Then('the current step should remain {int}', function (this: TestContext, expectedStep: number) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.value).toBe(expectedStep); }); Then('step {int} should navigate to {string}', function (this: TestContext, stepNumber: number, description: string) { // Verify step execution would happen expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should click {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should fill {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should configure {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should access {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should handle {string} modal', function (this: TestContext, stepNumber: number, modalName: string) { expect([6, 9, 12]).toContain(stepNumber); }); Then('step {int} should set {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBeGreaterThanOrEqual(1); expect(stepNumber).toBeLessThanOrEqual(18); }); Then('step {int} should reach {string}', function (this: TestContext, stepNumber: number, description: string) { expect(stepNumber).toBe(18); }); Then('the session should stop at step {int}', function (this: TestContext, expectedStep: number) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.value).toBe(expectedStep); expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true); }); Then('the session state should be {string}', function (this: TestContext, expectedState: string) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.value).toBe(expectedState); }); Then('a manual submit warning should be displayed', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.isFinalStep()).toBe(true); }); Then('the automation should detect the modal', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.isAtModalStep()).toBe(true); }); Then('the automation should wait for modal content to load', async function (this: TestContext) { // Simulate wait expect(this.currentSession).toBeDefined(); }); Then('the automation should fill admin fields', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); }); Then('the automation should close the modal', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); }); Then('the automation should transition to step {int}', async function (this: TestContext, nextStep: number) { expect(this.currentSession).toBeDefined(); this.currentSession!.transitionToStep(StepId.create(nextStep)); }); Then('the automation should select the car {string}', async function (this: TestContext, carId: string) { expect(this.sessionConfig.carIds).toContain(carId); }); Then('the automation should confirm the selection', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); }); Then('the automation should select the track {string}', async function (this: TestContext, trackId: string) { expect(this.sessionConfig.trackId).toBe(trackId); }); Then('the automation should automatically stop', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true); }); Then('no submit action should be executed', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true); }); Then('a notification should inform the user to review before submitting', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.isFinalStep()).toBe(true); }); Then('the automation should continue from step {int}', function (this: TestContext, expectedStep: number) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.value).toBe(expectedStep); }); Then('an error message should be recorded', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.errorMessage).toBeDefined(); }); Then('the session should have a completedAt timestamp', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.completedAt).toBeDefined(); }); Then('the user should be notified of the failure', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.state.isFailed()).toBe(true); }); Then('the session creation should fail', function (this: TestContext) { expect(this.error).toBeDefined(); }); Then('an error message should indicate {string}', function (this: TestContext, expectedMessage: string) { expect(this.error).toBeDefined(); expect(this.error!.message).toContain(expectedMessage); }); Then('no session should be persisted', async function (this: TestContext) { const sessions = await this.sessionRepository.findAll(); expect(sessions).toHaveLength(0); }); Then('the transition should be rejected', function (this: TestContext) { expect(this.error).toBeDefined(); }); Then('all three cars should be added via the modal', function (this: TestContext) { expect(this.sessionConfig.carIds).toHaveLength(3); }); Then('the automation should handle the modal three times', function (this: TestContext) { expect(this.sessionConfig.carIds).toHaveLength(3); }); Then('the session should be recoverable from storage', async function (this: TestContext) { expect(this.currentSession).toBeDefined(); }); Then('the session configuration should be intact', function (this: TestContext) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.config).toBeDefined(); }); Then('the second session creation should be queued or rejected', function (this: TestContext) { expect(this.error).toBeDefined(); }); Then('a warning should inform about the active session', function (this: TestContext) { expect(this.error).toBeDefined(); }); Then('the elapsed time should be approximately {int} milliseconds', function (this: TestContext, expectedMs: number) { expect(this.currentSession).toBeDefined(); const elapsed = this.currentSession!.getElapsedTime(); expect(elapsed).toBeGreaterThanOrEqual(expectedMs - 1000); expect(elapsed).toBeLessThanOrEqual(expectedMs + 1000); }); Then('the elapsed time should increase while in progress', function (this: TestContext) { expect(this.currentSession).toBeDefined(); const elapsed = this.currentSession!.getElapsedTime(); expect(elapsed).toBeGreaterThan(0); }); Then('each step should take between {int}ms and {int}ms', function (this: TestContext, minMs: number, maxMs: number) { // This would be validated during actual execution expect(minMs).toBeLessThan(maxMs); }); Then('modal steps should take longer than regular steps', function (this: TestContext) { // This would be validated during actual execution expect(true).toBe(true); }); Then('the total workflow should complete in under {int} seconds', function (this: TestContext, maxSeconds: number) { expect(this.currentSession).toBeDefined(); const elapsed = this.currentSession!.getElapsedTime(); expect(elapsed).toBeLessThan(maxSeconds * 1000); }); Then('the session should stop at step {int} without submitting', function (this: TestContext, expectedStep: number) { expect(this.currentSession).toBeDefined(); expect(this.currentSession!.currentStep.value).toBe(expectedStep); expect(this.currentSession!.state.isStoppedAtStep18()).toBe(true); });