# Mock HTML Fixtures Design Document ## Overview This document specifies simplified mock HTML fixtures with explicit test attributes for browser automation testing. These fixtures replace the current full-page iRacing dumps with lightweight, testable HTML pages that simulate the iRacing hosted session wizard. ## Purpose **Test fixtures for E2E testing** - Simplified HTML pages served by FixtureServer that simulate iRacing's wizard for testing the PlaywrightAutomationAdapter in isolation, without needing access to the real iRacing website. ## Problem Statement Current fixtures in `resources/iracing-hosted-sessions/`: - Full page dumps (~2.4M tokens per file) - React/Chakra UI with obfuscated CSS classes (`css-451i2c`, etc.) - No stable `data-testid` or `data-automation` attributes - Unsuitable for reliable CSS selector-based automation ## Solution: Simplified Mock Fixtures ### Design Principles 1. **Explicit Test Attributes**: Every interactive element has stable `data-*` attributes 2. **Minimal HTML**: Only essential structure, no framework artifacts 3. **Self-Contained**: Each fixture includes all CSS needed for visual verification 4. **Navigation-Aware**: Buttons link to appropriate next/previous fixtures 5. **Form Fields Match Domain**: Field names align with `HostedSessionConfig` entity --- ## Attribute Schema ### Core Attributes | Attribute | Purpose | Example Values | |-----------|---------|----------------| | `data-step` | Step identification | `2` through `18` | | `data-action` | Navigation/action buttons | `next`, `back`, `confirm`, `cancel`, `create`, `add`, `select` | | `data-field` | Form input fields | `sessionName`, `password`, `description`, `region`, etc. | | `data-modal` | Modal container flag | `true` | | `data-modal-trigger` | Button that opens a modal | `admin`, `car`, `track` | | `data-list` | List container | `admins`, `cars`, `tracks` | | `data-item` | Selectable list item | Car/track/admin ID | | `data-toggle` | Toggle/checkbox element | `startNow`, `teamDriving`, `rollingStart` | | `data-dropdown` | Dropdown select | `region`, `weather`, `trackState`, `carClass` | | `data-slider` | Slider input | `time`, `temperature`, `practice`, `qualify`, `race` | | `data-indicator` | Step indicator | `race-info`, `server-details`, etc. | ### Navigation Attribute Values | Value | Description | Usage | |-------|-------------|-------| | `next` | Proceed to next step | All non-final steps | | `back` | Return to previous step | Steps 3-18 | | `confirm` | Confirm modal action | Modal steps (6, 9, 12) | | `cancel` | Cancel/close modal | Modal steps | | `create` | Create new race | Step 2 | | `add` | Open add modal | Steps 5, 8, 11 | | `select` | Select item from list | Modal list items | --- ## Step-by-Step Fixture Specifications ### Step 1: Login - Handled Externally > Note: Login is handled externally. No fixture needed. ### Step 2: Hosted Racing - Main Page **Purpose**: Landing page with Create a Race button **Elements**: ``` data-step="2" data-indicator="hosted-racing" data-action="create" → Button: Create a Race ``` **Fields**: None **Navigation**: - `[data-action="create"]` → Step 3 --- ### Step 3: Race Information **Purpose**: Basic session configuration **Elements**: ``` data-step="3" data-indicator="race-information" data-field="sessionName" → Input: Session name - required data-field="password" → Input: Session password - optional data-field="description" → Textarea: Session description data-action="next" → Button: Next data-action="back" → Button: Back ``` **Fields**: | Field | Type | Required | Domain Property | |-------|------|----------|-----------------| | `sessionName` | text | Yes | `config.sessionName` | | `password` | password | No | `config.password` | | `description` | textarea | No | `config.description` | --- ### Step 4: Server Details **Purpose**: Server region and timing configuration **Elements**: ``` data-step="4" data-indicator="server-details" data-dropdown="region" → Select: Server region data-toggle="startNow" → Checkbox: Start immediately data-action="next" data-action="back" ``` **Fields**: | Field | Type | Options | |-------|------|---------| | `region` | dropdown | `us-east`, `us-west`, `eu-central`, `eu-west`, `asia`, `oceania` | | `startNow` | toggle | Boolean | --- ### Step 5: Set Admins **Purpose**: Admin list management **Elements**: ``` data-step="5" data-indicator="set-admins" data-list="admins" → Container: Admin list data-modal-trigger="admin" → Button: Add Admin data-action="next" data-action="back" ``` --- ### Step 6: Add an Admin - Modal **Purpose**: Search and select admin to add **Elements**: ``` data-step="6" data-modal="true" data-indicator="add-admin" data-field="adminSearch" → Input: Search admins data-list="adminResults" → Container: Search results data-item="{adminId}" → Each result item data-action="select" → Button: Select admin data-action="confirm" → Button: Add Selected data-action="cancel" → Button: Cancel ``` **Fields**: | Field | Type | Purpose | |-------|------|---------| | `adminSearch` | text | Filter admin list | --- ### Step 7: Time Limits **Purpose**: Practice, qualify, and race duration settings **Elements**: ``` data-step="7" data-indicator="time-limits" data-slider="practice" → Range: Practice length in minutes data-slider="qualify" → Range: Qualify length in minutes data-slider="race" → Range: Race length in laps or minutes data-toggle="unlimitedTime" → Checkbox: Unlimited time data-action="next" data-action="back" ``` **Fields**: | Field | Type | Range | Default | |-------|------|-------|---------| | `practice` | slider | 0-120 min | 15 | | `qualify` | slider | 0-60 min | 10 | | `race` | slider | 1-500 laps | 20 | --- ### Step 8: Set Cars **Purpose**: Car list management **Elements**: ``` data-step="8" data-indicator="set-cars" data-list="cars" → Container: Selected cars data-modal-trigger="car" → Button: Add Car data-action="next" data-action="back" ``` --- ### Step 9: Add a Car - Modal **Purpose**: Search and select cars **Elements**: ``` data-step="9" data-modal="true" data-indicator="add-car" data-field="carSearch" → Input: Search cars data-list="carResults" → Container: Car grid data-item="{carId}" → Each car tile data-action="select" → Select car data-action="confirm" → Button: Add Selected data-action="cancel" → Button: Cancel ``` --- ### Step 10: Set Car Classes **Purpose**: Multi-class race configuration **Elements**: ``` data-step="10" data-indicator="car-classes" data-dropdown="carClass" → Select: Car class assignment data-list="classAssignments" → Container: Class assignments data-action="next" data-action="back" ``` --- ### Step 11: Set Track **Purpose**: Track selection **Elements**: ``` data-step="11" data-indicator="set-track" data-field="selectedTrack" → Display: Currently selected track data-modal-trigger="track" → Button: Select Track data-action="next" data-action="back" ``` --- ### Step 12: Add a Track - Modal **Purpose**: Search and select track **Elements**: ``` data-step="12" data-modal="true" data-indicator="add-track" data-field="trackSearch" → Input: Search tracks data-list="trackResults" → Container: Track grid data-item="{trackId}" → Each track tile data-action="select" → Select track data-action="confirm" → Button: Select data-action="cancel" → Button: Cancel ``` --- ### Step 13: Track Options **Purpose**: Track configuration selection **Elements**: ``` data-step="13" data-indicator="track-options" data-dropdown="trackConfig" → Select: Track configuration data-toggle="dynamicTrack" → Checkbox: Dynamic track data-action="next" data-action="back" ``` --- ### Step 14: Time of Day **Purpose**: Race start time configuration **Elements**: ``` data-step="14" data-indicator="time-of-day" data-slider="timeOfDay" → Range: Time of day 0-24 data-field="raceDate" → Date picker: Race date data-toggle="simulatedTime" → Checkbox: Simulated time progression data-action="next" data-action="back" ``` --- ### Step 15: Weather **Purpose**: Weather conditions **Elements**: ``` data-step="15" data-indicator="weather" data-dropdown="weatherType" → Select: Weather type data-slider="temperature" → Range: Temperature data-slider="humidity" → Range: Humidity data-toggle="dynamicWeather" → Checkbox: Dynamic weather data-action="next" data-action="back" ``` **Weather Types**: `clear`, `partly-cloudy`, `mostly-cloudy`, `overcast` --- ### Step 16: Race Options **Purpose**: Race rules and settings **Elements**: ``` data-step="16" data-indicator="race-options" data-field="maxDrivers" → Input: Maximum drivers data-toggle="rollingStart" → Checkbox: Rolling start data-toggle="fullCourseCautions" → Checkbox: Full course cautions data-toggle="fastRepairs" → Checkbox: Fast repairs data-action="next" data-action="back" ``` --- ### Step 17: Team Driving **Purpose**: Team race configuration **Elements**: ``` data-step="17" data-indicator="team-driving" data-toggle="teamDriving" → Checkbox: Enable team driving data-field="minDrivers" → Input: Min drivers per team data-field="maxDrivers" → Input: Max drivers per team data-action="next" data-action="back" ``` --- ### Step 18: Track Conditions - Final Step **Purpose**: Track state configuration **Elements**: ``` data-step="18" data-indicator="track-conditions" data-dropdown="trackState" → Select: Track state data-toggle="marbles" → Checkbox: Marbles simulation data-slider="rubberLevel" → Range: Rubber buildup data-action="back" → Button: Back ``` **Track States**: `auto-generated`, `clean`, `low-rubber`, `medium-rubber`, `high-rubber` > **Note**: No Submit button on Step 18. Automation intentionally stops here for user review. --- ## Navigation Flow Diagram ```mermaid flowchart TD S2[Step 2: Hosted Racing] -->|Create Race| S3[Step 3: Race Information] S3 -->|Next| S4[Step 4: Server Details] S4 -->|Next| S5[Step 5: Set Admins] S5 -->|Add Admin| S6[Step 6: Add Admin Modal] S6 -->|Confirm/Cancel| S5 S5 -->|Next| S7[Step 7: Time Limits] S7 -->|Next| S8[Step 8: Set Cars] S8 -->|Add Car| S9[Step 9: Add Car Modal] S9 -->|Confirm/Cancel| S8 S8 -->|Next| S10[Step 10: Car Classes] S10 -->|Next| S11[Step 11: Set Track] S11 -->|Select Track| S12[Step 12: Add Track Modal] S12 -->|Confirm/Cancel| S11 S11 -->|Next| S13[Step 13: Track Options] S13 -->|Next| S14[Step 14: Time of Day] S14 -->|Next| S15[Step 15: Weather] S15 -->|Next| S16[Step 16: Race Options] S16 -->|Next| S17[Step 17: Team Driving] S17 -->|Next| S18[Step 18: Track Conditions] S18 -->|STOP| REVIEW[Manual Review Required] S3 -.->|Back| S2 S4 -.->|Back| S3 S5 -.->|Back| S4 S7 -.->|Back| S5 S8 -.->|Back| S7 S10 -.->|Back| S8 S11 -.->|Back| S10 S13 -.->|Back| S11 S14 -.->|Back| S13 S15 -.->|Back| S14 S16 -.->|Back| S15 S17 -.->|Back| S16 S18 -.->|Back| S17 ``` --- ## Example Fixture: Step 3 - Race Information ```html iRacing - Race Information
Step 3 of 18 Race Information

Race Information

``` --- ## Selector Strategy for PlaywrightAutomationAdapter ### Primary Selector Pattern Use **data-* attribute selectors** as the primary strategy: ```typescript // Selector constants const SELECTORS = { // Step identification stepContainer: (step: number) => `[data-step="${step}"]`, stepIndicator: (name: string) => `[data-indicator="${name}"]`, // Navigation nextButton: '[data-action="next"]', backButton: '[data-action="back"]', confirmButton: '[data-action="confirm"]', cancelButton: '[data-action="cancel"]', createButton: '[data-action="create"]', addButton: '[data-action="add"]', selectButton: '[data-action="select"]', // Form fields field: (name: string) => `[data-field="${name}"]`, dropdown: (name: string) => `[data-dropdown="${name}"]`, toggle: (name: string) => `[data-toggle="${name}"]`, slider: (name: string) => `[data-slider="${name}"]`, // Modals modal: '[data-modal="true"]', modalTrigger: (type: string) => `[data-modal-trigger="${type}"]`, // Lists and items list: (name: string) => `[data-list="${name}"]`, listItem: (id: string) => `[data-item="${id}"]`, }; ``` ### PlaywrightAutomationAdapter Integration ```typescript import { Page } from 'playwright'; export class PlaywrightAutomationAdapter implements IScreenAutomation { private page: Page; async waitForStep(stepNumber: number): Promise { await this.page.waitForSelector(`[data-step="${stepNumber}"]`, { state: 'visible', timeout: 10000, }); } async clickAction(action: string): Promise { const selector = `[data-action="${action}"]`; await this.page.click(selector); return { success: true }; } async fillField(fieldName: string, value: string): Promise { const selector = `[data-field="${fieldName}"]`; await this.page.fill(selector, value); return { success: true, fieldName, value }; } async selectDropdown(name: string, value: string): Promise { const selector = `[data-dropdown="${name}"]`; await this.page.selectOption(selector, value); } async setToggle(name: string, checked: boolean): Promise { const selector = `[data-toggle="${name}"]`; const isChecked = await this.page.isChecked(selector); if (isChecked !== checked) { await this.page.click(selector); } } async setSlider(name: string, value: number): Promise { const selector = `[data-slider="${name}"]`; await this.page.fill(selector, String(value)); } async waitForModal(): Promise { await this.page.waitForSelector('[data-modal="true"]', { state: 'visible', }); } async selectListItem(itemId: string): Promise { const selector = `[data-item="${itemId}"]`; await this.page.click(selector); } async executeStep(stepId: StepId, config: SessionConfig): Promise { const step = stepId.value; await this.waitForStep(step); switch (step) { case 2: await this.clickAction('create'); break; case 3: await this.fillField('sessionName', config.sessionName); if (config.password) { await this.fillField('password', config.password); } if (config.description) { await this.fillField('description', config.description); } await this.clickAction('next'); break; // Additional steps follow same pattern... } return { success: true }; } } ``` ### Selector Priority Order 1. **`data-action`** - For all clickable navigation elements 2. **`data-field`** - For all form inputs 3. **`data-step`** - For step identification/verification 4. **`data-modal`** - For modal detection 5. **`data-item`** - For list item selection ### Benefits of This Strategy 1. **Stability**: Selectors will not break when CSS/styling changes 2. **Clarity**: Self-documenting selectors indicate purpose 3. **Consistency**: Same pattern across all steps 4. **Testability**: Easy to verify correct element targeting 5. **Maintenance**: Simple to update when workflow changes --- ## File Structure ``` resources/ └── mock-fixtures/ # NEW: Simplified test fixtures ├── step-02-hosted-racing.html ├── step-03-race-information.html ├── step-04-server-details.html ├── step-05-set-admins.html ├── step-06-add-admin.html ├── step-07-time-limits.html ├── step-08-set-cars.html ├── step-09-add-car.html ├── step-10-car-classes.html ├── step-11-set-track.html ├── step-12-add-track.html ├── step-13-track-options.html ├── step-14-time-of-day.html ├── step-15-weather.html ├── step-16-race-options.html ├── step-17-team-driving.html ├── step-18-track-conditions.html └── shared.css # Optional: Shared styles ``` --- ## FixtureServer Updates Update `STEP_TO_FIXTURE` mapping in [`FixtureServer.ts`](../packages/infrastructure/adapters/automation/FixtureServer.ts:16): ```typescript const STEP_TO_FIXTURE: Record = { 2: 'step-02-hosted-racing.html', 3: 'step-03-race-information.html', 4: 'step-04-server-details.html', 5: 'step-05-set-admins.html', 6: 'step-06-add-admin.html', 7: 'step-07-time-limits.html', 8: 'step-08-set-cars.html', 9: 'step-09-add-car.html', 10: 'step-10-car-classes.html', 11: 'step-11-set-track.html', 12: 'step-12-add-track.html', 13: 'step-13-track-options.html', 14: 'step-14-time-of-day.html', 15: 'step-15-weather.html', 16: 'step-16-race-options.html', 17: 'step-17-team-driving.html', 18: 'step-18-track-conditions.html', }; ``` --- ## Implementation Tasks for Code Mode 1. Create `resources/mock-fixtures/` directory 2. Create 17 HTML fixture files for steps 2-18 3. Update [`FixtureServer.ts`](../packages/infrastructure/adapters/automation/FixtureServer.ts:42) constructor to use new fixtures path 4. Create `PlaywrightAutomationAdapter` implementing selector strategy 5. Update E2E tests to use PlaywrightAutomationAdapter with FixtureServer --- ## Testing Verification Checklist For each fixture, verify: - [ ] `data-step` attribute present on body - [ ] `data-indicator` present for step identification - [ ] All navigation buttons have `data-action` - [ ] All form fields have `data-field`, `data-dropdown`, `data-toggle`, or `data-slider` - [ ] Modal fixtures have `data-modal="true"` - [ ] Navigation links point to correct next/previous fixtures - [ ] Visual rendering is acceptable in browser