highway.getPerf() (additive) + tests/browser/highway-perf-baseline.spec.ts. No behaviour change. This lands BEFORE highway.js is touched, because a perf-gated refactor without a perf gate is just a refactor. ━━━ FRAME RATE IS THE WRONG THING TO MEASURE ━━━ The highway AUTO-SCALES. When the smoothed draw cost passes _DRAW_BUDGET_HI_MS (12ms) it LOWERS THE RENDER RESOLUTION to protect the frame rate (#654). Exactly right for players — and it means a real perf regression does NOT show up as dropped frames. It shows up as a BLURRIER PICTURE at a perfectly healthy 60fps. Benchmark fps and you measure the feedback loop, not the renderer, and conclude nothing changed while the image quietly degrades. So the gate pins the scale (setRenderScale(1) + setMinRenderScale(1), which clamps autoScale to [1,1]) and measures drawMs — the renderer's own cost. None of that was reachable before: neither drawMs nor the effective scale escaped the closure. Hence getPerf(). The threshold is the app's OWN: _DRAW_BUDGET_HI_MS is the cost at which the highway itself starts sacrificing resolution in production. Exceeding it is not an arbitrary benchmark line — it is the renderer failing its own budget. Current cost ~2.2ms, so ~5x headroom: far more than headless-CI variance, far less than any regression worth shipping. ━━━ I WROTE THIS GATE WRONG THREE TIMES. EACH TIME IT PASSED. ━━━ 1. VACUOUS ASSERTION. First cut asserted "the auto-scaler wasn't forced to intervene", i.e. effectiveScale == 1. I injected a 10x regression (drawMs 2.4 -> 22.4ms, nearly DOUBLE the budget) and it PASSED. Of course it did: setMinRenderScale(1) sets the scaler's FLOOR to 1, so effectiveScale CANNOT drop below it. The very pinning that stops the scaler hiding a regression also stops it ever reporting one. A guard that cannot fail. 2. MEASURING AN IDLE RENDERER (Codex [P2]). playSong() takes ~3-4s to actually start — it is fetching and decoding stems. My "if not playing after 2s, togglePlay()" fired BEFORE autoplay, started playback, and then the app's own autoplay toggled it straight back to PAUSED. The renderer idled through the entire measurement. Now it WAITS for playback rather than racing it, and asserts the chart clock advanced DURING the sampling window — not merely at some point beforehand, which the first fix would have accepted. 3. UNENCODED FILENAME (Codex [P2]). playSong() decodes its argument before building the /ws/highway path, so every real caller passes encodeURIComponent(filename) (app.js:2879, 4137). Raw, a name containing # ? % or / yields an invalid WebSocket URL, the song never loads — and on those libraries the gate would have silently measured an idle renderer instead of failing. Every one of those bugs made the gate PASS. That is the whole hazard of a perf test: it fails safe in the wrong direction. BITE-TESTED, and this is the only reason I trust it: a 10x regression injected into the draw path FAILS the gate under live playback (22.0ms vs the 12ms budget) and the clean build passes at ~2.1ms with the chart clock advancing 5.6s across the sample. node 1045, pytest 2412, ESLint 0, Codex 0. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| audio-session-compat.spec.ts | ||
| audio-session.spec.ts | ||
| basic-load.spec.ts | ||
| capability-inspector.spec.ts | ||
| check-errors.spec.ts | ||
| default-arrangement-pin.spec.ts | ||
| exit-confirm.spec.ts | ||
| highway-3d-lefty.spec.ts | ||
| highway-perf-baseline.spec.ts | ||
| keyboard-shortcuts.spec.ts | ||
| progress-shop.spec.ts | ||
| README.md | ||
| resume-session.spec.ts | ||
| settings-tabbed.spec.ts | ||
| v3-grid-virtualization.spec.ts | ||
| v3-section-map-leftmost-click.spec.ts | ||
| v3-tree-select.spec.ts | ||
| wake-lock.spec.ts | ||
Browser Tests
This directory contains Playwright browser tests for FeedBack keyboard shortcuts.
Prerequisites
-
FeedBack web server: Tests need the server reachable at
http://localhost:8000. Playwright auto-starts it viawebServer.commandinplaywright.config.ts, so manual startup is optional. Start it manually if you want to debug the server, run tests outside Playwright, or skip the per-run boot delay:LIBRARY_PATH=/path/to/your/library docker compose up -dPlaywright reuses an already-running server locally (
reuseExistingServer: true). -
Node.js installed: Required for running Playwright tests
node --version # Should be v18 or higher
Installation
Install dependencies:
npm install
Install Playwright browsers:
npm run install:playwright
Running Tests
Run all tests:
npm test
Run tests in headed mode (watch the browser):
npm run test:headed
Debug tests with interactive inspector:
npm run test:debug
Test Files
basic-load.spec.ts- Basic app load and shortcut registry availabilitycheck-errors.spec.ts- Check for console errorskeyboard-shortcuts.spec.ts- Comprehensive keyboard shortcut tests
Writing Tests
Tests use Playwright's test API. Example:
import { test, expect } from '@playwright/test';
test('my test', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('.screen.active');
// Interact with the page
await page.keyboard.press('?');
// Assert
await expect(page.locator('#shortcuts-modal')).toBeVisible();
});
Troubleshooting
Container won't start
If the Docker container exits immediately, check the logs:
docker compose logs
Common issue: Missing dependencies. Rebuild the container:
docker compose build --no-cache
docker compose up -d
Tests timeout
Increase timeout in playwright.config.ts if needed.
LIBRARY_PATH not set
Make sure to set the LIBRARY_PATH environment variable:
LIBRARY_PATH=~/feedBack-library docker compose up -d
CI/CD
In CI environments, Playwright will:
- Run tests in headless mode
- Retry failed tests up to 2 times
- Generate HTML report with screenshots/videos on failure