# iRacing Selectors Update Plan **Date:** 2025-11-27 **Based on:** HTML dumps from `html-dumps-optimized/iracing-hosted-sessions/` (01-18) vs [`IRacingSelectors.ts`](packages/infrastructure/adapters/automation/IRacingSelectors.ts). **Goal:** Verify selectors against recent dumps, propose updates for stability (React/Chakra UI resilience), prioritize fixes. ## Clean Architecture Impact Selectors adhere to Clean Arch by relying on stable attributes (text, aria-label, data-testid, IDs like #set-*) rather than volatile classes. Updates reinforce this: prefer `:has-text()`, `data-testid`, label proximity over class names. No cross-layer leaks; selectors are pure infrastructure adapters. ## Priority Summary | Priority | Count | Examples | |----------|-------|----------| | **Critical** (broken) | 2 | `adminList` (no [data-list="admins"]), generic sliders (risky ID match) | | **Recommended** (stability) | 8 | Time sliders (add label context), fields (add chakra-), unconfirmed fields (label-for/placeholder) | | **Optional** (enhancements) | 5 | Add Car/Track buttons (dynamic count handling), BLOCKED_SELECTORS (chakra-button) | | **Verified/Matches** | 70+ | Wizard nav/step IDs, most buttons/text | **Total selectors needing updates: 15** ## Selector Verification Tables ### login | Selector | Current Selector | Status | Evidence (Dump) | Proposed | Priority | |----------|------------------|--------|-----------------|----------|----------| | emailInput | `#username, input[name="username"], input[type="email"]` | Unconfirmed | No login dump | N/A | - | | passwordInput | `#password, input[type="password"]` | Unconfirmed | No login dump | N/A | - | | submitButton | `button[type="submit"], button:has-text("Sign In")` | Unconfirmed | No login dump | N/A | - | ### hostedRacing | Selector | Current Selector | Status | Evidence (Dump) | Proposed | Priority | |----------|------------------|--------|-----------------|----------|----------| | createRaceButton | `button:has-text("Create a Race"), button[aria-label="Create a Race"]` | Matches | 01-hosted-racing.json: `bu.chakra-button:0 t:"Create a Race"` | N/A | Verified | | hostedTab | `a:has-text("Hosted")` | Matches | 01: sidebar `a.c0:2 t:"Hosted"` | N/A | Verified | | createRaceModal | `#modal-children-container, .modal-content` | Matches | 02: `#confirm-create-race-modal-modal-content` | N/A | Verified | | newRaceButton | `a.btn:has-text("New Race")` | Matches | 02: `a.btn.btn-lg:1 t:"New Race"` | N/A | Verified | | lastSettingsButton | `a.btn:has-text("Last Settings")` | Matches | 02: `a.btn.btn-lg:0 t:"Last Settings"` | N/A | Verified | ### wizard #### Core | Selector | Current Selector | Status | Evidence | Proposed | Priority | |----------|------------------|--------|-----------|----------|----------| | modal | `#create-race-modal-modal-content, .modal-content` | Matches | All dumps: `#create-race-modal-modal-content` | N/A | Verified | | modalDialog | `.modal-dialog` | Matches | Dumps: `#create-race-modal-modal-dialog` | N/A | Verified | | modalContent | `#create-race-modal-modal-content, .modal-content` | Matches | Dumps | N/A | Verified | | modalTitle | `[data-testid="modal-title"], .modal-title` | Unconfirmed | No exact match | `[data-testid="modal-title"]` | Optional | | nextButton | `.wizard-footer a.btn:last-child` | Matches | 03,05,07: `d.wizard-footer@4>d.pull-xs-left>a.btn.btn-sm:1` (dynamic text) | N/A | Verified | | backButton | `.wizard-footer a.btn:first-child` | Matches | Dumps: first-child | N/A | Verified | | confirmButton | `.modal-footer a.btn-success, button:has-text("Confirm")` | Unconfirmed | No final confirm dump | N/A | - | | cancelButton | `.modal-footer a.btn-secondary:has-text("Back")` | Matches | Dumps: "Back" | N/A | Verified | | closeButton | `[data-testid="button-close-modal"]` | Matches | Dumps: `data-testid=button-close-modal` | N/A | Verified | #### sidebarLinks (all Matches - data-testid exact) | Selector | Status | Evidence | |----------|--------|----------| | raceInformation | Matches | 03+: `data-testid=wizard-nav-set-session-information` | | ... (all 11) | Matches | Exact data-testid in 03,05,07,08 | #### stepContainers (all Matches - #set-* IDs) | Selector | Status | Evidence | |----------|--------|----------| | raceInformation (#set-session-information) | Matches | 03 | | admins (#set-admins) | Matches | 05 | | timeLimit (#set-time-limit) | Matches | 07 | | cars (#set-cars) | Matches | 08 | | ... (all 11) | Matches | Dumps | ### fields (Recommended: Add chakra- for stability) | Selector | Current | Status | Evidence | Proposed | Priority | |----------|---------|--------|----------|----------|----------| | textInput | `input.form-control, .chakra-input, ...` | Matches | Chakra inputs in dumps | `.chakra-input, input[placeholder], input[type="text"]` | Recommended | | ... (similar for others) | Partial | Chakra dominant | Add chakra- prefixes | Recommended | ### steps (Key issues highlighted) | Selector | Current | Status | Evidence (Dump) | Proposed | Priority | |----------|---------|--------|-----------------|----------|----------| | sessionName | `#set-session-information .card-block .form-group:first-of-type input.form-control, ...` | Unconfirmed | 03: form-groups, chakra-input | `label:has-text("Session Name") ~ input.chakra-input` | Recommended | | password | Complex | Unconfirmed | 03 | `label:has-text("Password") ~ input[type="password"], input[placeholder*="Password"]` | Recommended | | adminList | `[data-list="admins"]` | No Match | 05: no data-list; #set-admins card | `#set-admins table.table.table-striped, #set-admins .card-block table` | Critical | | practice | `input[id*="time-limit-slider"]` | Matches but risky | 07: `time-limit-slider1764248520320` | `label:has-text("Practice") ~ div input[id*="time-limit-slider"]` | Recommended | | qualify/race | Similar | Matches risky | 07 | Label proximity | Recommended | | addCarButton | `a.btn:has-text("Add a Car")` | Matches | 08: `a.btn.btn-sm t:"Add a Car 16 Available"` | `a.btn:has-text("Add a Car")` (handles dynamic) | Verified | | carList | `table.table.table-striped` | Matches | 08: many `table.table.table-striped` | `#set-cars table.table.table-striped` | Verified | | ... (track similar) | Matches | 08+ | N/A | Verified | ### BLOCKED_SELECTORS (Optional: Chakra enhancements) | Selector | Status | Proposed | Priority | |----------|--------|----------|----------| | checkout | Matches | Add `.chakra-button:has-text("Check Out")` | Optional | | ... | Matches | Minor | Optional | ## BDD Scenarios for Verification - GIVEN hosted page (01), THEN `hostedRacing.createRaceButton` finds 1 button. - GIVEN #set-admins (05), THEN `steps.adminList` finds 1 table; `addAdminButton` finds 1. - GIVEN time-limits (07), THEN `steps.practice` finds 1 slider near "Practice" label. - GIVEN cars (08), THEN `carList` finds table; `addCarButton:has-text("Add a Car")` finds 1. - GIVEN any step, THEN `wizard.nextButton:last-child` enabled, finds 1. **Run via Playwright: `expect(page.locator(selector)).toHaveCount(1)` per scenario.** ## Docker E2E Impacts No major changes; selectors stable. Minor fixture updates if sliders refined (update E2ETestBrowserLauncher.ts expectations). Test post-update. ## Implementation Roadmap (for Code mode) 1. Apply Critical/Recommended updates via apply_diff. 2. Verify with browser_action on local iRacing mock/fixture. 3. Add BDD tests in tests/.