Concurrent runs
Start with one worker, the default. Once your app has isolated test resources, try dyno --workers 4 and compare run times. More tabs don’t make a shared database safe.
Dyno uses one managed browser with private worker contexts, gateways, and app resources. Tests keep their hooks and fresh documents; the reporter combines results in catalog order. Manual runs use one worker; explicitly combining --manual --workers 4 fails.
Give each worker its own resources
Section titled “Give each worker its own resources”Return createWorker from Node setup:
import { prepareBuild, startTestProject } from './support/project.js';
export default async function setup({ session, signal, onCleanup }) { let build = await prepareBuild({ session, signal, onCleanup }); return { createWorker: ({ worker, signal, onCleanup }) => startTestProject({ session, worker, build, signal, onCleanup }), };}These helpers belong to your project. worker has a string id and numeric index. Each call returns { webServer, env, fixtures, beforeExecution, drain, teardown }, overriding setup defaults. Share immutable build output; keep databases, queues, files, and mutable app state private. A deliberately stateless app can share a server.
If setup starts the server, return its URL with command: undefined and reuseExistingServer: true. Worker teardown runs before shared setup teardown. Register each release with onCleanup before the next fallible step. Registration closes when the factory settles; callbacks run once in reverse order even after setup fails. Returned teardown joins that same cleanup stack. Do not register the same release twice. The frozen session.id is shared across workers and stays stable during watch reruns; combine it with worker identity when naming private backend resources. The same setup path works with one worker.
Protect shared resources
Section titled “Protect shared resources”Declare resources such as the OS clipboard before execution:
test('copies a token', async ({ I }) => { await I.click('Copy token');}, { resources: ['clipboard'] });Dyno reserves the names together until document retirement and project draining finish. Other eligible tests continue. Global Pause stops new assignments at test boundaries; app.pause() holds only its worker. Stop or worker loss saves accepted outcomes and images, drains, and closes the run. Tests and workers aren’t automatically retried.
Randomize order
Section titled “Randomize order”dyno --randomdyno --seed issue-184The seed appears in CLI output, Run info, and saved reports. It preserves priority for the same test IDs, including when filtering. It doesn’t control app randomness or asynchronous timing.
Replay worker queues
Section titled “Replay worker queues”dyno --replay .dyno/reports/<run-id>/schedule.jsonschedule.json records assignments and completion order. Replay restores worker queues and count, not network or timer interleaving. The bundle fingerprint and catalog must match; an explicit --workers must match too. Selection flags, seeds, and --watch can’t combine with replay.
A completed schedule replays its full selection. An interrupted one replays attempted tests, including the unfinished assignment, and reports omitted unassigned tests. Those tests never count as passed. App code outside the test bundle, backend data, and application randomness remain yours to control. The last app stays available until shutdown, which is recorded in session.json.