···11-# UXET — UX Eye-Tracking Testing Framework
22-33-UXET is a browser-based UX testing framework with integrated **WebGazer.js** eye tracking. Load any web app into a sandboxed iframe, define a task and a win condition, and UXET records mouse, keyboard, scroll, and gaze data — generating a heatmap when the test completes.
44-55----
66-77-## Quick Start
88-99-```bash
1010-# Serve the project root (any static server works)
1111-npx http-server . -p 8080
1212-1313-# Open http://localhost:8080
1414-```
1515-1616-1. Select an app from the dropdown
1717-2. Click **Load**
1818-3. Complete the 9-point eye-tracking calibration (or skip it with the invisible debug button at the top-right corner)
1919-4. Read the task briefing, then click **Begin Testing**
2020-5. Interact with the app — the test auto-stops when the **win condition** is met
2121-6. View the debrief screen with stats and a gaze heatmap
2222-2323----
2424-2525-## Adding a Test App
2626-2727-### 1. Create the app
2828-2929-Place your app inside `testable-apps/`:
3030-3131-```
3232-testable-apps/
3333- my-app/
3434- index.html
3535-```
3636-3737-Your app is a normal HTML page. **No UXET-specific code is required.**
3838-3939-### 2. Register it in the dropdown
4040-4141-Open `index.html` and add a `<div class="dropdown-item">` inside `#dropdown-menu`:
4242-4343-```html
4444-<div class="dropdown-item"
4545- data-value="testable-apps/my-app/index.html"
4646- data-task="Complete the signup flow"
4747- data-win="selector:.signup-success">
4848- <span class="item-name">My App</span>
4949- <span class="item-task">Sign up</span>
5050-</div>
5151-```
5252-5353-| Attribute | Description |
5454-|---|---|
5555-| `data-value` | Path to the app's entry HTML file |
5656-| `data-task` | Task description shown to the test participant |
5757-| `data-win` | Win condition that ends the test (see below) |
5858-5959----
6060-6161-## Win Conditions
6262-6363-Win conditions define **when the test automatically stops**. They are evaluated externally by UXET — the test app does not need any UXET-specific code.
6464-6565-### Syntax
6666-6767-```
6868-data-win="strategy:value"
6969-```
7070-7171-### Available Strategies
7272-7373-| Strategy | Syntax | What it detects |
7474-|---|---|---|
7575-| `selector` | `selector:<CSS selector>` | Element exists **and is visible** in the iframe |
7676-| `text` | `text:<substring>` | Substring appears in the iframe's visible text |
7777-| `url` | `url:<glob pattern>` | Iframe URL matches a glob pattern (`*` = wildcard) |
7878-| `postMessage` | `postMessage` | Iframe sends `{ type: 'UXET_TASK_COMPLETE' }` via `postMessage` |
7979-8080-### Examples
8181-8282-```html
8383-<!-- Fires when .checkout-success becomes visible -->
8484-data-win="selector:.checkout-success.active"
8585-8686-<!-- Fires when "Order confirmed" appears on the page -->
8787-data-win="text:Order confirmed"
8888-8989-<!-- Fires when the iframe navigates to a /success URL -->
9090-data-win="url:*/success*"
9191-9292-<!-- Legacy: app explicitly signals completion -->
9393-data-win="postMessage"
9494-```
9595-9696-### Strategy Details
9797-9898-**`selector:`** — Polls every 300ms for the CSS selector inside the iframe DOM. The element must be visible (not `display: none`, `visibility: hidden`, or `opacity: 0`). Best for single-page apps where a success state is reflected by a DOM change.
9999-100100-**`text:`** — Polls every 500ms for a substring match in `document.body.innerText`. Best for detecting success messages, confirmation text, or any visible string.
101101-102102-**`url:`** — Polls every 500ms. The glob pattern uses `*` as a wildcard. Best for multi-page flows where success means navigating to a specific URL.
103103-104104-**`postMessage`** — Listens for `window.postMessage({ type: 'UXET_TASK_COMPLETE' })` from the iframe. The only strategy that works with **cross-origin** iframes.
105105-106106-> **Cross-origin note:** `selector:` and `text:` require same-origin iframe access. If the iframe is cross-origin, use `url:` or `postMessage` instead. UXET will log a warning to the console if a DOM-based strategy fails due to CORS restrictions.
107107-108108----
109109-110110-## Test Flow
111111-112112-```
113113-Load App → Calibration → Task Briefing → Testing → Debrief + Heatmap
114114-```
115115-116116-1. **Load**: App is loaded into the iframe; WebGazer initializes the webcam (hidden)
117117-2. **Calibration**: 9-point gaze calibration grid. Click each point 5 times. An advisory card explains the process first.
118118-3. **Task Briefing**: Shows the task description (`data-task`). Participant clicks "Begin Testing" when ready.
119119-4. **Testing**: Participant interacts with the app. Mouse, keyboard, scroll, and gaze events are recorded. The win condition watcher runs in the background.
120120-5. **Debrief**: Shows session stats (time, clicks, keystrokes, scroll events, gaze points, fixations) and a rendered gaze heatmap.
121121-122122----
123123-124124-## File Structure
125125-126126-```
127127-UXET/
128128-├── index.html # Main UXET shell (dropdown, calibration, briefing, debrief)
129129-├── index.css # All styles
130130-├── js/
131131-│ ├── main.js # App controller — flow, win conditions, heatmap
132132-│ ├── tracker.js # Mouse, keyboard, scroll event tracking
133133-│ ├── session.js # Session timer and state management
134134-│ └── gazeTracker.js # WebGazer.js wrapper — gaze data collection
135135-├── testable-apps/
136136-│ ├── shop-app/
137137-│ │ └── index.html # ShopEasy store demo (win: checkout success)
138138-│ └── example-app/
139139-│ └── index.html # Form demo (win: form submitted)
140140-└── README.md
141141-```
142142-143143-### Key Modules
144144-145145-| Module | Responsibility |
146146-|---|---|
147147-| `main.js` (`UXETApp`) | Orchestrates the entire flow: app loading, calibration, testing, win conditions, heatmap rendering, data export |
148148-| `tracker.js` (`Tracker`) | Attaches to the iframe and records mouse position/clicks/distance, keyboard events, and scroll events |
149149-| `session.js` (`Session`) | Manages session lifecycle (start/stop/reset) and elapsed time |
150150-| `gazeTracker.js` (`GazeTracker`) | Initializes WebGazer.js, runs calibration, collects gaze coordinates mapped to iframe-relative positions, detects fixations |
151151-152152----
153153-154154-## Heatmap
155155-156156-When a test completes, UXET renders a gaze density heatmap on a `<canvas>` in the debrief screen:
157157-158158-- Each gaze point is drawn as a **Gaussian splat** (radial gradient)
159159-- Intensity is normalized and colorized: **blue → cyan → green → yellow → red**
160160-- Only gaze points that fell within the iframe are included
161161-- A reference grid is drawn for spatial context
162162-163163----
164164-165165-## Debug Skip
166166-167167-For automated testing, an invisible 20×20px button at the top-right corner of the calibration overlay bypasses calibration entirely. It has `id="debug-skip-calibration"` with `opacity: 0`.
168168-169169----
170170-171171-## Data Export
172172-173173-Click **Export Data** on the debrief screen to download a JSON file containing:
174174-175175-- Session metadata (app, task, duration)
176176-- All tracked events (mouse, keyboard, scroll)
177177-- Raw gaze data points with iframe-relative coordinates
178178-- Per-screen heatmap data
179179-- Aggregate statistics
180180-181181----
182182-183183-## Dependencies
184184-185185-- [WebGazer.js](https://webgazer.cs.brown.edu/) — loaded from jsDelivr CDN
186186-- [MediaPipe Face Mesh](https://google.github.io/mediapipe/) — loaded internally by WebGazer from jsDelivr CDN
187187-188188-No build step, no npm install. Just serve and open.