835 lines
22 KiB
Markdown
835 lines
22 KiB
Markdown
# 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
|
|
<!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:
|
|
|
|
```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<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`](../packages/infrastructure/adapters/automation/FixtureServer.ts:16):
|
|
|
|
```typescript
|
|
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`](../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 |