ReeQA
ReeQA is a separate Reepolee app that runs on its own process and port (REEQA_PORT, default 2340). It provides a QA dashboard for running two kinds of checks against any project in your checkout: command checks (run a shell command like bun test and capture pass/fail) and visual baselines (capture headless-browser screenshots of pages, then compare later runs against the baseline to detect visual drift).
It shares Reepolee's renderer, auth, route pipeline, translations, components, and static files - so the dashboard itself is built with the same tools as the app it tests. QA commands and browser captures run as isolated child processes and cannot block the main app server.
Starting ReeQA
ReeQA runs on its own port, independent of the main app server:
bun dev:reeqa # ReeQA only (port 2340)
bun dev:all # app + reeman + reeqa + queue worker together
In production, ReeQA runs as its own process under systemd or PM2:
bun start:reeqa # bun apps/reeqa/server.ts --prod
The dashboard is at http://localhost:2340. It uses the same login and users table as the main app - you log in with the same credentials. See Quick Start for the full dev-process table.
The Dashboard
The dashboard (/) shows an overview of the active project: pass/fail counts for recent command-check runs, the active page set and its baseline status, and any currently running check or visual comparison. A /__busy endpoint reports whether a run is in progress, used by the shared layout's busy-poller.
The sidebar navigates between four main sections:
| Route | Page | Purpose |
|---|---|---|
/ | Dashboard | Overview: recent runs, pass/fail counts, baseline status |
/projects | Projects | Register projects, create and manage page sets |
/run-tests | Run Tests | Capture baselines, run visual comparisons, schedule recurring checks |
/command-checks | Command Checks | Run QA suites (shell commands) and view their output |
Projects
A project is a path on disk with a package.json and a base URL. ReeQA manages multiple projects, but one is active at a time - the dashboard and run pages operate against the active project.
To register a project, provide:
- Name - a human-readable label
- Path - an absolute path to a directory containing
package.json - Base URL - the HTTP/HTTPS origin ReeQA will capture pages from
The path must be absolute and must contain a package.json - this is how ReeQA discovers available QA suites (it checks whether the project's package.json defines the required script).
Projects are stored in .reepolee/reeqa/projects.json. The active project is tracked in .reepolee/reeqa/active-project.json. Deleting a project also deletes its page sets and clears the active selection.
Command Checks
Command checks run a shell command in the project's directory and report pass (exit code 0) or fail (non-zero). They are the simplest QA primitive: pick a suite, run it, read the output.
QA Suites
ReeQA ships with four built-in suites, each backed by a shell command:
| Suite code | Name | Command | Requires script |
|---|---|---|---|
tests | Website tests | bun test | - |
engine-drift | Engine drift | bun run engine:check | engine:check |
naming | Naming compliance | bun run naming:check | naming:check |
docs-links | Documentation links | bun run docs:check | docs:check |
A suite is available for a project when its required_script exists in the project's package.json scripts (or when it has no required_script, like tests). The /command-checks page shows only available suites.
Running a Suite
Pick a suite from the dropdown and submit. ReeQA spawns the command as a child process in the project's directory with CI=1 and NO_COLOR=1 set (so output is plain text and tests run in CI mode). The run is:
- Queued - the job goes to the queue worker (or runs in-process if the queue is unavailable)
- Running - stdout and stderr are streamed into the run record (up to 2 MB, then the head is truncated)
- Passed/Failed - exit code 0 is passed, anything else is failed
- Canceled - if you cancel mid-run
Only one command check can run at a time. If a run is already in progress, starting another returns an error.
Run state is persisted to .reepolee/reeqa/runs.json with atomic writes (temp file + rename), so a server restart does not lose run history. A stale running or queued run (from a crash) is re-executed by the worker on next startup.
Page Sets
A page set defines which pages ReeQA captures and compares. Each page set belongs to a project. One page set per project is active at a time.
There are two kinds of page set:
URL-list Page Sets
A list of URLs from the project's sitemap.xml. ReeQA fetches the sitemap, presents the pages (sorted by lastmod, newest first), and you check which ones to include. Every URL must be in the current sitemap - this validation runs on create and update.
This is the simplest model: one screenshot per URL, compared pixel-for-pixel against the baseline.
Workflow Page Sets
A JSON array of steps that drive a headless Chrome browser through an interaction flow (login, fill a form, click a button) and capture screenshots at marked checkpoints. This is for pages that are only reachable after an action, or states that require setup (logged-in dashboard, post-submit confirmation, etc.).
Three step types:
| Step type | Fields | Description |
|---|---|---|
navigate | url, checkpoint?, before_seconds?, delay_seconds? | Open a URL. Must be the first step. |
click | selector or text, checkpoint?, timings | Click an element by CSS selector or visible text |
fill | selector, value or value_env, checkpoint?, timings | Fill an input field |
[
{ "type": "navigate", "url": "https://example.com/login" },
{ "type": "fill", "selector": "#username", "value_env": "QA_USERNAME" },
{ "type": "fill", "selector": "#password", "value_env": "QA_PASSWORD" },
{ "type": "click", "text": "Sign in", "checkpoint": true }
]
Validation rules:
- The first step must be
navigate. - At least one step must have
"checkpoint": true- a workflow with no checkpoint captures nothing to compare. clickmust have exactly one ofselectorortext(not both, not neither).fillmust setvalue(a literal string) orvalue_env(an environment variable name resolved from the project's.envfile) - not both. ReeQA never stores secrets: only the variable name is saved in the page set; the value is resolved at run time.- Unknown fields are rejected (a typo like
chekpointproduces an error, not a silent no-op). navigatetargets must stay on the project's own origin (a login POST target legitimately is not in the sitemap, but it must be on the same domain).
before_seconds and delay_seconds pause before and after each step's action. outline_seconds and glide_seconds (click steps only) pace the recording's click presentation - they are harmless in a capture, since screenshots are taken after the step already ran.
Capture Size
Each page set has a capture size - the browser viewport used for both baseline capture and comparison. Three presets:
| Preset | Width | Height |
|---|---|---|
| mobile | 390 | 844 |
| tablet | 768 | 1024 |
| desktop | 1920 | 1080 |
Capture and compare use the same size so pages render identically (including scrollbar gutters).
Auto-Evidence and Auto-Recording
Two optional flags on a page set:
auto_evidence- after a compare run finishes, automatically record a narrated evidence video for every changed/new page, instead of requiring a manual "Record evidence" click per page.auto_recording- after a compare run finishes, also record a clean (un-annotated) video for unchanged (passing) pages - the only way a video exists for a test that passed.
Both use the queue worker; if the queue is unavailable, they run in-process as a fallback.
Page sets are stored in .reepolee/reeqa/page-sets.json. The active page set is tracked in .reepolee/reeqa/active-page-set.json.
Visual Baselines and Comparison
The /run-tests page is where you capture baselines and run comparisons. The workflow is:
- Select a project and page set (done on the
/projectspage). - Capture a baseline - ReeQA opens a headless Chrome at the page set's capture size, navigates to each URL (or replays each workflow step), captures a full-page screenshot plus a DOM snapshot and HTML, and stores them as the baseline. If the project has a
db:clone-testscript, the test database is reset and snapshotted alongside the images, so a later compare restores the identical starting state. - Run a comparison - ReeQA restores the DB snapshot (if one exists), re-captures every page, and compares each screenshot against the baseline by SHA-256 hash. If the hash differs, a pixel-level diff image is generated with vips, and the changed DOM elements are identified from the stored DOM snapshot.
Page Status
Each page in a comparison report gets one of:
| Status | Meaning |
|---|---|
unchanged | Screenshot hash matches the baseline exactly |
changed | Hash differs; a diff image and changed-element list are generated |
new | The page exists in the current run but not in the baseline |
removed | The page existed in the baseline but was not captured this run |
baseline | The page was captured as part of a baseline run (no comparison) |
Evidence Recording
For changed or new pages, you can record a narrated evidence video - Chrome replays the steps to reach the page, then captures a screencast with on-screen annotations (action labels, cursor movement, click ripples) and text-to-speech narration. The video is produced with ffmpeg. This runs as a queue job (reeqa_evidence) and the server is notified via a WebSocket relay when the video is ready.
For unchanged pages with auto_recording enabled, a clean (un-annotated) clip is recorded instead (reeqa_recording job).
Scheduling
You can schedule a recurring baseline capture or comparison for a project + page set at an interval of 1+ hours. The scheduler ticks every 60 seconds and starts any due schedule, skipping the tick if a visual run is already in progress (so scheduled runs never stack). Schedules are stored in .reepolee/reeqa/schedules.json.
Tool Requirements
Visual baselines and comparisons require external tools installed on the server:
| Tool | Used for | Required for |
|---|---|---|
| Chrome | Headless browser capture | Baseline + compare |
| vips | Image diffing and cropping | Compare only |
| ffmpeg | Evidence/recording video production | Evidence recording |
| ffprobe | Video metadata | Evidence recording |
| say | Text-to-speech narration (macOS) | Evidence recording |
visual_capabilities() checks which tools are available and the /run-tests page shows what is and is not possible on the current server. Command checks need no external tools - they just run shell commands.
File Layout
All ReeQA state lives under .reepolee/reeqa/ in the project root:
.reepolee/reeqa/
projects.json # registered projects
active-project.json # the active project id
page-sets.json # all page sets across all projects
active-page-set.json # the active page set per project
runs.json # command-check run history
visual-runs.json # visual run history (baselines + comparisons)
schedules.json # recurring schedules
baselines/ # captured baseline images, DOM snapshots, DB snapshots
<project-id>/<page-set-id>/
manifest.json # baseline metadata (pages, hashes, capture size, DB snapshot)
*.png # full-page screenshots
*.dom.json # DOM snapshots (element rects for changed-element mapping)
*.html # raw HTML at capture time
db.snapshot # SQLite database snapshot (when db:clone-test exists)
reports/ # comparison report artifacts
<run-id>/
*-baseline.png # the baseline image copied for side-by-side view
*-current.png # the current capture
*-diff.png # the pixel diff
*-baseline-zoom.png # zoomed diff region (baseline)
*-current-zoom.png # zoomed diff region (current)
*-diff-zoom.png # zoomed diff region (diff)
profiles/ # temporary Chrome profile directories (deleted after each run)
Queue Integration
ReeQA uses the queue for long-running jobs. Three job types are registered in apps/reeqa/workers.ts:
| Job type | What it does |
|---|---|
reeqa_suite_run | Execute a command check (spawn the subprocess, stream output) |
reeqa_visual_run | Execute a visual capture or comparison (Chrome + vips) |
reeqa_evidence | Record a narrated evidence video for one changed page |
reeqa_recording | Record a clean clip for one unchanged page |
reeqa_cancel | Cancel a running suite or visual job (kill the process / close the browser) |
When the queue is unavailable (e.g. the worker process is not running), every job falls back to in-process execution so the dashboard still works. The queue worker is started with bun dev:worker or bun dev:all in development, and as a separate process under PM2 or systemd in production.
Agent Mode
ReeQA supports an --agent flag (requires --dev) that runs the dashboard on a dedicated port (AGENT_REEQA_SERVER_PORT) for automated agent-driven QA workflows. This is used by the Codebuff agent integration to run and inspect QA checks programmatically. In agent mode, the server boots on the agent port instead of the default REEQA_PORT.