458 lines
16 KiB
TypeScript
458 lines
16 KiB
TypeScript
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);
|
|
}); |