fix(automation): ensure Cars panel nav-click + resilient selectors and retry for add-car flow
This commit is contained in:
@@ -1062,25 +1062,26 @@ 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, .wizard-step[id*="cars"], .cars-panel';
|
||||
const carsFallbackSelector = '#set-cars, #select-car-compact-content, .cars-panel, [id*="select-car"], [data-step="set-cars"]';
|
||||
try {
|
||||
try {
|
||||
await this.page!.click('[data-testid="wizard-nav-set-cars"]');
|
||||
this.log('debug', 'Clicked wizard nav for Cars', { selector: '[data-testid="wizard-nav-set-cars"]' });
|
||||
} catch (e) {
|
||||
this.log('debug', 'Wizard nav for Cars not present (continuing)', { error: String(e) });
|
||||
}
|
||||
this.log('debug', 'nav-click attempted for Cars', { navSelector: '[data-testid="wizard-nav-set-cars"]' });
|
||||
// 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"]' });
|
||||
|
||||
try {
|
||||
this.log('debug', 'Waiting for Cars panel using primary selector', { selector: carsFallbackSelector });
|
||||
await this.page!.waitForSelector(carsFallbackSelector, { state: 'attached', timeout: 5000 });
|
||||
this.log('info', 'Cars panel found', { selector: carsFallbackSelector });
|
||||
} catch (err) {
|
||||
this.log('warn', 'Cars panel not found with fallback selector, dumping #create-race-wizard innerHTML', { selector: carsFallbackSelector });
|
||||
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-cars"]'); } catch {}
|
||||
this.log('warn', 'Cars panel not found with primary selector, capturing wizard HTML and retrying', { selector: carsFallbackSelector });
|
||||
const html = await this.page!.innerHTML('#create-race-wizard').catch(() => '');
|
||||
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!.waitForSelector(carsFallbackSelector, { state: 'attached', timeout: 10000 });
|
||||
this.log('info', 'Cars panel found after retry', { selector: carsFallbackSelector });
|
||||
}
|
||||
} catch (e) {
|
||||
this.log('error', 'Failed waiting for Cars panel', { error: String(e), selector: carsFallbackSelector });
|
||||
@@ -2374,19 +2375,33 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
}
|
||||
|
||||
try {
|
||||
this.log('debug', 'Waiting for Add Car modal to appear');
|
||||
// Wait for modal container - use 'attached' because iRacing wizard steps have class="hidden"
|
||||
const modalSelector = IRACING_SELECTORS.steps.addCarModal;
|
||||
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';
|
||||
await this.page.waitForSelector(modalSelector, {
|
||||
state: 'attached',
|
||||
timeout: this.isRealMode() ? IRACING_TIMEOUTS.elementWait : this.config.timeout,
|
||||
});
|
||||
// Brief pause for modal animation (reduced from 300ms)
|
||||
await this.page.waitForTimeout(150);
|
||||
this.log('info', 'Add Car modal is visible');
|
||||
this.log('info', 'Add Car modal is visible', { selector: modalSelector });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
this.log('warn', 'Add Car modal did not appear', { error: message });
|
||||
this.log('warn', 'Add Car modal not found with primary selector, dumping #create-race-wizard innerHTML and retrying', { error: message });
|
||||
const html = await this.page!.innerHTML('#create-race-wizard').catch(() => '');
|
||||
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';
|
||||
await this.page.waitForSelector(modalSelectorRetry, {
|
||||
state: 'attached',
|
||||
timeout: 10000,
|
||||
});
|
||||
await this.page.waitForTimeout(150);
|
||||
this.log('info', 'Add Car modal found after retry', { selector: modalSelectorRetry });
|
||||
} catch {
|
||||
this.log('warn', 'Add Car modal still not found after retry');
|
||||
}
|
||||
// Don't throw - modal might appear differently in real iRacing
|
||||
}
|
||||
}
|
||||
@@ -2463,35 +2478,56 @@ export class PlaywrightAutomationAdapter implements IBrowserAutomation, IAuthent
|
||||
// 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');
|
||||
this.log('info', 'Clicked direct Select button for first search result', { selector: directSelector });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Fallback: dropdown toggle pattern
|
||||
const dropdownSelector = '.modal table a.btn.btn-primary.btn-xs.dropdown-toggle';
|
||||
const dropdownButton = this.page.locator(dropdownSelector).first();
|
||||
|
||||
|
||||
if (await dropdownButton.count() > 0 && await dropdownButton.isVisible()) {
|
||||
// Click dropdown to open menu
|
||||
await this.safeClick(dropdownSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('debug', 'Clicked dropdown toggle, waiting for menu');
|
||||
|
||||
this.log('debug', 'Clicked dropdown toggle, waiting for menu', { selector: dropdownSelector });
|
||||
|
||||
// Wait for dropdown menu to appear
|
||||
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';
|
||||
await this.page.waitForTimeout(200);
|
||||
await this.safeClick(itemSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked first dropdown item to select track config');
|
||||
this.log('info', 'Clicked first dropdown item to select track config', { selector: itemSelector });
|
||||
return;
|
||||
}
|
||||
|
||||
// If neither found, throw error
|
||||
throw new Error('No Select button found in modal table');
|
||||
|
||||
// Final fallback: try tolerant car row selectors (various UI variants)
|
||||
const carRowSelector = '.car-row, .car-item, [data-testid*="car"], [id*="favorite_cars"], [id*="select-car"]';
|
||||
const carRow = this.page.locator(carRowSelector).first();
|
||||
if (await carRow.count() > 0) {
|
||||
this.log('info', 'Fallback: clicking car row/item to select', { selector: carRowSelector });
|
||||
// Click the row itself (or its first clickable descendant)
|
||||
try {
|
||||
await this.safeClick(carRowSelector, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked car row fallback selector');
|
||||
return;
|
||||
} catch (e) {
|
||||
this.log('debug', 'Car row fallback click failed, attempting to click first link inside row', { error: String(e) });
|
||||
const linkInside = this.page.locator(`${carRowSelector} a, ${carRowSelector} button`).first();
|
||||
if (await linkInside.count() > 0 && await linkInside.isVisible()) {
|
||||
await this.safeClick(`${carRowSelector} a, ${carRowSelector} button`, { timeout: IRACING_TIMEOUTS.elementWait });
|
||||
this.log('info', 'Clicked link/button inside car row fallback');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If none found, throw error
|
||||
throw new Error('No Select button found in modal table and no fallback car row found');
|
||||
}
|
||||
|
||||
// NOTE: clickCarModalConfirm() and clickTrackModalConfirm() have been removed.
|
||||
|
||||
Reference in New Issue
Block a user