wip
This commit is contained in:
@@ -565,56 +565,40 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a function that checks if selectors exist on the page
|
||||
const checkSelector = (selector: string): boolean => {
|
||||
// Synchronously check if selector exists (count > 0)
|
||||
// We'll need to make this sync-compatible, so we check in the validator call
|
||||
return false; // Placeholder - will be resolved in evaluate
|
||||
};
|
||||
const selectorChecks: Record<string, boolean> = {};
|
||||
|
||||
// Use page.evaluate to check all selectors at once in the browser context
|
||||
const selectorChecks = await this.page.evaluate(
|
||||
({ requiredSelectors, forbiddenSelectors }) => {
|
||||
const results: Record<string, boolean> = {};
|
||||
|
||||
// Check required selectors
|
||||
for (const selector of requiredSelectors) {
|
||||
try {
|
||||
results[selector] = document.querySelectorAll(selector).length > 0;
|
||||
} catch {
|
||||
results[selector] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check forbidden selectors
|
||||
for (const selector of forbiddenSelectors || []) {
|
||||
try {
|
||||
results[selector] = document.querySelectorAll(selector).length > 0;
|
||||
} catch {
|
||||
results[selector] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
{
|
||||
requiredSelectors: validation.requiredSelectors,
|
||||
forbiddenSelectors: validation.forbiddenSelectors || []
|
||||
for (const selector of validation.requiredSelectors) {
|
||||
try {
|
||||
const count = await this.page.locator(selector).count();
|
||||
selectorChecks[selector] = count > 0;
|
||||
} catch {
|
||||
selectorChecks[selector] = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
for (const selector of validation.forbiddenSelectors || []) {
|
||||
try {
|
||||
const count = await this.page.locator(selector).count();
|
||||
selectorChecks[selector] = count > 0;
|
||||
} catch {
|
||||
selectorChecks[selector] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create actualState function that uses the captured results
|
||||
const actualState = (selector: string): boolean => {
|
||||
return selectorChecks[selector] === true;
|
||||
};
|
||||
|
||||
// Validate using domain service
|
||||
return this.pageStateValidator.validateStateEnhanced(actualState, validation, this.isRealMode());
|
||||
return this.pageStateValidator.validateStateEnhanced(
|
||||
actualState,
|
||||
validation,
|
||||
this.isRealMode(),
|
||||
);
|
||||
} catch (error) {
|
||||
return Result.err(
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(`Page state validation failed: ${String(error)}`)
|
||||
: new Error(`Page state validation failed: ${String(error)}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -775,29 +759,53 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
|
||||
async executeStep(stepId: StepId, config: Record<string, unknown>): Promise<AutomationResult> {
|
||||
const stepNumber = stepId.value;
|
||||
|
||||
if (!this.isRealMode() && this.config.baseUrl) {
|
||||
if (stepNumber >= 2 && stepNumber <= this.totalSteps) {
|
||||
try {
|
||||
const fixture = getFixtureForStep(stepNumber);
|
||||
if (fixture) {
|
||||
const base = this.config.baseUrl.replace(/\/$/, '');
|
||||
const url = `${base}/${fixture}`;
|
||||
this.log('debug', 'Mock mode: navigating to fixture for step', {
|
||||
const skipFixtureNavigation =
|
||||
(config as any).__skipFixtureNavigation === true;
|
||||
|
||||
if (!skipFixtureNavigation) {
|
||||
if (!this.isRealMode() && this.config.baseUrl) {
|
||||
if (stepNumber >= 2 && stepNumber <= this.totalSteps) {
|
||||
try {
|
||||
const fixture = getFixtureForStep(stepNumber);
|
||||
if (fixture) {
|
||||
const base = this.config.baseUrl.replace(/\/$/, '');
|
||||
const url = `${base}/${fixture}`;
|
||||
this.log('debug', 'Mock mode: navigating to fixture for step', {
|
||||
step: stepNumber,
|
||||
url,
|
||||
});
|
||||
await this.navigator.navigateToPage(url);
|
||||
}
|
||||
} catch (error) {
|
||||
this.log('debug', 'Mock mode fixture navigation failed (non-fatal)', {
|
||||
step: stepNumber,
|
||||
url,
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (this.isRealMode() && this.config.baseUrl && !this.config.baseUrl.includes('members.iracing.com')) {
|
||||
if (stepNumber >= 2 && stepNumber <= this.totalSteps) {
|
||||
try {
|
||||
const fixture = getFixtureForStep(stepNumber);
|
||||
if (fixture) {
|
||||
const base = this.config.baseUrl.replace(/\/$/, '');
|
||||
const url = `${base}/${fixture}`;
|
||||
this.log('info', 'Fixture host (real mode): navigating to fixture for step', {
|
||||
step: stepNumber,
|
||||
url,
|
||||
});
|
||||
await this.navigator.navigateToPage(url);
|
||||
}
|
||||
} catch (error) {
|
||||
this.log('warn', 'Real-mode fixture navigation failed (non-fatal)', {
|
||||
step: stepNumber,
|
||||
error: String(error),
|
||||
});
|
||||
await this.navigator.navigateToPage(url);
|
||||
}
|
||||
} catch (error) {
|
||||
this.log('debug', 'Mock mode fixture navigation failed (non-fatal)', {
|
||||
step: stepNumber,
|
||||
error: String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return this.stepOrchestrator.executeStep(stepId, config);
|
||||
}
|
||||
|
||||
@@ -1852,8 +1860,10 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the "New Race" button in the modal that appears after clicking "Create a Race".
|
||||
* This modal asks whether to use "Last Settings" or "New Race".
|
||||
* Click the "New Race" option in the modal that appears after clicking "Create a Race".
|
||||
* Supports both:
|
||||
* - Direct "New Race" button
|
||||
* - Dropdown menu with "Last Settings" / "New Race" items (fixture HTML)
|
||||
*/
|
||||
private async clickNewRaceInModal(): Promise<void> {
|
||||
if (!this.page) {
|
||||
@@ -1863,26 +1873,58 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
try {
|
||||
this.log('info', 'Waiting for Create Race modal to appear');
|
||||
|
||||
// Wait for the modal - use 'attached' because iRacing elements may have class="hidden"
|
||||
const modalSelector = IRACING_SELECTORS.hostedRacing.createRaceModal;
|
||||
await this.page.waitForSelector(modalSelector, {
|
||||
state: 'attached',
|
||||
timeout: IRACING_TIMEOUTS.elementWait,
|
||||
});
|
||||
|
||||
this.log('info', 'Create Race modal attached, clicking New Race button');
|
||||
this.log('info', 'Create Race modal attached, resolving New Race control');
|
||||
|
||||
// Click the "New Race" button - use 'attached' for consistency
|
||||
const newRaceSelector = IRACING_SELECTORS.hostedRacing.newRaceButton;
|
||||
await this.page.waitForSelector(newRaceSelector, {
|
||||
state: 'attached',
|
||||
timeout: IRACING_TIMEOUTS.elementWait,
|
||||
});
|
||||
await this.safeClick(newRaceSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
const directSelector = IRACING_SELECTORS.hostedRacing.newRaceButton;
|
||||
const direct = this.page.locator(directSelector).first();
|
||||
const hasDirect =
|
||||
(await direct.count().catch(() => 0)) > 0 &&
|
||||
(await direct.isVisible().catch(() => false));
|
||||
|
||||
this.log('info', 'Clicked New Race button, waiting for form to load');
|
||||
if (hasDirect) {
|
||||
this.log('info', 'Clicking direct New Race button', { selector: directSelector });
|
||||
await this.safeClick(directSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
} else {
|
||||
const dropdownToggleSelector =
|
||||
'.btn-toolbar .btn-group.dropup > a.dropdown-toggle, .btn-group.dropup > a.dropdown-toggle';
|
||||
const dropdownToggle = this.page.locator(dropdownToggleSelector).first();
|
||||
const hasDropdown =
|
||||
(await dropdownToggle.count().catch(() => 0)) > 0 &&
|
||||
(await dropdownToggle.isVisible().catch(() => false));
|
||||
|
||||
// Wait a moment for the form to load
|
||||
if (!hasDropdown) {
|
||||
throw new Error(
|
||||
`Create Race modal present but no direct New Race button or dropdown toggle found (selectors: ${directSelector}, ${dropdownToggleSelector})`,
|
||||
);
|
||||
}
|
||||
|
||||
this.log('info', 'Clicking dropdown toggle to open New Race menu', {
|
||||
selector: dropdownToggleSelector,
|
||||
});
|
||||
await this.safeClick(dropdownToggleSelector, {
|
||||
timeout: IRACING_TIMEOUTS.elementWait,
|
||||
});
|
||||
|
||||
const menuSelector =
|
||||
'.dropdown-menu a.dropdown-item.text-danger:has-text("New Race"), .dropdown-menu a.dropdown-item:has-text("New Race")';
|
||||
this.log('debug', 'Waiting for New Race entry in dropdown menu', {
|
||||
selector: menuSelector,
|
||||
});
|
||||
await this.page.waitForSelector(menuSelector, {
|
||||
state: 'attached',
|
||||
timeout: IRACING_TIMEOUTS.elementWait,
|
||||
});
|
||||
await this.safeClick(menuSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked New Race dropdown item');
|
||||
}
|
||||
|
||||
this.log('info', 'Waiting for Race Information form to load after New Race selection');
|
||||
await this.page.waitForTimeout(500);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
@@ -1949,7 +1991,13 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
*/
|
||||
private async handleLogin(): Promise<AutomationResult> {
|
||||
try {
|
||||
// Check session cookies FIRST before launching browser
|
||||
if (this.config.baseUrl && !this.config.baseUrl.includes('members.iracing.com')) {
|
||||
this.log('info', 'Fixture baseUrl detected, treating session as authenticated for Step 1', {
|
||||
baseUrl: this.config.baseUrl,
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const sessionResult = await this.checkSession();
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user