wip
This commit is contained in:
@@ -63,8 +63,11 @@ export class PlaywrightAuthSessionService implements AuthenticationServicePort {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
// ===== Helpers =====
|
||||
|
||||
@@ -609,8 +609,11 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private syncSessionStateFromBrowser(): void {
|
||||
@@ -758,7 +761,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResultDTO> {
|
||||
const stepNumber = stepId.value;
|
||||
const skipFixtureNavigation =
|
||||
(config as any).__skipFixtureNavigation === true;
|
||||
(config as { __skipFixtureNavigation?: unknown }).__skipFixtureNavigation === true;
|
||||
|
||||
if (!skipFixtureNavigation) {
|
||||
if (!this.isRealMode() && this.config.baseUrl) {
|
||||
@@ -2292,9 +2295,9 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
await this.page.evaluate(({ sel, val }) => {
|
||||
const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
|
||||
if (!el) return;
|
||||
(el as any).value = val;
|
||||
(el as any).dispatchEvent(new Event('input', { bubbles: true }));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.value = val;
|
||||
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) {
|
||||
@@ -2492,11 +2495,11 @@ 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 }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
try { (el as HTMLElement).click(); } catch { /* ignore */ }
|
||||
}
|
||||
} catch {
|
||||
@@ -2997,7 +3000,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, Authenti
|
||||
* Get the source of the browser mode configuration.
|
||||
*/
|
||||
getBrowserModeSource(): 'env' | 'file' | 'default' {
|
||||
return this.browserSession.getBrowserModeSource() as any;
|
||||
return this.browserSession.getBrowserModeSource() as 'env' | 'file' | 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,8 +48,11 @@ export class PlaywrightBrowserSession {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private isRealMode(): boolean {
|
||||
@@ -122,8 +125,10 @@ export class PlaywrightBrowserSession {
|
||||
this.browserModeSource = currentConfig.source as BrowserModeSource;
|
||||
const effectiveMode = forceHeaded ? 'headed' : currentConfig.mode;
|
||||
|
||||
const adapterAny = PlaywrightAutomationAdapter as any;
|
||||
const launcher = adapterAny.testLauncher ?? chromium;
|
||||
const adapterWithLauncher = PlaywrightAutomationAdapter as typeof PlaywrightAutomationAdapter & {
|
||||
testLauncher?: typeof chromium;
|
||||
};
|
||||
const launcher = adapterWithLauncher.testLauncher ?? chromium;
|
||||
|
||||
this.log('debug', 'Effective browser mode at connect', {
|
||||
effectiveMode,
|
||||
|
||||
@@ -108,8 +108,11 @@ export class WizardStepOrchestrator {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private async waitIfPaused(): Promise<void> {
|
||||
@@ -345,7 +348,7 @@ export class WizardStepOrchestrator {
|
||||
{ selector: raceInfoFallback },
|
||||
);
|
||||
const inner = await this.page!.evaluate(() => {
|
||||
const doc = (globalThis as any).document as any;
|
||||
const doc = (globalThis as { document?: Document }).document;
|
||||
return (
|
||||
doc?.querySelector('#create-race-wizard')?.innerHTML || ''
|
||||
);
|
||||
@@ -428,32 +431,32 @@ export class WizardStepOrchestrator {
|
||||
const page = this.page;
|
||||
if (page) {
|
||||
await page.evaluate((term) => {
|
||||
const doc = (globalThis as any).document as any;
|
||||
const doc = (globalThis as { document?: Document }).document;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
const root =
|
||||
(doc.querySelector('#set-admins') as any) ?? doc.body;
|
||||
(doc.querySelector('#set-admins') as HTMLElement | null) ?? doc.body;
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
const rows = Array.from(
|
||||
(root as any).querySelectorAll(
|
||||
root.querySelectorAll<HTMLTableRowElement>(
|
||||
'tbody[data-testid="admin-display-name-list"] tr',
|
||||
),
|
||||
) as any[];
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
return;
|
||||
}
|
||||
const needle = String(term).toLowerCase();
|
||||
for (const r of rows) {
|
||||
const text = String((r as any).textContent || '').toLowerCase();
|
||||
const text = String(r.textContent || '').toLowerCase();
|
||||
if (text.includes(needle)) {
|
||||
(r as any).setAttribute('data-selected-admin', 'true');
|
||||
r.setAttribute('data-selected-admin', 'true');
|
||||
return;
|
||||
}
|
||||
}
|
||||
(rows[0] as any).setAttribute('data-selected-admin', 'true');
|
||||
rows[0]?.setAttribute('data-selected-admin', 'true');
|
||||
}, String(adminSearch));
|
||||
}
|
||||
}
|
||||
@@ -975,7 +978,7 @@ export class WizardStepOrchestrator {
|
||||
{ selector: weatherFallbackSelector },
|
||||
);
|
||||
const inner = await this.page!.evaluate(() => {
|
||||
const doc = (globalThis as any).document as any;
|
||||
const doc = (globalThis as { document?: Document }).document;
|
||||
return (
|
||||
doc?.querySelector('#create-race-wizard')?.innerHTML || ''
|
||||
);
|
||||
@@ -1130,7 +1133,7 @@ export class WizardStepOrchestrator {
|
||||
} else {
|
||||
const valueStr = String(config.trackState);
|
||||
await this.page!.evaluate((trackStateValue) => {
|
||||
const doc = (globalThis as any).document as any;
|
||||
const doc = (globalThis as { document?: Document }).document;
|
||||
if (!doc) {
|
||||
return;
|
||||
}
|
||||
@@ -1145,27 +1148,24 @@ export class WizardStepOrchestrator {
|
||||
};
|
||||
const numeric = map[trackStateValue] ?? null;
|
||||
const inputs = Array.from(
|
||||
doc.querySelectorAll(
|
||||
doc.querySelectorAll<HTMLInputElement>(
|
||||
'input[id*="starting-track-state"], input[id*="track-state"], input[data-value]',
|
||||
),
|
||||
) as any[];
|
||||
);
|
||||
if (numeric !== null && inputs.length > 0) {
|
||||
for (const inp of inputs) {
|
||||
try {
|
||||
(inp as any).value = String(numeric);
|
||||
const ds =
|
||||
(inp as any).dataset || ((inp as any).dataset = {});
|
||||
ds.value = String(numeric);
|
||||
(inp as any).setAttribute?.(
|
||||
inp.value = String(numeric);
|
||||
inp.dataset.value = String(numeric);
|
||||
inp.setAttribute(
|
||||
'data-value',
|
||||
String(numeric),
|
||||
);
|
||||
const Ev = (globalThis as any).Event;
|
||||
(inp as any).dispatchEvent?.(
|
||||
new Ev('input', { bubbles: true }),
|
||||
inp.dispatchEvent(
|
||||
new Event('input', { bubbles: true }),
|
||||
);
|
||||
(inp as any).dispatchEvent?.(
|
||||
new Ev('change', { bubbles: true }),
|
||||
inp.dispatchEvent(
|
||||
new Event('change', { bubbles: true }),
|
||||
);
|
||||
} catch {
|
||||
}
|
||||
|
||||
@@ -22,8 +22,11 @@ export class IRacingDomInteractor {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private isRealMode(): boolean {
|
||||
@@ -86,7 +89,7 @@ export class IRacingDomInteractor {
|
||||
});
|
||||
const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
|
||||
if (!el) return;
|
||||
(el as any).value = val;
|
||||
el.value = val;
|
||||
el.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}, { sel: selector, val: value });
|
||||
@@ -194,9 +197,9 @@ export class IRacingDomInteractor {
|
||||
await page.evaluate(({ sel, val }) => {
|
||||
const el = document.querySelector(sel) as HTMLInputElement | HTMLTextAreaElement | null;
|
||||
if (!el) return;
|
||||
(el as any).value = val;
|
||||
(el as any).dispatchEvent(new Event('input', { bubbles: true }));
|
||||
(el as any).dispatchEvent(new Event('change', { bubbles: true }));
|
||||
el.value = val;
|
||||
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) {
|
||||
@@ -372,8 +375,8 @@ export class IRacingDomInteractor {
|
||||
const tag = await page
|
||||
.locator(h)
|
||||
.first()
|
||||
.evaluate((el: any) =>
|
||||
String((el as any).tagName || '').toLowerCase(),
|
||||
.evaluate((el: Element) =>
|
||||
String(el.tagName || '').toLowerCase(),
|
||||
)
|
||||
.catch(() => '');
|
||||
if (tag === 'select') {
|
||||
@@ -511,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 {
|
||||
@@ -544,8 +547,8 @@ export class IRacingDomInteractor {
|
||||
if (count === 0) continue;
|
||||
|
||||
const tagName = await locator
|
||||
.evaluate((el: any) =>
|
||||
String((el as any).tagName || '').toLowerCase(),
|
||||
.evaluate((el: Element) =>
|
||||
String(el.tagName || '').toLowerCase(),
|
||||
)
|
||||
.catch(() => '');
|
||||
const type = await locator.getAttribute('type').catch(() => '');
|
||||
@@ -682,8 +685,8 @@ export class IRacingDomInteractor {
|
||||
if (count === 0) continue;
|
||||
|
||||
const tagName = await locator
|
||||
.evaluate((el: any) =>
|
||||
String((el as any).tagName || '').toLowerCase(),
|
||||
.evaluate((el: Element) =>
|
||||
String(el.tagName || '').toLowerCase(),
|
||||
)
|
||||
.catch(() => '');
|
||||
if (tagName === 'input') {
|
||||
|
||||
@@ -32,8 +32,11 @@ export class IRacingDomNavigator {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private isRealMode(): boolean {
|
||||
|
||||
@@ -15,8 +15,11 @@ export class SafeClickService {
|
||||
if (!this.logger) {
|
||||
return;
|
||||
}
|
||||
const logger: any = this.logger;
|
||||
logger[level](message, context as any);
|
||||
const logger = this.logger as Record<
|
||||
'debug' | 'info' | 'warn' | 'error',
|
||||
(msg: string, ctx?: Record<string, unknown>) => void
|
||||
>;
|
||||
logger[level](message, context);
|
||||
}
|
||||
|
||||
private isRealMode(): boolean {
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 { IAutomationLifecycleEmitter, LifecycleCallback } from '../../../IAutomationLifecycleEmitter';
|
||||
|
||||
interface MockConfig {
|
||||
simulateFailures?: boolean;
|
||||
@@ -24,9 +25,10 @@ interface StepExecutionResult {
|
||||
};
|
||||
}
|
||||
|
||||
export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
export class MockBrowserAutomationAdapter implements IBrowserAutomation, IAutomationLifecycleEmitter {
|
||||
private config: MockConfig;
|
||||
private connected: boolean = false;
|
||||
private lifecycleCallbacks: Set<LifecycleCallback> = new Set();
|
||||
|
||||
constructor(config: MockConfig = {}) {
|
||||
this.config = {
|
||||
@@ -105,6 +107,13 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
}
|
||||
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResultDTO> {
|
||||
// Emit a simple lifecycle event for tests/overlay sync
|
||||
await this.emitLifecycle({
|
||||
type: 'action-started',
|
||||
actionId: String(stepId.value),
|
||||
timestamp: Date.now(),
|
||||
payload: { config },
|
||||
});
|
||||
if (this.shouldSimulateFailure()) {
|
||||
throw new Error(`Simulated failure at step ${stepId.value}`);
|
||||
}
|
||||
@@ -154,4 +163,18 @@ export class MockBrowserAutomationAdapter implements IBrowserAutomation {
|
||||
}
|
||||
return Math.random() < (this.config.failureRate || 0.1);
|
||||
}
|
||||
|
||||
onLifecycle(cb: LifecycleCallback): void {
|
||||
this.lifecycleCallbacks.add(cb);
|
||||
}
|
||||
|
||||
offLifecycle(cb: LifecycleCallback): void {
|
||||
this.lifecycleCallbacks.delete(cb);
|
||||
}
|
||||
|
||||
private async emitLifecycle(event: Parameters<LifecycleCallback>[0]): Promise<void> {
|
||||
for (const cb of Array.from(this.lifecycleCallbacks)) {
|
||||
await cb(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user