wip
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { AutomationEvent } from '../../application/ports/IAutomationEventPublisher';
|
||||
import { AutomationEvent } from '@gridpilot/automation/application/ports/AutomationEventPublisherPort';
|
||||
|
||||
export type LifecycleCallback = (event: AutomationEvent) => Promise<void> | void;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Result } from '../../../shared/result/Result';
|
||||
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
|
||||
import { CheckoutInfo } from '../../../application/ports/ICheckoutService';
|
||||
import { Result } from '../../../../shared/result/Result';
|
||||
import { CheckoutPrice } from '../../../domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '../../../domain/value-objects/CheckoutState';
|
||||
import type { CheckoutInfoDTO } from '../../../application/dto/CheckoutInfoDTO';
|
||||
import { IRACING_SELECTORS } from './dom/IRacingSelectors';
|
||||
|
||||
interface Page {
|
||||
@@ -22,7 +22,7 @@ export class CheckoutPriceExtractor {
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async extractCheckoutInfo(): Promise<Result<CheckoutInfo>> {
|
||||
async extractCheckoutInfo(): Promise<Result<CheckoutInfoDTO>> {
|
||||
try {
|
||||
// Prefer the explicit pill element which contains the price
|
||||
const pillLocator = this.page.locator('.label-pill, .label-inverse');
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Page } from 'playwright';
|
||||
import { ILogger } from '../../../../application/ports/ILogger';
|
||||
import { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
|
||||
export class AuthenticationGuard {
|
||||
constructor(
|
||||
private readonly page: Page,
|
||||
private readonly logger?: ILogger
|
||||
private readonly logger?: LoggerPort
|
||||
) {}
|
||||
|
||||
async checkForLoginUI(): Promise<boolean> {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Page } from 'playwright';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
||||
import { IRACING_URLS, IRACING_SELECTORS, IRACING_TIMEOUTS } from '../dom/IRacingSelectors';
|
||||
import { AuthenticationGuard } from './AuthenticationGuard';
|
||||
|
||||
export class IRacingPlaywrightAuthFlow implements IPlaywrightAuthFlow {
|
||||
constructor(private readonly logger?: ILogger) {}
|
||||
constructor(private readonly logger?: LoggerPort) {}
|
||||
|
||||
getLoginUrl(): string {
|
||||
return IRACING_URLS.login;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as fs from 'fs';
|
||||
import type { BrowserContext, Page } from 'playwright';
|
||||
|
||||
import type { IAuthenticationService } from '../../../../application/ports/IAuthenticationService';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
|
||||
import { Result } from '../../../../shared/result/Result';
|
||||
import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
||||
import { BrowserAuthenticationState } from '../../../../domain/value-objects/BrowserAuthenticationState';
|
||||
import { Result } from '../../../../../shared/result/Result';
|
||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||
import { SessionCookieStore } from './SessionCookieStore';
|
||||
import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
|
||||
@@ -26,11 +26,11 @@ interface PlaywrightAuthSessionConfig {
|
||||
* - Cookie persistence via SessionCookieStore
|
||||
* - Exposing the IAuthenticationService port for application layer
|
||||
*/
|
||||
export class PlaywrightAuthSessionService implements IAuthenticationService {
|
||||
export class PlaywrightAuthSessionService implements AuthenticationServicePort {
|
||||
private readonly browserSession: PlaywrightBrowserSession;
|
||||
private readonly cookieStore: SessionCookieStore;
|
||||
private readonly authFlow: IPlaywrightAuthFlow;
|
||||
private readonly logger?: ILogger;
|
||||
private readonly logger?: LoggerPort;
|
||||
|
||||
private readonly navigationTimeoutMs: number;
|
||||
private readonly loginWaitTimeoutMs: number;
|
||||
@@ -41,7 +41,7 @@ export class PlaywrightAuthSessionService implements IAuthenticationService {
|
||||
browserSession: PlaywrightBrowserSession,
|
||||
cookieStore: SessionCookieStore,
|
||||
authFlow: IPlaywrightAuthFlow,
|
||||
logger?: ILogger,
|
||||
logger?: LoggerPort,
|
||||
config?: PlaywrightAuthSessionConfig,
|
||||
) {
|
||||
this.browserSession = browserSession;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
import { CookieConfiguration } from '@gridpilot/automation/domain/value-objects/CookieConfiguration';
|
||||
import { Result } from '../../../../shared/result/Result';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
||||
import { CookieConfiguration } from '../../../../domain/value-objects/CookieConfiguration';
|
||||
import { Result } from '../../../../../shared/result/Result';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
|
||||
interface Cookie {
|
||||
name: string;
|
||||
@@ -43,9 +43,9 @@ const EXPIRY_BUFFER_SECONDS = 300;
|
||||
|
||||
export class SessionCookieStore {
|
||||
private readonly storagePath: string;
|
||||
private logger?: ILogger;
|
||||
|
||||
constructor(userDataDir: string, logger?: ILogger) {
|
||||
private logger?: LoggerPort;
|
||||
|
||||
constructor(userDataDir: string, logger?: LoggerPort) {
|
||||
this.storagePath = path.join(userDataDir, 'session-state.json');
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import type { Browser, Page, BrowserContext } from 'playwright';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import { AuthenticationState } from '@gridpilot/automation/domain/value-objects/AuthenticationState';
|
||||
import { BrowserAuthenticationState } from '@gridpilot/automation/domain/value-objects/BrowserAuthenticationState';
|
||||
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/IScreenAutomation';
|
||||
import type {
|
||||
NavigationResult,
|
||||
FormFillResult,
|
||||
ClickResult,
|
||||
WaitResult,
|
||||
ModalResult,
|
||||
AutomationResult,
|
||||
} from '../../../../application/ports/AutomationResults';
|
||||
import type { IAuthenticationService } from '../../../../application/ports/IAuthenticationService';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import { Result } from '../../../../shared/result/Result';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
|
||||
import { BrowserAuthenticationState } from '../../../../domain/value-objects/BrowserAuthenticationState';
|
||||
import { CheckoutPrice } from '../../../../domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '../../../../domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '../../../../domain/value-objects/CheckoutConfirmation';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/ScreenAutomationPort';
|
||||
import type { NavigationResultDTO } from '../../../../application/dto/NavigationResultDTO';
|
||||
import type { FormFillResultDTO } from '../../../../application/dto/FormFillResultDTO';
|
||||
import type { ClickResultDTO } from '../../../../application/dto/ClickResultDTO';
|
||||
import type { WaitResultDTO } from '../../../../application/dto/WaitResultDTO';
|
||||
import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO';
|
||||
import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO';
|
||||
import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
import { Result } from '../../../../../shared/result/Result';
|
||||
import { IRACING_SELECTORS, IRACING_URLS, IRACING_TIMEOUTS, ALL_BLOCKED_SELECTORS, BLOCKED_KEYWORDS } from '../dom/IRacingSelectors';
|
||||
import { SessionCookieStore } from '../auth/SessionCookieStore';
|
||||
import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
|
||||
@@ -421,7 +419,7 @@ export interface PlaywrightConfig {
|
||||
userDataDir?: string;
|
||||
}
|
||||
|
||||
export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthenticationService {
|
||||
export class PlaywrightAutomationAdapter implements IBrowserAutomation, AuthenticationServicePort {
|
||||
private browser: Browser | null = null;
|
||||
private persistentContext: BrowserContext | null = null;
|
||||
private context: BrowserContext | null = null;
|
||||
@@ -430,7 +428,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
private browserSession: PlaywrightBrowserSession;
|
||||
private connected = false;
|
||||
private isConnecting = false;
|
||||
private logger?: ILogger;
|
||||
private logger?: LoggerPort;
|
||||
private cookieStore: SessionCookieStore;
|
||||
private authService: PlaywrightAuthSessionService;
|
||||
private overlayInjected = false;
|
||||
@@ -450,7 +448,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
private domInteractor!: IRacingDomInteractor;
|
||||
private readonly stepOrchestrator: WizardStepOrchestrator;
|
||||
|
||||
constructor(config: PlaywrightConfig = {}, logger?: ILogger, browserModeLoader?: BrowserModeConfigLoader) {
|
||||
constructor(config: PlaywrightConfig = {}, logger?: LoggerPort, browserModeLoader?: BrowserModeConfigLoader) {
|
||||
this.config = {
|
||||
headless: true,
|
||||
timeout: 10000,
|
||||
@@ -623,7 +621,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
this.connected = this.browserSession.isConnected();
|
||||
}
|
||||
|
||||
async connect(forceHeaded: boolean = false): Promise<AutomationResult> {
|
||||
async connect(forceHeaded: boolean = false): Promise<AutomationResultDTO> {
|
||||
const result = await this.browserSession.connect(forceHeaded);
|
||||
if (!result.success) {
|
||||
return { success: false, error: result.error };
|
||||
@@ -701,7 +699,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
return this.connected && this.page !== null;
|
||||
}
|
||||
|
||||
async navigateToPage(url: string): Promise<NavigationResult> {
|
||||
async navigateToPage(url: string): Promise<NavigationResultDTO> {
|
||||
const result = await this.navigator.navigateToPage(url);
|
||||
if (result.success) {
|
||||
// Reset overlay state after successful navigation (page context changed)
|
||||
@@ -710,7 +708,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
return result;
|
||||
}
|
||||
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
return this.domInteractor.fillFormField(fieldName, value);
|
||||
}
|
||||
|
||||
@@ -727,7 +725,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
return fieldMap[fieldName] || IRACING_SELECTORS.fields.textInput;
|
||||
}
|
||||
|
||||
async clickElement(target: string): Promise<ClickResult> {
|
||||
async clickElement(target: string): Promise<ClickResultDTO> {
|
||||
return this.domInteractor.clickElement(target);
|
||||
}
|
||||
|
||||
@@ -749,15 +747,15 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
return actionMap[action] || `button:has-text("${action}")`;
|
||||
}
|
||||
|
||||
async waitForElement(target: string, maxWaitMs?: number): Promise<WaitResult> {
|
||||
async waitForElement(target: string, maxWaitMs?: number): Promise<WaitResultDTO> {
|
||||
return this.navigator.waitForElement(target, maxWaitMs);
|
||||
}
|
||||
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResult> {
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResultDTO> {
|
||||
return this.domInteractor.handleModal(stepId, action);
|
||||
}
|
||||
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResult> {
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResultDTO> {
|
||||
const stepNumber = stepId.value;
|
||||
const skipFixtureNavigation =
|
||||
(config as any).__skipFixtureNavigation === true;
|
||||
@@ -1989,7 +1987,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
* First checks if user is already authenticated - if so, navigates directly to hosted sessions.
|
||||
* Otherwise navigates to login page and waits for user to complete manual login.
|
||||
*/
|
||||
private async handleLogin(): Promise<AutomationResult> {
|
||||
private async handleLogin(): Promise<AutomationResultDTO> {
|
||||
try {
|
||||
if (this.config.baseUrl && !this.config.baseUrl.includes('members.iracing.com')) {
|
||||
this.log('info', 'Fixture baseUrl detected, treating session as authenticated for Step 1', {
|
||||
@@ -2120,7 +2118,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
* Tries the primary selector first, then falls back to alternative selectors.
|
||||
* This is needed because iRacing's form structure can vary slightly.
|
||||
*/
|
||||
private async fillFieldWithFallback(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
private async fillFieldWithFallback(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
if (!this.page) {
|
||||
return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
|
||||
}
|
||||
@@ -2224,7 +2222,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
}
|
||||
|
||||
async clickAction(action: string): Promise<ClickResult> {
|
||||
async clickAction(action: string): Promise<ClickResultDTO> {
|
||||
if (!this.page) {
|
||||
return { success: false, target: action, error: 'Browser not connected' };
|
||||
}
|
||||
@@ -2253,7 +2251,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
return { success: true, target: selector };
|
||||
}
|
||||
|
||||
async fillField(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillField(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
if (!this.page) {
|
||||
return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import { BrowserModeConfigLoader, BrowserMode } from '../../../config/BrowserModeConfig';
|
||||
import { getAutomationMode } from '../../../config/AutomationConfig';
|
||||
import type { PlaywrightConfig } from './PlaywrightAutomationAdapter';
|
||||
@@ -27,7 +27,7 @@ export class PlaywrightBrowserSession {
|
||||
|
||||
constructor(
|
||||
private readonly config: Required<PlaywrightConfig>,
|
||||
private readonly logger?: ILogger,
|
||||
private readonly logger?: LoggerPort,
|
||||
browserModeLoader?: BrowserModeConfigLoader,
|
||||
) {
|
||||
const automationMode = getAutomationMode();
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { Page } from 'playwright';
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import type {
|
||||
AutomationResult,
|
||||
ClickResult,
|
||||
FormFillResult,
|
||||
} from '../../../../application/ports/AutomationResults';
|
||||
import type { IAuthenticationService } from '../../../../application/ports/IAuthenticationService';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO';
|
||||
import type { ClickResultDTO } from '../../../../application/dto/ClickResultDTO';
|
||||
import type { FormFillResultDTO } from '../../../../application/dto/FormFillResultDTO';
|
||||
import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
import { CheckoutPrice } from '../../../../domain/value-objects/CheckoutPrice';
|
||||
import { CheckoutState } from '../../../../domain/value-objects/CheckoutState';
|
||||
import { CheckoutConfirmation } from '../../../../domain/value-objects/CheckoutConfirmation';
|
||||
import type { PlaywrightConfig } from './PlaywrightAutomationAdapter';
|
||||
import { PlaywrightBrowserSession } from './PlaywrightBrowserSession';
|
||||
import { IRacingDomNavigator } from '../dom/IRacingDomNavigator';
|
||||
@@ -19,16 +17,16 @@ import { getFixtureForStep } from '../engine/FixtureServer';
|
||||
import type {
|
||||
PageStateValidation,
|
||||
PageStateValidationResult,
|
||||
} from '@gridpilot/automation/domain/services/PageStateValidator';
|
||||
import type { Result } from '../../../../shared/result/Result';
|
||||
} from '../../../../domain/services/PageStateValidator';
|
||||
import type { Result } from '../../../../../shared/result/Result';
|
||||
|
||||
interface WizardStepOrchestratorDeps {
|
||||
config: Required<PlaywrightConfig>;
|
||||
browserSession: PlaywrightBrowserSession;
|
||||
navigator: IRacingDomNavigator;
|
||||
interactor: IRacingDomInteractor;
|
||||
authService: IAuthenticationService;
|
||||
logger?: ILogger;
|
||||
authService: AuthenticationServicePort;
|
||||
logger?: LoggerPort;
|
||||
totalSteps: number;
|
||||
getCheckoutConfirmationCallback: () =>
|
||||
| ((
|
||||
@@ -56,7 +54,7 @@ interface WizardStepOrchestratorDeps {
|
||||
dismissDatetimePickers(): Promise<void>;
|
||||
};
|
||||
helpers: {
|
||||
handleLogin(): Promise<AutomationResult>;
|
||||
handleLogin(): Promise<AutomationResultDTO>;
|
||||
validatePageState(
|
||||
validation: PageStateValidation,
|
||||
): Promise<Result<PageStateValidationResult, Error>>;
|
||||
@@ -69,8 +67,8 @@ export class WizardStepOrchestrator {
|
||||
private readonly browserSession: PlaywrightBrowserSession;
|
||||
private readonly navigator: IRacingDomNavigator;
|
||||
private readonly interactor: IRacingDomInteractor;
|
||||
private readonly authService: IAuthenticationService;
|
||||
private readonly logger?: ILogger;
|
||||
private readonly authService: AuthenticationServicePort;
|
||||
private readonly logger?: LoggerPort;
|
||||
private readonly totalSteps: number;
|
||||
private readonly getCheckoutConfirmationCallbackInternal: WizardStepOrchestratorDeps['getCheckoutConfirmationCallback'];
|
||||
private readonly overlay: WizardStepOrchestratorDeps['overlay'];
|
||||
@@ -139,7 +137,7 @@ export class WizardStepOrchestrator {
|
||||
await this.guards.dismissModals();
|
||||
}
|
||||
|
||||
private async handleLogin(): Promise<AutomationResult> {
|
||||
private async handleLogin(): Promise<AutomationResultDTO> {
|
||||
return this.helpers.handleLogin();
|
||||
}
|
||||
|
||||
@@ -147,14 +145,14 @@ export class WizardStepOrchestrator {
|
||||
await this.navigator.waitForStep(stepNumber);
|
||||
}
|
||||
|
||||
private async clickAction(action: string): Promise<ClickResult> {
|
||||
private async clickAction(action: string): Promise<ClickResultDTO> {
|
||||
return this.interactor.clickAction(action);
|
||||
}
|
||||
|
||||
private async fillFieldWithFallback(
|
||||
fieldName: string,
|
||||
value: string,
|
||||
): Promise<FormFillResult> {
|
||||
): Promise<FormFillResultDTO> {
|
||||
return this.interactor.fillFieldWithFallback(fieldName, value);
|
||||
}
|
||||
|
||||
@@ -200,7 +198,7 @@ export class WizardStepOrchestrator {
|
||||
private async fillField(
|
||||
fieldName: string,
|
||||
value: string,
|
||||
): Promise<FormFillResult> {
|
||||
): Promise<FormFillResultDTO> {
|
||||
return this.interactor.fillField(fieldName, value);
|
||||
}
|
||||
|
||||
@@ -266,7 +264,7 @@ export class WizardStepOrchestrator {
|
||||
async executeStep(
|
||||
stepId: StepId,
|
||||
config: Record<string, unknown>,
|
||||
): Promise<AutomationResult> {
|
||||
): Promise<AutomationResultDTO> {
|
||||
if (!this.page) {
|
||||
return { success: false, error: 'Browser not connected' };
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { Page } from 'playwright';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import type {
|
||||
FormFillResult,
|
||||
ClickResult,
|
||||
ModalResult,
|
||||
} from '../../../../application/ports/AutomationResults';
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
import type { FormFillResultDTO } from '../../../../application/dto/FormFillResultDTO';
|
||||
import type { ClickResultDTO } from '../../../../application/dto/ClickResultDTO';
|
||||
import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
|
||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||
import { IRACING_SELECTORS, IRACING_TIMEOUTS } from './IRacingSelectors';
|
||||
@@ -17,7 +15,7 @@ export class IRacingDomInteractor {
|
||||
private readonly config: Required<PlaywrightConfig>,
|
||||
private readonly browserSession: PlaywrightBrowserSession,
|
||||
private readonly safeClickService: SafeClickService,
|
||||
private readonly logger?: ILogger,
|
||||
private readonly logger?: LoggerPort,
|
||||
) {}
|
||||
|
||||
private log(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: Record<string, unknown>): void {
|
||||
@@ -42,7 +40,7 @@ export class IRacingDomInteractor {
|
||||
|
||||
// ===== Public port-facing operations =====
|
||||
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
|
||||
@@ -104,7 +102,7 @@ export class IRacingDomInteractor {
|
||||
}
|
||||
}
|
||||
|
||||
async clickElement(target: string): Promise<ClickResult> {
|
||||
async clickElement(target: string): Promise<ClickResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, target, error: 'Browser not connected' };
|
||||
@@ -124,7 +122,7 @@ export class IRacingDomInteractor {
|
||||
}
|
||||
}
|
||||
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResult> {
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, stepId: stepId.value, action, error: 'Browser not connected' };
|
||||
@@ -156,7 +154,7 @@ export class IRacingDomInteractor {
|
||||
|
||||
// ===== Public interaction helpers used by adapter steps =====
|
||||
|
||||
async fillField(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillField(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
|
||||
@@ -208,7 +206,7 @@ export class IRacingDomInteractor {
|
||||
}
|
||||
}
|
||||
|
||||
async fillFieldWithFallback(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillFieldWithFallback(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, fieldName, valueSet: value, error: 'Browser not connected' };
|
||||
@@ -249,7 +247,7 @@ export class IRacingDomInteractor {
|
||||
}
|
||||
}
|
||||
|
||||
async clickAction(action: string): Promise<ClickResult> {
|
||||
async clickAction(action: string): Promise<ClickResultDTO> {
|
||||
const page = this.browserSession.getPage();
|
||||
if (!page) {
|
||||
return { success: false, target: action, error: 'Browser not connected' };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Page } from 'playwright';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import type { NavigationResult, WaitResult } from '../../../../application/ports/AutomationResults';
|
||||
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
|
||||
import type { NavigationResultDTO } from '../../../../application/dto/NavigationResultDTO';
|
||||
import type { WaitResultDTO } from '../../../../application/dto/WaitResultDTO';
|
||||
import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
|
||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||
import { IRACING_SELECTORS, IRACING_TIMEOUTS, IRACING_URLS } from './IRacingSelectors';
|
||||
@@ -23,7 +24,7 @@ export class IRacingDomNavigator {
|
||||
constructor(
|
||||
private readonly config: Required<PlaywrightConfig>,
|
||||
private readonly browserSession: PlaywrightBrowserSession,
|
||||
private readonly logger?: ILogger,
|
||||
private readonly logger?: LoggerPort,
|
||||
private readonly onWizardDismissed?: () => Promise<void>,
|
||||
) {}
|
||||
|
||||
@@ -43,7 +44,7 @@ export class IRacingDomNavigator {
|
||||
return this.browserSession.getPage();
|
||||
}
|
||||
|
||||
async navigateToPage(url: string): Promise<NavigationResult> {
|
||||
async navigateToPage(url: string): Promise<NavigationResultDTO> {
|
||||
const page = this.getPage();
|
||||
if (!page) {
|
||||
return { success: false, url, loadTime: 0, error: 'Browser not connected' };
|
||||
@@ -78,7 +79,7 @@ export class IRacingDomNavigator {
|
||||
}
|
||||
}
|
||||
|
||||
async waitForElement(target: string, maxWaitMs?: number): Promise<WaitResult> {
|
||||
async waitForElement(target: string, maxWaitMs?: number): Promise<WaitResultDTO> {
|
||||
const page = this.getPage();
|
||||
if (!page) {
|
||||
return { success: false, target, waitedMs: 0, found: false, error: 'Browser not connected' };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Page } from 'playwright';
|
||||
import type { ILogger } from '../../../../application/ports/ILogger';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import { IRACING_SELECTORS, BLOCKED_KEYWORDS } from './IRacingSelectors';
|
||||
import type { PlaywrightConfig } from '../core/PlaywrightAutomationAdapter';
|
||||
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
|
||||
@@ -8,7 +8,7 @@ export class SafeClickService {
|
||||
constructor(
|
||||
private readonly config: Required<PlaywrightConfig>,
|
||||
private readonly browserSession: PlaywrightBrowserSession,
|
||||
private readonly logger?: ILogger,
|
||||
private readonly logger?: LoggerPort,
|
||||
) {}
|
||||
|
||||
private log(level: 'debug' | 'info' | 'warn' | 'error', message: string, context?: Record<string, unknown>): void {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { IAutomationEngine, ValidationResult } from '../../../../application/ports/IAutomationEngine';
|
||||
import { HostedSessionConfig } from '@gridpilot/automation/domain/entities/HostedSessionConfig';
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/IScreenAutomation';
|
||||
import { ISessionRepository } from '../../../../application/ports/ISessionRepository';
|
||||
import { StepTransitionValidator } from '@gridpilot/automation/domain/services/StepTransitionValidator';
|
||||
import type { AutomationEnginePort } from '../../../../application/ports/AutomationEnginePort';
|
||||
import { HostedSessionConfig } from '../../../../domain/entities/HostedSessionConfig';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/ScreenAutomationPort';
|
||||
import type { SessionRepositoryPort } from '../../../../application/ports/SessionRepositoryPort';
|
||||
import { StepTransitionValidator } from '../../../../domain/services/StepTransitionValidator';
|
||||
|
||||
type ValidationResult = {
|
||||
isValid: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Real Automation Engine Adapter.
|
||||
@@ -22,13 +27,13 @@ import { StepTransitionValidator } from '@gridpilot/automation/domain/services/S
|
||||
* browser automation when available. See docs/ARCHITECTURE.md
|
||||
* for the updated automation strategy.
|
||||
*/
|
||||
export class AutomationEngineAdapter implements IAutomationEngine {
|
||||
export class AutomationEngineAdapter implements AutomationEnginePort {
|
||||
private isRunning = false;
|
||||
private automationPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly browserAutomation: IBrowserAutomation,
|
||||
private readonly sessionRepository: ISessionRepository
|
||||
private readonly sessionRepository: SessionRepositoryPort
|
||||
) {}
|
||||
|
||||
async validateConfiguration(config: HostedSessionConfig): Promise<ValidationResult> {
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { IAutomationEngine, ValidationResult } from '../../../../application/ports/IAutomationEngine';
|
||||
import { HostedSessionConfig } from '@gridpilot/automation/domain/entities/HostedSessionConfig';
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/IScreenAutomation';
|
||||
import { ISessionRepository } from '../../../../application/ports/ISessionRepository';
|
||||
import { StepTransitionValidator } from '@gridpilot/automation/domain/services/StepTransitionValidator';
|
||||
import type { AutomationEnginePort } from '../../../../application/ports/AutomationEnginePort';
|
||||
import { HostedSessionConfig } from '../../../../domain/entities/HostedSessionConfig';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/ScreenAutomationPort';
|
||||
import type { SessionRepositoryPort } from '../../../../application/ports/SessionRepositoryPort';
|
||||
import { StepTransitionValidator } from '../../../../domain/services/StepTransitionValidator';
|
||||
|
||||
export class MockAutomationEngineAdapter implements IAutomationEngine {
|
||||
type ValidationResult = {
|
||||
isValid: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export class MockAutomationEngineAdapter implements AutomationEnginePort {
|
||||
private isRunning = false;
|
||||
private automationPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly browserAutomation: IBrowserAutomation,
|
||||
private readonly sessionRepository: ISessionRepository
|
||||
private readonly sessionRepository: SessionRepositoryPort
|
||||
) {}
|
||||
|
||||
async validateConfiguration(config: HostedSessionConfig): Promise<ValidationResult> {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { StepId } from '@gridpilot/automation/domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/IScreenAutomation';
|
||||
import {
|
||||
NavigationResult,
|
||||
FormFillResult,
|
||||
ClickResult,
|
||||
WaitResult,
|
||||
ModalResult,
|
||||
AutomationResult,
|
||||
} from '../../../../application/ports/AutomationResults';
|
||||
import { StepId } from '../../../../domain/value-objects/StepId';
|
||||
import type { IBrowserAutomation } from '../../../../application/ports/ScreenAutomationPort';
|
||||
import type { NavigationResultDTO } from '../../../../application/dto/NavigationResultDTO';
|
||||
import type { FormFillResultDTO } from '../../../../application/dto/FormFillResultDTO';
|
||||
import type { ClickResultDTO } from '../../../../application/dto/ClickResultDTO';
|
||||
import type { WaitResultDTO } from '../../../../application/dto/WaitResultDTO';
|
||||
import type { ModalResultDTO } from '../../../../application/dto/ModalResultDTO';
|
||||
import type { AutomationResultDTO } from '../../../../application/dto/AutomationResultDTO';
|
||||
|
||||
interface MockConfig {
|
||||
simulateFailures?: boolean;
|
||||
@@ -37,7 +35,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async connect(): Promise<AutomationResult> {
|
||||
async connect(): Promise<AutomationResultDTO> {
|
||||
this.connected = true;
|
||||
return { success: true };
|
||||
}
|
||||
@@ -50,7 +48,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
async navigateToPage(url: string): Promise<NavigationResult> {
|
||||
async navigateToPage(url: string): Promise<NavigationResultDTO> {
|
||||
const delay = this.randomDelay(200, 800);
|
||||
await this.sleep(delay);
|
||||
return {
|
||||
@@ -60,7 +58,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResult> {
|
||||
async fillFormField(fieldName: string, value: string): Promise<FormFillResultDTO> {
|
||||
const delay = this.randomDelay(100, 500);
|
||||
await this.sleep(delay);
|
||||
return {
|
||||
@@ -70,7 +68,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async clickElement(selector: string): Promise<ClickResult> {
|
||||
async clickElement(selector: string): Promise<ClickResultDTO> {
|
||||
const delay = this.randomDelay(50, 300);
|
||||
await this.sleep(delay);
|
||||
return {
|
||||
@@ -79,7 +77,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async waitForElement(selector: string, maxWaitMs: number = 5000): Promise<WaitResult> {
|
||||
async waitForElement(selector: string, maxWaitMs: number = 5000): Promise<WaitResultDTO> {
|
||||
const delay = this.randomDelay(100, 1000);
|
||||
|
||||
await this.sleep(delay);
|
||||
@@ -92,7 +90,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResult> {
|
||||
async handleModal(stepId: StepId, action: string): Promise<ModalResultDTO> {
|
||||
if (!stepId.isModalStep()) {
|
||||
throw new Error(`Step ${stepId.value} is not a modal step`);
|
||||
}
|
||||
@@ -106,7 +104,7 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
};
|
||||
}
|
||||
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResult> {
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResultDTO> {
|
||||
if (this.shouldSimulateFailure()) {
|
||||
throw new Error(`Simulated failure at step ${stepId.value}`);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import { ipcMain } from 'electron';
|
||||
import { Result } from '../../../shared/result/Result';
|
||||
import type { ICheckoutConfirmationPort, CheckoutConfirmationRequest } from '../../../application/ports/ICheckoutConfirmationPort';
|
||||
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
|
||||
import { Result } from '../../../../shared/result/Result';
|
||||
import type { CheckoutConfirmationPort } from '../../../application/ports/CheckoutConfirmationPort';
|
||||
import type { CheckoutConfirmationRequestDTO } from '../../../application/dto/CheckoutConfirmationRequestDTO';
|
||||
import { CheckoutConfirmation } from '../../../domain/value-objects/CheckoutConfirmation';
|
||||
|
||||
export class ElectronCheckoutConfirmationAdapter implements ICheckoutConfirmationPort {
|
||||
export class ElectronCheckoutConfirmationAdapter implements CheckoutConfirmationPort {
|
||||
private mainWindow: BrowserWindow;
|
||||
private pendingConfirmation: {
|
||||
resolve: (confirmation: CheckoutConfirmation) => void;
|
||||
@@ -40,7 +41,7 @@ export class ElectronCheckoutConfirmationAdapter implements ICheckoutConfirmatio
|
||||
}
|
||||
|
||||
async requestCheckoutConfirmation(
|
||||
request: CheckoutConfirmationRequest
|
||||
request: CheckoutConfirmationRequestDTO
|
||||
): Promise<Result<CheckoutConfirmation>> {
|
||||
try {
|
||||
// Only allow one pending confirmation at a time
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ILogger, LogContext } from '../../../application/ports/ILogger';
|
||||
import type { LoggerPort } from '../../../application/ports/LoggerPort';
|
||||
import type { LogContext } from '../../../application/ports/LoggerContext';
|
||||
|
||||
export class NoOpLogAdapter implements ILogger {
|
||||
export class NoOpLogAdapter implements LoggerPort {
|
||||
debug(_message: string, _context?: LogContext): void {}
|
||||
|
||||
info(_message: string, _context?: LogContext): void {}
|
||||
@@ -11,7 +12,7 @@ export class NoOpLogAdapter implements ILogger {
|
||||
|
||||
fatal(_message: string, _error?: Error, _context?: LogContext): void {}
|
||||
|
||||
child(_context: LogContext): ILogger {
|
||||
child(_context: LogContext): LoggerPort {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ILogger, LogContext, LogLevel } from '../../../application/ports/ILogger';
|
||||
import type { LoggerPort } from '@gridpilot/automation/application/ports/LoggerPort';
|
||||
import type { LogContext } from '@gridpilot/automation/application/ports/LoggerContext';
|
||||
import type { LogLevel } from '@gridpilot/automation/application/ports/LoggerLogLevel';
|
||||
import { loadLoggingConfig, type LoggingEnvironmentConfig } from '../../config/LoggingConfig';
|
||||
|
||||
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
||||
@@ -18,7 +20,7 @@ const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
||||
*
|
||||
* This provides structured JSON logging to stdout with the same interface.
|
||||
*/
|
||||
export class PinoLogAdapter implements ILogger {
|
||||
export class PinoLogAdapter implements LoggerPort {
|
||||
private readonly config: LoggingEnvironmentConfig;
|
||||
private readonly baseContext: LogContext;
|
||||
private readonly levelPriority: number;
|
||||
@@ -106,7 +108,7 @@ export class PinoLogAdapter implements ILogger {
|
||||
this.log('fatal', message, context, error);
|
||||
}
|
||||
|
||||
child(context: LogContext): ILogger {
|
||||
child(context: LogContext): LoggerPort {
|
||||
return new PinoLogAdapter(this.config, { ...this.baseContext, ...context });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user