Files
gridpilot.gg/docs/MOCK_FIXTURES_DESIGN.md

22 KiB

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

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>iRacing - Race Information</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { 
      font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;
      background: #1a1a2e; 
      color: #eee; 
      min-height: 100vh;
      display: flex;
      flex-direction: column;
    }
    .header {
      background: #16213e;
      padding: 16px 24px;
      border-bottom: 1px solid #0f3460;
    }
    .step-indicator {
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 14px;
      color: #888;
    }
    .step-indicator .current {
      color: #e94560;
      font-weight: bold;
    }
    .main {
      flex: 1;
      padding: 32px 24px;
      max-width: 600px;
      margin: 0 auto;
      width: 100%;
    }
    .page-title {
      font-size: 24px;
      font-weight: 600;
      margin-bottom: 24px;
    }
    .form-group {
      margin-bottom: 20px;
    }
    .form-label {
      display: block;
      font-size: 14px;
      color: #aaa;
      margin-bottom: 6px;
    }
    .form-label.required::after {
      content: " *";
      color: #e94560;
    }
    .form-input {
      width: 100%;
      padding: 12px 16px;
      background: #16213e;
      border: 1px solid #0f3460;
      border-radius: 4px;
      color: #eee;
      font-size: 16px;
    }
    .form-input:focus {
      outline: none;
      border-color: #e94560;
    }
    textarea.form-input {
      min-height: 100px;
      resize: vertical;
    }
    .footer {
      background: #16213e;
      padding: 16px 24px;
      border-top: 1px solid #0f3460;
      display: flex;
      justify-content: space-between;
    }
    .btn {
      padding: 12px 24px;
      border-radius: 4px;
      font-size: 14px;
      font-weight: 500;
      cursor: pointer;
      border: none;
      transition: all 0.2s;
    }
    .btn-primary {
      background: #e94560;
      color: white;
    }
    .btn-primary:hover {
      background: #ff6b6b;
    }
    .btn-secondary {
      background: transparent;
      color: #aaa;
      border: 1px solid #0f3460;
    }
    .btn-secondary:hover {
      background: #0f3460;
      color: #eee;
    }
  </style>
</head>
<body data-step="3">
  
  <header class="header">
    <div class="step-indicator" data-indicator="race-information">
      <span>Step</span>
      <span class="current">3</span>
      <span>of 18</span>
      <span></span>
      <span>Race Information</span>
    </div>
  </header>
  
  <main class="main">
    <h1 class="page-title">Race Information</h1>
    
    <form id="race-info-form">
      <div class="form-group">
        <label class="form-label required" for="sessionName">Session Name</label>
        <input 
          type="text" 
          id="sessionName"
          class="form-input"
          data-field="sessionName"
          placeholder="Enter session name"
          required
        />
      </div>
      
      <div class="form-group">
        <label class="form-label" for="password">Password</label>
        <input 
          type="password" 
          id="password"
          class="form-input"
          data-field="password"
          placeholder="Optional password"
        />
      </div>
      
      <div class="form-group">
        <label class="form-label" for="description">Description</label>
        <textarea 
          id="description"
          class="form-input"
          data-field="description"
          placeholder="Optional session description"
        ></textarea>
      </div>
    </form>
  </main>
  
  <footer class="footer">
    <button 
      type="button" 
      class="btn btn-secondary"
      data-action="back"
      onclick="window.location.href='step-02-hosted-racing.html'"
    >
      Back
    </button>
    <button 
      type="button" 
      class="btn btn-primary"
      data-action="next"
      onclick="window.location.href='step-04-server-details.html'"
    >
      Next
    </button>
  </footer>
  
</body>
</html>

Selector Strategy for PlaywrightAutomationAdapter

Primary Selector Pattern

Use data- attribute selectors* as the primary strategy:

// 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

import { Page } from 'playwright';

export class PlaywrightAutomationAdapter implements IScreenAutomation {
  private page: Page;
  
  async waitForStep(stepNumber: number): Promise<void> {
    await this.page.waitForSelector(`[data-step="${stepNumber}"]`, {
      state: 'visible',
      timeout: 10000,
    });
  }
  
  async clickAction(action: string): Promise<ClickResult> {
    const selector = `[data-action="${action}"]`;
    await this.page.click(selector);
    return { success: true };
  }
  
  async fillField(fieldName: string, value: string): Promise<FormFillResult> {
    const selector = `[data-field="${fieldName}"]`;
    await this.page.fill(selector, value);
    return { success: true, fieldName, value };
  }
  
  async selectDropdown(name: string, value: string): Promise<void> {
    const selector = `[data-dropdown="${name}"]`;
    await this.page.selectOption(selector, value);
  }
  
  async setToggle(name: string, checked: boolean): Promise<void> {
    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<void> {
    const selector = `[data-slider="${name}"]`;
    await this.page.fill(selector, String(value));
  }
  
  async waitForModal(): Promise<void> {
    await this.page.waitForSelector('[data-modal="true"]', {
      state: 'visible',
    });
  }
  
  async selectListItem(itemId: string): Promise<void> {
    const selector = `[data-item="${itemId}"]`;
    await this.page.click(selector);
  }
  
  async executeStep(stepId: StepId, config: SessionConfig): Promise<AutomationResult> {
    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:

const STEP_TO_FIXTURE: Record<number, string> = {
  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 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