/** * 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: '[aria-label*="Hosted" i], [role="tab"]:has-text("Hosted")', // Modal that appears after clicking "Create a Race" createRaceModal: '#confirm-create-race-modal, .modal:has-text("Create a Race")', // "New Race" button in the modal body (not footer) - two side-by-side buttons in a row // Verified from real iRacing HTML: buttons are in modal-body newRaceButton: '#confirm-create-race-modal .modal-body a.btn:has-text("New Race"), #confirm-create-race-modal a.btn:has(.icon-wand)', lastSettingsButton: '#confirm-create-race-modal .modal-body a.btn:has-text("Last Settings"), #confirm-create-race-modal a.btn:has(.icon-servers)', }, // Common modal/wizard selectors - VERIFIED from real HTML wizard: { modal: '#create-race-modal, [role="dialog"], .modal.fade.in', modalDialog: '#create-race-modal-modal-dialog, .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") nextButton: '.wizard-footer a.btn:not(.disabled):has(.icon-caret-right)', backButton: '.wizard-footer a.btn:has(.icon-caret-left):has-text("Back")', // 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: '.modal-header a.close, [aria-label="Close"]', // Wizard sidebar navigation links - VERIFIED IDs from real HTML sidebarLinks: { raceInformation: '#wizard-sidebar-link-set-session-information', serverDetails: '#wizard-sidebar-link-set-server-details', admins: '#wizard-sidebar-link-set-admins', timeLimit: '#wizard-sidebar-link-set-time-limit', cars: '#wizard-sidebar-link-set-cars', track: '#wizard-sidebar-link-set-track', trackOptions: '#wizard-sidebar-link-set-track-options', timeOfDay: '#wizard-sidebar-link-set-time-of-day', weather: '#wizard-sidebar-link-set-weather', raceOptions: '#wizard-sidebar-link-set-race-options', trackConditions: '#wizard-sidebar-link-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"]', passwordInput: 'input[type="password"], input[maxlength="32"].form-control', textarea: 'textarea.form-control, .chakra-textarea, textarea', select: '.chakra-select, select.form-control, select', checkbox: '.chakra-checkbox, input[type="checkbox"], .switch-checkbox', slider: '.chakra-slider, input[type="range"]', toggle: '.switch input.switch-checkbox, .toggle-switch input', }, // 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', sessionNameAlt: '#set-session-information input.form-control[type="text"]:not([maxlength])', password: '#set-session-information .card-block .form-group:nth-of-type(2) input.form-control', passwordAlt: '#set-session-information input.form-control[maxlength="32"]', description: '#set-session-information .card-block .form-group:last-of-type textarea.form-control', descriptionAlt: '#set-session-information textarea.form-control', // League racing toggle in Step 3 leagueRacingToggle: '#set-session-information .switch-checkbox', // Step 4: Server Details region: '#set-server-details select.form-control, #set-server-details [data-dropdown="region"]', startNow: '#set-server-details .switch-checkbox, #set-server-details input[type="checkbox"]', // Step 5/6: Admins adminSearch: '.wizard-sidebar input[placeholder*="Search"], #set-admins input[placeholder*="Search"]', adminList: '#set-admins [data-list="admins"]', // Step 7: Time Limits - Bootstrap-slider uses hidden input[type="text"] with id containing slider name // Also targets the visible slider handle for interaction practice: '#set-time-limit input[id*="practice"], #set-time-limit .slider input[type="text"], #set-time-limit [data-slider="practice"]', qualify: '#set-time-limit input[id*="qualify"], #set-time-limit .slider input[type="text"], #set-time-limit [data-slider="qualify"]', race: '#set-time-limit input[id*="race"], #set-time-limit .slider input[type="text"], #set-time-limit [data-slider="race"]', // Step 8/9: Cars carSearch: '.wizard-sidebar input[placeholder*="Search"], #set-cars input[placeholder*="Search"], .modal input[placeholder*="Search"]', carList: '#set-cars [data-list="cars"]', // Add Car button - triggers the Add Car modal addCarButton: '#set-cars a.btn:has(.icon-plus), #set-cars button:has-text("Add"), #set-cars a.btn:has-text("Add")', // Add Car modal - appears after clicking Add Car button addCarModal: '#add-car-modal, .modal:has(input[placeholder*="Search"]):has-text("Car")', // Select button inside Add Car modal table row - clicking this adds the car immediately (no confirm step) // The "Select" button is an anchor styled as: a.btn.btn-block.btn-primary.btn-xs carSelectButton: '.modal table .btn-primary:has-text("Select"), .modal .btn-primary.btn-xs:has-text("Select"), .modal tbody .btn-primary', // Step 10/11/12: Track trackSearch: '.wizard-sidebar input[placeholder*="Search"], #set-track input[placeholder*="Search"], .modal input[placeholder*="Search"]', trackList: '#set-track [data-list="tracks"]', // Add Track button - triggers the Add Track modal addTrackButton: '#set-track a.btn:has(.icon-plus), #set-track button:has-text("Add"), #set-track a.btn:has-text("Add"), #set-track button:has-text("Select"), #set-track a.btn:has-text("Select")', // Add Track modal - appears after clicking Add Track button addTrackModal: '#add-track-modal, .modal:has(input[placeholder*="Search"]):has-text("Track")', // Select button inside Add Track modal table row - clicking this selects the track immediately (no confirm step) // Prefer direct buttons (not dropdown toggles) for single-config tracks trackSelectButton: '.modal table a.btn.btn-primary.btn-xs:not(.dropdown-toggle)', // Dropdown toggle for multi-config tracks - opens a menu of track configurations trackSelectDropdown: '.modal 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;