Configure your project
Start with your app’s command and URL. Paths are relative to dyno.config.js.
import { defineConfig } from 'dyno/config';
export default defineConfig({ testDir: 'tests/browser', webServer: { command: 'npm run dev', url: 'http://127.0.0.1:3000', },});Dyno gives each test 10 seconds, including hooks and document startup. Interactor assertions get up to 2 seconds. Change timeout or expectTimeout when the workflow needs it; use timings to find out why.
All options and defaults
| Option | Default | Purpose |
|---|---|---|
testDir |
tests/browser |
Test directory |
testMatch |
**/*.test.{js,jsx,ts,tsx} |
File pattern |
timeout |
10000 |
Test deadline, including runtime startup and hooks |
cleanupTimeout |
5000 |
Total budget for cleanup after execution ends |
expectTimeout |
min(2000, timeout / 2), rounded down |
Interactor assertion timeout |
runTimeout |
60000 |
Headless browser opening and results deadline, after app readiness |
host, port |
127.0.0.1, 7357 |
Gateway address; port 0 chooses a free port |
browser.name, path, args |
chromium, Playwright build, [] |
Managed target, optional executable, and launch arguments |
browser.deviceScaleFactor |
1 |
Playwright pixel ratio from 1 to 3 |
browser.isMobile, browser.hasTouch |
false |
Emulate mobile viewport behavior and touch input |
browser.userAgent |
Browser default | Override the managed browser user agent |
viewport |
Browser size; managed: 1280 × 800 | Required app viewport dimensions |
reportDir |
.dyno/reports |
Saved JSON, JUnit, HTML, and copied images per browser run |
artifactsDir |
.dyno/artifacts |
PNG output directory |
webServer.url |
Required | HTTP(S) origin without a path |
webServer.command |
None | App startup command |
webServer.cwd, env |
Config directory, inherited env | App process settings |
webServer.ready |
Any process output | Ready-message string or RegExp |
webServer.timeout |
30000 |
App startup deadline |
webServer.reuseExistingServer |
false |
Reuse a responding server |
setup |
None | Node setup module |
seed, random |
None, false |
Repeatable randomized priority or a generated seed |
workers |
1 |
Managed worker count, from 1 to 32; concurrent runs need project worker setup |
forbidOnly |
Whether CI is set |
Reject focused test/suite definitions |
Set up real app data
Section titled “Set up real app data”Point setup at a Node module. Its async default export receives { config, session, signal, onCleanup } and returns the resources your tests need:
import { startTestProject } from './support/project.js';
export default async function setup({ session, signal, onCleanup }) { return startTestProject({ session, signal, onCleanup });}startTestProject is your helper. Return webServer overrides, app env, JSON-safe fixtures for tests, and a teardown function. Keep credentials out of browser fixtures. Register owned releases with onCleanup as resources are acquired. They run even if setup throws before returning. Setup messages go to stderr.
Use project factories to create committed records and real sessions. Return the IDs and paths a scenario needs, then exercise the behavior through the app. Share immutable fixture inputs rather than mutable records. For concurrent runs, give each worker its own resources.
Use named app fixtures to call your real factories through app.fixture. Keep that test control outside production entrypoints. Operations receive explicit app dependencies; Dyno handles execution ownership and transport, while your app owns factories, auth, queues and visual comparison.
Finish work before reusing resources
Section titled “Finish work before reusing resources”A fresh page doesn’t stop server writes, background jobs, or service workers. Return drain when your project needs to join that work between tests. Close admission for the old scenario, release any held requests, and leave its resources ready for reuse.
Between tests Dyno retires the old document, then calls drain({ runId, executionId, workerId, test, signal }). It runs once per non-skipped execution, including the last one before results are saved. The final successful document can remain for inspection, so its backend admission still needs to close during drain. Its duration appears in session timings.
Keep draining within cleanupTimeout and honor its signal. A failure ends the session and saves accepted results; remaining tests stay unassigned. Callbacks must honor cancellation. Dyno retains their original promises until settlement before resource destruction; uncooperative Node work can delay shutdown. Make teardown safe after a drain failure.
Setup owns CLI-session resources, worker setup owns private resources, and optional beforeExecution resets or binds each scenario before browser hooks. Preparation uses the selected test timeout, leaving the remaining budget for browser work. Timings distinguish preparation from execution cleanup. For a multi-process app, use one project command to start the real stack and handle shutdown.