/** * Selectors for the real iRacing website (members.iracing.com) * Uses text-based and ARIA selectors since the site uses React/Chakra UI * with dynamically generated class names. * * VERIFIED against real iRacing HTML captured 2024-11-23 */ export const IRACING_SELECTORS = { // Login page login: { emailInput: '#username, input[name="username"], input[type="email"]', passwordInput: '#password, input[type="password"]', submitButton: 'button[type="submit"], button:has-text("Sign In")', }, // Hosted Racing page (Step 2) hostedRacing: { // Main "Create a Race" button on the hosted sessions page createRaceButton: 'button:has-text("Create a Race"), button[aria-label="Create a Race"]', hostedTab: 'a:has-text("Hosted")', // Modal that appears after clicking "Create a Race" createRaceModal: '#modal-children-container, .modal-content', // "New Race" button in the modal body (not footer) - two side-by-side buttons in a row newRaceButton: 'a.btn:has-text("New Race")', lastSettingsButton: 'a.btn:has-text("Last Settings")', }, // Common modal/wizard selectors - VERIFIED from real HTML wizard: { modal: '#create-race-modal-modal-content, .modal-content', modalDialog: '.modal-dialog', modalContent: '#create-race-modal-modal-content, .modal-content', modalTitle: '[data-testid="modal-title"], .modal-title', // Wizard footer buttons - these are anchor tags styled as buttons // The "Next" button shows the name of the next step (e.g., "Server Details") // In the dumps, the footer has two buttons: Previous Step (left) and Next Step (right) nextButton: '.wizard-footer a.btn:last-child', backButton: '.wizard-footer a.btn:first-child', // Modal footer actions confirmButton: '.modal-footer a.btn-success, button:has-text("Confirm"), button:has-text("OK")', cancelButton: '.modal-footer a.btn-secondary:has-text("Back"), button:has-text("Cancel")', closeButton: '[data-testid="button-close-modal"]', // Wizard sidebar navigation links - VERIFIED from dumps sidebarLinks: { raceInformation: '[data-testid="wizard-nav-set-session-information"]', serverDetails: '[data-testid="wizard-nav-set-server-details"]', admins: '[data-testid="wizard-nav-set-admins"]', timeLimit: '[data-testid="wizard-nav-set-time-limit"]', cars: '[data-testid="wizard-nav-set-cars"]', track: '[data-testid="wizard-nav-set-track"]', trackOptions: '[data-testid="wizard-nav-set-track-options"]', timeOfDay: '[data-testid="wizard-nav-set-time-of-day"]', weather: '[data-testid="wizard-nav-set-weather"]', raceOptions: '[data-testid="wizard-nav-set-race-options"]', trackConditions: '[data-testid="wizard-nav-set-track-conditions"]', }, // Wizard step containers (the visible step content) stepContainers: { raceInformation: '#set-session-information', serverDetails: '#set-server-details', admins: '#set-admins', timeLimit: '#set-time-limit', cars: '#set-cars', track: '#set-track', trackOptions: '#set-track-options', timeOfDay: '#set-time-of-day', weather: '#set-weather', raceOptions: '#set-race-options', trackConditions: '#set-track-conditions', }, }, // Form fields - based on actual iRacing DOM structure fields: { textInput: 'input.form-control, .chakra-input, input[type="text"], input[data-field], input[data-test], input[placeholder]', passwordInput: 'input[type="password"], input[maxlength="32"].form-control, input[data-field="password"], input[name="password"]', textarea: 'textarea.form-control, .chakra-textarea, textarea, textarea[data-field]', select: '.chakra-select, select.form-control, select, [data-dropdown], select[data-field]', checkbox: '.chakra-checkbox, input[type="checkbox"], .switch-checkbox, input[data-toggle], [data-toggle]', slider: '.chakra-slider, .slider, input[type="range"]', toggle: '.switch input.switch-checkbox, .toggle-switch input, input[data-toggle]', }, // Step-specific selectors - VERIFIED from real iRacing HTML structure steps: { // Step 3: Race Information - form structure inside #set-session-information // Form groups have labels followed by inputs sessionName: '#set-session-information .card-block .form-group:first-of-type input.form-control, #set-session-information [data-field="sessionName"], [data-field="sessionName"]', sessionNameAlt: '#set-session-information input.form-control[type="text"]:not([maxlength]), input[data-field="sessionName"]', password: '#set-session-information .card-block .form-group:nth-of-type(2) input.form-control, #set-session-information input[type="password"], #set-session-information input.chakra-input[type="text"]:not([name="Current page"]):not([id*="field-:rue:"]):not([id*="field-:rug:"]):not([id*="field-:ruj:"]):not([id*="field-:rl5b:"]):not([id*="field-:rktk:"]), #set-session-information [data-field="password"], [data-field="password"]', passwordAlt: '#set-session-information input.form-control[maxlength="32"], input[data-field="password"]', description: '#set-session-information .card-block .form-group:last-of-type textarea.form-control, #set-session-information textarea[data-field="description"], [data-field="description"]', descriptionAlt: '#set-session-information textarea.form-control, textarea[data-field="description"]', // League racing toggle in Step 3 leagueRacingToggle: '#set-session-information .switch-checkbox, [data-toggle="leagueRacing"]', // Step 4: Server Details region: '#set-server-details select.form-control, #set-server-details [data-dropdown="region"], #set-server-details [data-dropdown], [data-dropdown="region"]', startNow: '#set-server-details .switch-checkbox, #set-server-details input[type="checkbox"], [data-toggle="startNow"], input[data-toggle="startNow"]', // Step 5/6: Admins adminSearch: 'input[placeholder*="Search"]', adminList: '[data-list="admins"]', // Keep generic if not found in dumps, but search input is verified addAdminButton: 'a.btn:has-text("Add an Admin")', // Step 7: Time Limits - Bootstrap-slider uses hidden input[type="text"] with id containing slider name // Also targets the visible slider handle for interaction // Dumps show dynamic IDs like time-limit-slider1763726367635 practice: 'input[id*="time-limit-slider"]', // This is risky if multiple sliders share the same ID pattern. // TODO: Need better selectors for specific sliders if they exist. // For now, we'll assume the automation handles finding the right one by index or label if possible. qualify: 'input[id*="qualify"], input[id*="time-limit-slider"]', race: 'input[id*="race"], input[id*="time-limit-slider"]', // Step 8/9: Cars carSearch: 'input[placeholder*="Search"]', carList: 'table.table.table-striped', // Add Car button - triggers car selection interface in wizard sidebar addCarButton: 'a.btn:has-text("Add a Car")', // Car selection interface - CORRECTED: No separate modal, uses wizard sidebar within main modal addCarModal: '.wizard-sidebar', // Select button inside car table row - clicking this adds the car immediately (no confirm step) carSelectButton: 'a.btn:has-text("Select")', // Step 10/11/12: Track trackSearch: 'input[placeholder*="Search"]', trackList: 'table.table.table-striped', // Add Track button - triggers track selection interface in wizard sidebar addTrackButton: 'a.btn:has-text("Add a Track")', // Track selection interface - CORRECTED: No separate modal, uses wizard sidebar within main modal addTrackModal: '.wizard-sidebar', // Select button inside track table row - clicking this selects the track immediately (no confirm step) trackSelectButton: 'a.btn:has-text("Select")', // Dropdown toggle for multi-config tracks - opens a menu of track configurations trackSelectDropdown: '.wizard-sidebar table a.btn.btn-primary.btn-xs.dropdown-toggle, #set-track table a.btn.btn-primary.btn-xs.dropdown-toggle', // First item in the dropdown menu for selecting track configuration trackSelectDropdownItem: '.dropdown-menu.show .dropdown-item:first-child, .dropdown-menu-lg .dropdown-item:first-child', // Step 13: Track Options trackConfig: '#set-track-options select.form-control, #set-track-options [data-dropdown="trackConfig"]', // Step 14: Time of Day - iRacing uses datetime picker (rdt class) and Bootstrap-slider components // The datetime picker has input.form-control, sliders have hidden input[type="text"] timeOfDay: '#set-time-of-day .rdt input.form-control, #set-time-of-day input[id*="slider"], #set-time-of-day .slider input[type="text"], #set-time-of-day [data-slider="timeOfDay"]', // Step 15: Weather weatherType: '#set-weather select.form-control, #set-weather [data-dropdown="weatherType"]', // Temperature slider uses Bootstrap-slider with hidden input[type="text"] temperature: '#set-weather input[id*="slider"], #set-weather .slider input[type="text"], #set-weather [data-slider="temperature"]', // Step 16: Race Options maxDrivers: '#set-race-options input[name*="maxDrivers"], #set-race-options input[type="number"]', rollingStart: '#set-race-options .switch-checkbox[name*="rolling"], #set-race-options input[type="checkbox"]', // Step 17: Track Conditions (final step) trackState: '#set-track-conditions select.form-control, #set-track-conditions [data-dropdown="trackState"]', }, /** * DANGER ZONE - Selectors for checkout/payment buttons that should NEVER be clicked. * The automation must block any click on these selectors to prevent accidental purchases. * VERIFIED from real iRacing HTML - the checkout button has class btn-success with icon-cart */ BLOCKED_SELECTORS: { // Checkout/payment buttons - NEVER click these (verified from real HTML) checkout: 'a.btn-success:has(.icon-cart), a.btn:has-text("Check Out"), button:has-text("Check Out"), [data-testid*="checkout"]', purchase: 'button:has-text("Purchase"), a.btn:has-text("Purchase"), .chakra-button:has-text("Purchase"), button[aria-label="Purchase"]', buy: 'button:has-text("Buy"), a.btn:has-text("Buy Now"), button:has-text("Buy Now")', payment: 'button[type="submit"]:has-text("Submit Payment"), .payment-button, #checkout-button, button:has-text("Pay"), a.btn:has-text("Pay")', cart: 'a.btn:has(.icon-cart), button:has(.icon-cart), .btn-success:has(.icon-cart)', // Price labels that indicate purchase actions (e.g., "$0.50") priceAction: 'a.btn:has(.label-pill:has-text("$")), button:has(.label-pill:has-text("$")), .btn:has(.label-inverse:has-text("$"))', }, } as const; /** * Combined selector for all blocked/dangerous elements. * Use this to check if any selector targets a payment button. */ export const ALL_BLOCKED_SELECTORS = Object.values(IRACING_SELECTORS.BLOCKED_SELECTORS).join(', '); /** * Keywords that indicate a dangerous/checkout action. * Used for text-based safety checks. */ export const BLOCKED_KEYWORDS = [ 'checkout', 'check out', 'purchase', 'buy now', 'buy', 'pay', 'submit payment', 'add to cart', 'proceed to payment', ] as const; export const IRACING_URLS = { hostedSessions: 'https://members-ng.iracing.com/web/racing/hosted/browse-sessions', login: 'https://members.iracing.com/membersite/login.jsp', home: 'https://members.iracing.com', } as const; /** * Timeout values for real iRacing automation (in milliseconds) */ export const IRACING_TIMEOUTS = { navigation: 30000, elementWait: 15000, loginWait: 120000, // 2 minutes for manual login pageLoad: 20000, } as const;