wip
This commit is contained in:
@@ -995,9 +995,10 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
if (this.isRealMode()) {
|
||||
await this.clickNewRaceInModal();
|
||||
// Ensure Race Information panel is visible by clicking sidebar nav then waiting for fallback selectors
|
||||
const raceInfoFallback = '#set-session-information, .wizard-step[id*="session"], .wizard-step[id*="race-information"]';
|
||||
const raceInfoFallback = IRACING_SELECTORS.wizard.stepContainers.raceInformation;
|
||||
const raceInfoNav = IRACING_SELECTORS.wizard.sidebarLinks.raceInformation;
|
||||
try {
|
||||
try { await this.page!.click('[data-testid="wizard-nav-set-session-information"]'); this.log('debug','Clicked wizard nav for Race Information', { selector: '[data-testid="wizard-nav-set-session-information"]' }); } catch (e) { this.log('debug','Wizard nav for Race Information not present (continuing)', { error: String(e) }); }
|
||||
try { await this.page!.click(raceInfoNav); this.log('debug','Clicked wizard nav for Race Information', { selector: raceInfoNav }); } catch (e) { this.log('debug','Wizard nav for Race Information not present (continuing)', { error: String(e) }); }
|
||||
await this.page!.waitForSelector(raceInfoFallback, { state: 'attached', timeout: 5000 });
|
||||
this.log('info','Race Information panel found', { selector: raceInfoFallback });
|
||||
} catch (err) {
|
||||
@@ -1005,7 +1006,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
const inner = await this.page!.evaluate(() => document.querySelector('#create-race-wizard')?.innerHTML || '');
|
||||
this.log('debug','create-race-wizard innerHTML (truncated)', { html: inner ? inner.substring(0,2000) : '' });
|
||||
// Retry nav click once then wait longer before failing
|
||||
try { await this.page!.click('[data-testid="wizard-nav-set-session-information"]'); } catch {}
|
||||
try { await this.page!.click(raceInfoNav); } catch {}
|
||||
await this.page!.waitForSelector(raceInfoFallback, { state: 'attached', timeout: 10000 });
|
||||
}
|
||||
}
|
||||
@@ -1096,12 +1097,13 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
// Robust: try opening Cars via sidebar nav then wait for a set of fallback selectors.
|
||||
const carsFallbackSelector = '#set-cars, #select-car-compact-content, .cars-panel, [id*="select-car"], [data-step="set-cars"]';
|
||||
const carsFallbackSelector = IRACING_SELECTORS.wizard.stepContainers.cars;
|
||||
const carsNav = IRACING_SELECTORS.wizard.sidebarLinks.cars;
|
||||
try {
|
||||
this.log('debug', 'nav-click attempted for Cars', { navSelector: '[data-testid="wizard-nav-set-cars"]' });
|
||||
this.log('debug', 'nav-click attempted for Cars', { navSelector: carsNav });
|
||||
// Attempt nav click (best-effort) - tolerate absence
|
||||
await this.page!.click('[data-testid="wizard-nav-set-cars"]').catch(() => {});
|
||||
this.log('debug', 'Primary nav-click attempted', { selector: '[data-testid="wizard-nav-set-cars"]' });
|
||||
await this.page!.click(carsNav).catch(() => {});
|
||||
this.log('debug', 'Primary nav-click attempted', { selector: carsNav });
|
||||
|
||||
try {
|
||||
this.log('debug', 'Waiting for Cars panel using primary selector', { selector: carsFallbackSelector });
|
||||
@@ -1113,7 +1115,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
this.log('debug', 'captured #create-race-wizard innerHTML (truncated)', { html: html ? html.slice(0, 2000) : '' });
|
||||
this.log('info', 'retry attempted for Cars nav-click', { attempt: 1 });
|
||||
// Retry nav click once (best-effort) then wait longer before failing
|
||||
await this.page!.click('[data-testid="wizard-nav-set-cars"]').catch(() => {});
|
||||
await this.page!.click(carsNav).catch(() => {});
|
||||
await this.page!.waitForSelector(carsFallbackSelector, { state: 'attached', timeout: 10000 });
|
||||
this.log('info', 'Cars panel found after retry', { selector: carsFallbackSelector });
|
||||
}
|
||||
@@ -1184,7 +1186,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
|
||||
// Check if we're on Track page (Step 11) instead of Cars page
|
||||
const onTrackPage = wizardFooter.includes('Track Options') ||
|
||||
await this.page!.locator('#set-track').isVisible().catch(() => false);
|
||||
await this.page!.locator(IRACING_SELECTORS.wizard.stepContainers.track).isVisible().catch(() => false);
|
||||
|
||||
if (onTrackPage) {
|
||||
const errorMsg = `FATAL: Step 9 attempted on Track page (Step 11) - navigation bug detected. Wizard footer: "${wizardFooter}"`;
|
||||
@@ -1278,7 +1280,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
this.log('info', 'Step 11: Validating page state before proceeding');
|
||||
const step11Validation = await this.validatePageState({
|
||||
expectedStep: 'track',
|
||||
requiredSelectors: ['#set-track'], // Both modes use same container ID
|
||||
requiredSelectors: [IRACING_SELECTORS.wizard.stepContainers.track], // Both modes use same container ID
|
||||
forbiddenSelectors: this.isRealMode()
|
||||
? [IRACING_SELECTORS.steps.addCarButton]
|
||||
: [] // Mock mode: no forbidden selectors needed
|
||||
@@ -1430,11 +1432,12 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
// Robust: try opening Weather via sidebar nav then wait for a set of fallback selectors.
|
||||
const weatherFallbackSelector = '#set-weather, .wizard-step[id*="weather"], .wizard-step[data-step="weather"], .weather-panel';
|
||||
const weatherFallbackSelector = IRACING_SELECTORS.wizard.stepContainers.weather;
|
||||
const weatherNav = IRACING_SELECTORS.wizard.sidebarLinks.weather;
|
||||
try {
|
||||
try {
|
||||
await this.page!.click('[data-testid="wizard-nav-set-weather"]');
|
||||
this.log('debug', 'Clicked wizard nav for Weather', { selector: '[data-testid="wizard-nav-set-weather"]' });
|
||||
await this.page!.click(weatherNav);
|
||||
this.log('debug', 'Clicked wizard nav for Weather', { selector: weatherNav });
|
||||
} catch (e) {
|
||||
this.log('debug', 'Wizard nav for Weather not present (continuing)', { error: String(e) });
|
||||
}
|
||||
@@ -1447,7 +1450,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
const inner = await this.page!.evaluate(() => document.querySelector('#create-race-wizard')?.innerHTML || '');
|
||||
this.log('debug', 'create-race-wizard innerHTML (truncated)', { html: inner ? inner.substring(0, 2000) : '' });
|
||||
// Retry nav click once then wait longer before failing
|
||||
try { await this.page!.click('[data-testid="wizard-nav-set-weather"]'); } catch {}
|
||||
try { await this.page!.click(weatherNav); } catch {}
|
||||
await this.page!.waitForSelector(weatherFallbackSelector, { state: 'attached', timeout: 10000 });
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1882,7 +1885,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
|
||||
try {
|
||||
// Check for Chakra UI modals (do NOT use this for datetime pickers - see dismissDatetimePickers)
|
||||
const modalContainer = this.page.locator('.chakra-modal__content-container');
|
||||
const modalContainer = this.page.locator('.chakra-modal__content-container, .modal-content');
|
||||
const isModalVisible = await modalContainer.isVisible().catch(() => false);
|
||||
|
||||
if (!isModalVisible) {
|
||||
@@ -1972,10 +1975,10 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
// Strategy 2: Click on the modal body outside the picker
|
||||
// This simulates clicking elsewhere to close the dropdown
|
||||
this.log('debug', `${stillOpenCount} picker(s) still open, clicking outside`);
|
||||
const modalBody = this.page.locator('.modal-body').first();
|
||||
const modalBody = this.page.locator(IRACING_SELECTORS.wizard.modalContent).first();
|
||||
if (await modalBody.isVisible().catch(() => false)) {
|
||||
// Click at a safe spot - the header area of the card
|
||||
const cardHeader = this.page.locator('#set-time-of-day .card-header').first();
|
||||
const cardHeader = this.page.locator(`${IRACING_SELECTORS.wizard.stepContainers.timeOfDay} .card-header`).first();
|
||||
if (await cardHeader.isVisible().catch(() => false)) {
|
||||
await cardHeader.click({ force: true, timeout: 1000 }).catch(() => {});
|
||||
await this.page.waitForTimeout(100);
|
||||
@@ -2411,7 +2414,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
try {
|
||||
this.log('debug', 'Waiting for Add Car modal to appear (primary selector)');
|
||||
// Wait for modal container - expanded selector list to tolerate UI variants
|
||||
const modalSelector = '#add-car-modal, #select-car-compact-content, .drawer[id*="select-car"], [id*="select-car-compact"], .select-car-modal';
|
||||
const modalSelector = IRACING_SELECTORS.steps.addCarModal;
|
||||
await this.page.waitForSelector(modalSelector, {
|
||||
state: 'attached',
|
||||
timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
|
||||
@@ -2426,7 +2429,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
this.log('debug', 'create-race-wizard innerHTML (truncated)', { html: html ? html.slice(0,2000) : '' });
|
||||
this.log('info', 'Retrying wait for Add Car modal with extended timeout');
|
||||
try {
|
||||
const modalSelectorRetry = '#add-car-modal, #select-car-compact-content, .drawer[id*="select-car"], [id*="select-car-compact"], .select-car-modal';
|
||||
const modalSelectorRetry = IRACING_SELECTORS.steps.addCarModal;
|
||||
await this.page.waitForSelector(modalSelectorRetry, {
|
||||
state: 'attached',
|
||||
timeout: 10000,
|
||||
@@ -2509,18 +2512,24 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
throw new Error('Browser not connected');
|
||||
}
|
||||
|
||||
// First try direct select button (non-dropdown)
|
||||
const directSelector = '.modal table a.btn.btn-primary.btn-xs:not(.dropdown-toggle)';
|
||||
const directButton = this.page.locator(directSelector).first();
|
||||
|
||||
if (await directButton.count() > 0 && await directButton.isVisible()) {
|
||||
await this.safeClick(directSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked direct Select button for first search result', { selector: directSelector });
|
||||
return;
|
||||
// First try direct select button (non-dropdown) - using verified selectors
|
||||
// Try both track and car select buttons as this method is shared
|
||||
const directSelectors = [
|
||||
IRACING_SELECTORS.steps.trackSelectButton,
|
||||
IRACING_SELECTORS.steps.carSelectButton
|
||||
];
|
||||
|
||||
for (const selector of directSelectors) {
|
||||
const button = this.page.locator(selector).first();
|
||||
if (await button.count() > 0 && await button.isVisible()) {
|
||||
await this.safeClick(selector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked direct Select button for first search result', { selector });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: dropdown toggle pattern
|
||||
const dropdownSelector = '.modal table a.btn.btn-primary.btn-xs.dropdown-toggle';
|
||||
// Fallback: dropdown toggle pattern (for multi-config tracks)
|
||||
const dropdownSelector = IRACING_SELECTORS.steps.trackSelectDropdown;
|
||||
const dropdownButton = this.page.locator(dropdownSelector).first();
|
||||
|
||||
if (await dropdownButton.count() > 0 && await dropdownButton.isVisible()) {
|
||||
@@ -2532,7 +2541,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
await this.page.waitForSelector('.dropdown-menu.show', { timeout: 3000 }).catch(() => {});
|
||||
|
||||
// Click first item in dropdown (first track config)
|
||||
const itemSelector = '.dropdown-menu.show .dropdown-item:first-child';
|
||||
const itemSelector = IRACING_SELECTORS.steps.trackSelectDropdownItem;
|
||||
await this.page.waitForTimeout(200);
|
||||
await this.safeClick(itemSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked first dropdown item to select track config', { selector: itemSelector });
|
||||
@@ -2707,8 +2716,7 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
// Check for authenticated UI indicators
|
||||
// Look for elements that are ONLY present when authenticated
|
||||
const authSelectors = [
|
||||
'button:has-text("Create a Race")',
|
||||
'[aria-label="Create a Race"]',
|
||||
IRACING_SELECTORS.hostedRacing.createRaceButton,
|
||||
// User menu/profile indicators (present on ALL authenticated pages)
|
||||
'[aria-label*="user menu" i]',
|
||||
'[aria-label*="account menu" i]',
|
||||
@@ -3897,6 +3905,8 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
// Check for close button click or ESC key
|
||||
if (await this.isCloseRequested()) {
|
||||
this.log('info', 'Browser close requested by user (close button or ESC key)');
|
||||
// Only close if we are not in the middle of a critical operation or if explicitly confirmed
|
||||
// For now, we'll just log and throw, but we might want to add a confirmation dialog in the future
|
||||
await this.closeBrowserContext();
|
||||
throw new Error('USER_CLOSE_REQUESTED: Browser closed by user request');
|
||||
}
|
||||
@@ -4112,12 +4122,16 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
// ESC key listener - close browser on ESC press
|
||||
// DISABLED: ESC key is often used to close modals/popups in iRacing
|
||||
// We should only close on explicit close button click
|
||||
/*
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
console.log('[GridPilot] ESC key pressed, requesting close');
|
||||
(window as unknown as { __gridpilot_close_requested?: boolean }).__gridpilot_close_requested = true;
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// Modal visibility observer - detect when wizard modal is closed
|
||||
// Look for Bootstrap modal backdrop disappearing or modal being hidden
|
||||
@@ -4129,14 +4143,18 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
// Modal backdrop removed
|
||||
if (node.classList.contains('modal-backdrop')) {
|
||||
console.log('[GridPilot] Modal backdrop removed, checking if wizard dismissed');
|
||||
// Small delay to allow for legitimate modal transitions
|
||||
// Increased delay to allow for legitimate modal transitions (e.g. step changes)
|
||||
setTimeout(() => {
|
||||
// Check if ANY wizard-related modal is visible
|
||||
const wizardModal = document.querySelector('.modal.fade.in, .modal.show');
|
||||
if (!wizardModal) {
|
||||
// Also check if we are just transitioning between steps (sometimes modal is briefly hidden)
|
||||
const wizardContent = document.querySelector('.wizard-content, .wizard-step');
|
||||
|
||||
if (!wizardModal && !wizardContent) {
|
||||
console.log('[GridPilot] Wizard modal no longer visible, requesting close');
|
||||
(window as unknown as { __gridpilot_close_requested?: boolean }).__gridpilot_close_requested = true;
|
||||
}
|
||||
}, 500);
|
||||
}, 2000); // Increased from 500ms to 2000ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user