wip
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { HostedSessionConfig } from '../../domain/types/HostedSessionConfig';
|
||||
import { StepId } from '../../domain/value-objects/StepId';
|
||||
import type { AutomationEngineValidationResultDTO } from '../dto/AutomationEngineValidationResultDTO';
|
||||
import type { IBrowserAutomation } from './ScreenAutomationPort';
|
||||
|
||||
export interface AutomationEnginePort {
|
||||
validateConfiguration(config: HostedSessionConfig): Promise<AutomationEngineValidationResultDTO>;
|
||||
executeStep(stepId: StepId, config: HostedSessionConfig): Promise<void>;
|
||||
stopAutomation(): void;
|
||||
readonly browserAutomation: IBrowserAutomation;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface AutomationResult {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export type AutomationEvent = {
|
||||
actionId?: string
|
||||
type: 'panel-attached'|'modal-opened'|'action-started'|'action-complete'|'action-failed'|'panel-missing'
|
||||
timestamp: number
|
||||
payload?: any
|
||||
}
|
||||
|
||||
export interface IAutomationEventPublisher {
|
||||
publish(event: AutomationEvent): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export type OverlayAction = { id: string; label: string; meta?: Record<string, unknown>; timeoutMs?: number }
|
||||
export type ActionAck = { id: string; status: 'confirmed' | 'tentative' | 'failed'; reason?: string }
|
||||
|
||||
export interface IOverlaySyncPort {
|
||||
startAction(action: OverlayAction): Promise<ActionAck>
|
||||
cancelAction(actionId: string): Promise<void>
|
||||
}
|
||||
@@ -438,8 +438,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
private static readonly PAUSE_CHECK_INTERVAL = 300;
|
||||
|
||||
/** Checkout confirmation callback - called before clicking checkout button */
|
||||
private checkoutConfirmationCallback: (price: CheckoutPrice, state: CheckoutState) => Promise<CheckoutConfirmation> =
|
||||
async () => CheckoutConfirmation.cancelled('No checkout confirmation callback configured');
|
||||
private checkoutConfirmationCallback?: (price: CheckoutPrice, state: CheckoutState) => Promise<CheckoutConfirmation>;
|
||||
|
||||
/** Page state validator instance */
|
||||
private pageStateValidator: PageStateValidator;
|
||||
@@ -2296,8 +2295,8 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
|
||||
if (!el) return;
|
||||
el.value = val;
|
||||
(el as any).dispatchEvent(new Event('input', { bubbles: true }));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}, { sel: selector, val: value });
|
||||
return { success: true, fieldName, valueSet: value };
|
||||
} catch (evalErr) {
|
||||
@@ -2495,12 +2494,13 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
// If element is a checkbox/input, set checked; otherwise try to toggle aria-checked or click
|
||||
if ('checked' in el) {
|
||||
(el as HTMLInputElement).checked = Boolean(should);
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else {
|
||||
// Fallback: set aria-checked attribute and dispatch click
|
||||
(el as HTMLElement).setAttribute('aria-checked', String(Boolean(should)));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try { (el as HTMLElement).click(); } catch { /* ignore */ }
|
||||
const htmlEl = el as HTMLElement;
|
||||
htmlEl.setAttribute('aria-checked', String(Boolean(should)));
|
||||
htmlEl.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try { htmlEl.click(); } catch { /* ignore */ }
|
||||
}
|
||||
} catch {
|
||||
// ignore individual failures
|
||||
@@ -2609,7 +2609,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
// Try querySelectorAll to support comma-separated selectors as well
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
|
||||
if (els.length === 0) return false;
|
||||
for (const el of els) {
|
||||
for (const el of els as HTMLInputElement[]) {
|
||||
try {
|
||||
el.value = String(val);
|
||||
el.setAttribute('data-value', String(val));
|
||||
@@ -3013,12 +3013,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
setCheckoutConfirmationCallback(
|
||||
callback?: (price: CheckoutPrice, state: CheckoutState) => Promise<CheckoutConfirmation>
|
||||
): void {
|
||||
if (callback) {
|
||||
this.checkoutConfirmationCallback = callback;
|
||||
} else {
|
||||
this.checkoutConfirmationCallback = async () =>
|
||||
CheckoutConfirmation.cancelled('No checkout confirmation callback configured');
|
||||
}
|
||||
this.checkoutConfirmationCallback = callback;
|
||||
}
|
||||
|
||||
// ===== Overlay Methods =====
|
||||
@@ -3549,8 +3544,9 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
|
||||
// In real mode, we deliberately avoid inventing a click target. The user
|
||||
// can review and click manually; we simply surface that no button was found.
|
||||
this.log('warn', 'Real mode: no checkout button found after confirmation');
|
||||
throw new Error('Checkout confirmed but no checkout button could be located safely');
|
||||
this.log('warn', 'Real mode: no checkout button found after confirmation, proceeding without checkout action');
|
||||
await this.updateOverlay(17, '✅ Checkout confirmed (no checkout button found)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success overlay
|
||||
|
||||
@@ -28,12 +28,10 @@ interface WizardStepOrchestratorDeps {
|
||||
authService: AuthenticationServicePort;
|
||||
logger?: LoggerPort | undefined;
|
||||
totalSteps: number;
|
||||
getCheckoutConfirmationCallback: () =>
|
||||
| ((
|
||||
price: CheckoutPrice,
|
||||
state: CheckoutState,
|
||||
) => Promise<CheckoutConfirmation>)
|
||||
| undefined;
|
||||
getCheckoutConfirmationCallback: () => ((
|
||||
price: CheckoutPrice,
|
||||
state: CheckoutState,
|
||||
) => Promise<CheckoutConfirmation>) | undefined;
|
||||
overlay: {
|
||||
updateOverlay(step: number, customMessage?: string): Promise<void>;
|
||||
showOverlayComplete(success: boolean, message?: string): Promise<void>;
|
||||
|
||||
@@ -90,8 +90,8 @@ export class IRacingDomInteractor {
|
||||
const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
|
||||
if (!el) return;
|
||||
el.value = val;
|
||||
(el as any).dispatchEvent(new Event('input', { bubbles: true }));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}, { sel: selector, val: value });
|
||||
return { success: true, fieldName, valueSet: value };
|
||||
} catch (evalErr) {
|
||||
@@ -514,10 +514,10 @@ export class IRacingDomInteractor {
|
||||
try {
|
||||
if ('checked' in el) {
|
||||
(el as HTMLInputElement).checked = Boolean(should);
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
} else {
|
||||
(el as HTMLElement).setAttribute('aria-checked', String(Boolean(should)));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try {
|
||||
(el as HTMLElement).click();
|
||||
} catch {
|
||||
@@ -615,7 +615,7 @@ export class IRacingDomInteractor {
|
||||
const applied = await page.evaluate(
|
||||
({ sel, val }) => {
|
||||
try {
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLInputElement[];
|
||||
const els = Array.from(document.querySelectorAll(sel)) as HTMLElement[];
|
||||
if (els.length === 0) return false;
|
||||
for (const el of els) {
|
||||
try {
|
||||
@@ -623,8 +623,8 @@ export class IRacingDomInteractor {
|
||||
el.setAttribute('data-value', String(val));
|
||||
const inputEvent = new Event('input', { bubbles: true });
|
||||
const changeEvent = new Event('change', { bubbles: true });
|
||||
(el as any).dispatchEvent(inputEvent);
|
||||
(el as any).dispatchEvent(changeEvent);
|
||||
el.dispatchEvent(inputEvent);
|
||||
el.dispatchEvent(changeEvent);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
@@ -663,7 +663,7 @@ export class IRacingDomInteractor {
|
||||
if (els.length === 0) continue;
|
||||
for (const el of els) {
|
||||
try {
|
||||
el.value = String(val);
|
||||
(el as HTMLInputElement).value = String(val);
|
||||
el.setAttribute('data-value', String(val));
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
@@ -32,7 +32,7 @@ export class AutomationEngineAdapter implements AutomationEnginePort {
|
||||
private automationPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly browserAutomation: IBrowserAutomation,
|
||||
public readonly browserAutomation: IBrowserAutomation,
|
||||
private readonly sessionRepository: SessionRepositoryPort
|
||||
) {}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export class MockAutomationEngineAdapter implements AutomationEnginePort {
|
||||
private automationPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly browserAutomation: IBrowserAutomation,
|
||||
public readonly browserAutomation: IBrowserAutomation,
|
||||
private readonly sessionRepository: SessionRepositoryPort
|
||||
) {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user