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-testidordata-automationattributes - Unsuitable for reliable CSS selector-based automation
Solution: Simplified Mock Fixtures
Design Principles
- Explicit Test Attributes: Every interactive element has stable
data-*attributes - Minimal HTML: Only essential structure, no framework artifacts
- Self-Contained: Each fixture includes all CSS needed for visual verification
- Navigation-Aware: Buttons link to appropriate next/previous fixtures
- Form Fields Match Domain: Field names align with
HostedSessionConfigentity
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
data-action- For all clickable navigation elementsdata-field- For all form inputsdata-step- For step identification/verificationdata-modal- For modal detectiondata-item- For list item selection
Benefits of This Strategy
- Stability: Selectors will not break when CSS/styling changes
- Clarity: Self-documenting selectors indicate purpose
- Consistency: Same pattern across all steps
- Testability: Easy to verify correct element targeting
- 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
- Create
resources/mock-fixtures/directory - Create 17 HTML fixture files for steps 2-18
- Update
FixtureServer.tsconstructor to use new fixtures path - Create
PlaywrightAutomationAdapterimplementing selector strategy - Update E2E tests to use PlaywrightAutomationAdapter with FixtureServer
Testing Verification Checklist
For each fixture, verify:
data-stepattribute present on bodydata-indicatorpresent for step identification- All navigation buttons have
data-action - All form fields have
data-field,data-dropdown,data-toggle, ordata-slider - Modal fixtures have
data-modal="true" - Navigation links point to correct next/previous fixtures
- Visual rendering is acceptable in browser