Project
Config
danwo.config.ts: defineConfig and every option.
import { defineConfig, stripeEnvironment } from "@danwo/sdk"
import { launchShop, type Shop } from "./src/launch.ts"
const WEBHOOK_SECRET = "whsec_danwo_example"
export default defineConfig({
testDir: "tests",
workers: 2,
timeout: 60_000,
/*
* After every action, wait until the shop's calls to Stripe have finished and play the webhooks
* they caused on the emulator's clock, so tests never wait or settle by hand.
*/
readiness: { mode: "page+backend", settle: true },
/*
* What `danwo test --changed` runs for a change to the shop's own code: the webhook handler
* affects the tests that received Stripe's checkout webhook; the server's pages affect every
* test that visited one.
*/
impact: {
rules: {
"src/webhooks.ts": ["webhook checkout.session.completed"],
"src/server.ts": ["/"],
"src/launch.ts": ["/"],
},
},
// One Stripe emulator, proxy and shop per worker; `danwo matrix` resets them between runs.
async environment() {
let shop: Shop | undefined
const stripe = await stripeEnvironment({
seed: () =>
shop && {
webhookEndpoints: [
{
url: `${shop.url}/webhooks/stripe`,
events: ["checkout.session.completed"],
secret: WEBHOOK_SECRET,
},
],
},
})
const start = async () => {
shop = await launchShop(
stripe.proxy.env({
...process.env,
STRIPE_SECRET_KEY: "sk_test_danwo_example",
STRIPE_WEBHOOK_SECRET: WEBHOOK_SECRET,
SHOP_TEST_RESET: "1",
}),
)
await stripe.reset()
}
await start()
return {
get baseUrl() {
return shop?.url
},
proxy: stripe.proxy,
webhooks: stripe.emulator.webhooks,
fixtures: stripe.fixtures,
settle: stripe.settle,
async reset() {
await shop?.stop()
await start()
},
/*
* `danwo matrix` resets once per worker, keeps this snapshot, and restores it before every
* later run: the emulator's store comes back from memory and the shop empties its books,
* so no run restarts the app.
*/
snapshot: () => stripe.snapshot(),
async restore() {
await stripe.restore()
await fetch(`${shop?.url ?? ""}/__test/reset`, { method: "POST" })
},
async close() {
await shop?.stop()
await stripe.close()
},
}
},
})danwo.config.ts default-exports defineConfig({...}); every key is optional.
defineConfig
function
defineConfig(config: DanwoConfig): DanwoConfigTypes a project's config; returns it unchanged.
DanwoConfig
interface
What danwo.config.ts default-exports; every key is optional.
| Member | Description |
|---|---|
testDir?: string | Where test files are found, relative to the config file; default the config's folder. |
testMatch?: readonly string[] | Globs relative to testDir; default every *.danwo.ts. |
baseUrl?: string | The app's URL when no environment provides one. |
browser?: BrowserConfig | The browser every test runs in. contextOptions sets reduced motion, the colour scheme, the locale, the time zone, the viewport, HTTP credentials and headers sent with every request; they apply to every browser context the run opens, saved sessions' and the MCP server's included. A date-time time.set is given without an offset is read in timezoneId. |
workers?: number | Worker processes; default 1. Each runs one browser attempt at a time. |
splitFiles?: boolean | Hand workers single tests instead of whole files (default false), so one slow file does not hold a worker while the others sit idle. Only a file whose tests share nothing is split: no beforeAll or afterAll, and every test on isolation: "test"; other files run whole. Each test loads its file afresh, so tests must not pass values to each other in module variables. |
retries?: number | Extra attempts for a failing test; default 0. |
timeout?: number | One test attempt's deadline in milliseconds; default 60 000. |
model?: LanguageModel | The agent's model: an AI SDK model or a Vercel AI Gateway id. |
judge?: LanguageModel | The model agent.assert judges with; another provider than model is best. |
budget?: Budgets | Limits on what the agent spends, per step and per test: turns, model calls, dollars, wall time. Default 25 turns a step. A step or test that runs out fails with BUDGET_EXCEEDED. |
judgeScreenshot?: boolean | Show the judge of agent.assert a screenshot besides the page's text (default false). |
vision?: VisionSetting | When the agent may see the page as an image: auto (default) when the text view misses something (a canvas, an unnamed button, a frame it cannot list), always, or off. { mode, model } names a model of its own for reading screenshots. |
cache?: { readonly dir: string; readonly replayOnly?: boolean } | The replay cache; dir is relative to the config file. replayOnly: true makes every danwo test run as --replay-only: no model is called, and a step with no recording that replays fails with REPLAY_MISSING. |
secrets?: Readonly<Record<string, SessionSecret>> | Values the model never sees; tests pass secret(name). |
files?: Readonly<Record<string, string>> | Files tests may upload, by name; paths are relative to the config file. |
relocate?: boolean | Whether a replay looks for a recorded element that moved instead of handing the step back to the model (default true). danwo matrix never relocates: its runs follow recordings exactly. |
relocation?: RelocationPolicy | How replays find recorded elements: similar-v1 (default) also takes the one element that clearly matches a moved one, exact never does. The policy is part of every recording's key, so changing it records the steps afresh. Takes precedence over relocate. |
effects?: EffectsSetting | Whether a replayed agent step must have the effects its recording had: the same elements appearing and vanishing on the page, the same provider calls and webhooks behind it (default true). { ignore: [...] } leaves text matching any pattern out of page effects. An act call can turn effects off for itself with { effects: false }. |
readiness?: | ReadinessMode | { readonly mode?: ReadinessMode; readonly capMs?: number; readonly settle?: boolean } | What each step waits for after an action. page+backend (the default with an environment that has a proxy): the page settles, then nothing the app started at the proxy or the webhook senders is still in flight. page: the page alone. off: no waiting. settle: true also lets the environment play out background work (webhooks on a manual clock) after every action, so a test needs no settle() of its own. capMs bounds one wait (default 5000). |
appErrors?: { readonly console?: boolean } | What counts as the app failing during a step, besides 5xx responses from the app and uncaught page errors: console: true (default false) adds console errors. A live agent step stops at the first with APP_ERROR, or PROVIDER_ERROR when a provider call failed before it; a step the app failed in is never kept as a recording. |
sessions?: Readonly<Record<string, SessionDefinition<SessionArgs>>> | Saved sessions by name: each is set up once per worker and its browser state reused by every test that names it (test.use({ session })), until the environment resets. The setup also receives the environment's fixtures. |
environment?: (context: EnvironmentContext) => Promise<WorkerEnvironment> | Starts what a worker's tests run against (app, emulators, proxy); called once per worker. |
isolation?: Isolation | How much state tests share. test (default) starts every test, and every retry, from the environment's seeded state: emulators back to their seeds, the clock to now, the proxy's log and webhook queues empty, and data stores restored, with a file's beforeAll work and saved sessions kept on top. file resets once per file, none never. A file or describe block can choose its own with test.use({ isolation }). The app's own memory is reset only by the environment's restore or reset (e.g. by calling a test-reset endpoint). |
record?: RecordMode | RecordOptions | The flight recorder: always (default) keeps what every run did under .danwo/runs/<run>/ with an HTML report; on-failure keeps failed runs only; off records nothing. As an object, { mode, keepRuns } also says how many of the newest runs keep their recordings (default 50). |
video?: VideoSetting | Video in the flight record, one per tab or the device's screen, with every step, call and webhook on its clock: on (default) keeps it with every record kept; retain-on-failure films every run and keeps the video of runs that did not pass (a matrix keeps failing and invalid trials'); off films nothing. Secret fields show as dots, but a secret the app prints as text is on screen: turn video off for suites that show one. As an object, { mode, cursor }; cursor: false films without the pointer drawn at each action. |
reporters?: readonly Reporter[] | Reporters told about every run besides the console, e.g. github() from @danwo/github. |
impact?: ImpactConfig | How danwo test --changed maps the app's own source files to what tests touch: globs → app paths (/checkout), provider hosts, calls or webhooks. Test files and what they import need no rule; a change no rule or import explains selects every test. |
lint?: LintConfig | false | Integration lint over every run's provider calls and webhooks: per-rule levels by id ("stripe/idempotency-key": "error" | "warn" | "info" | "off") and which findings fail the run (failOn, default never). false turns it off. |
matrix?: { /** Default `defaultOutboundFaults`: each provider's faults by host, plain HTTP failures elsewhere. */ readonly outboundFaults?: (call: OutboundCall) => readonly OutboundFault[] readonly webhookFaults?: readonly WebhookFault[] /** * Faults on the app's own data calls, through the environment's `data` proxies. Default * `defaultDataFaults`: writes lost before or after the server ran them, slow, or failing to * serialize; COMMITs lost or failing; BEGINs meeting a database that is down or full; cache * commands lost, slow, or answered OOM, READONLY or LOADING. */ readonly dataFaults?: DataFaultCatalogue /** Runs a faulted run again up to this many times while its fault never fires (default 0). */ readonly rerunInvalid?: number /** Worker processes for `danwo matrix`; default `workers`, else one per core (at most 8). */ readonly workers?: number /** * How long a faulted run's checks wait: at most `factor` times (default 3) as long as the same * check took in the clean run, and at least `floorMs` (default 1000; 6000 on a device, where * pacing starts once the trial's fault has fired). `false` keeps every check's normal timeout. */ readonly checkPace?: false | { readonly factor?: number; readonly floorMs?: number } /** * Race faults, each planned only where the clean run allows it; all on by default, `false` * turns every one off. `doubleSubmit`: each change-making request the page sends the app, * sent twice at once. `beforeRedirect`: a provider's webhooks delivered before the browser * lands back on the app. `webhookOrder`: two webhooks in a row delivered the other way round. * `slowThenRetry`: a payment provider's call held up to `ms` (default 3000) or until the app * gives up and retries, then processed anyway. Running a test by several people at once is * the test's own `concurrent` option. */ readonly races?: false | RaceSettings /** * Most faulted trials a matrix runs (default 40). Past it the plan folds repeated routes and * statements into their first, keeps each target's most telling faults, then keeps races, * webhooks and writes ahead of reads. `--budget` and `--all` override it. */ readonly budget?: number } | Fault catalogue for danwo matrix. |
Budgets
interface
A step's and a test's budget.
| Member | Description |
|---|---|
step?: Budget | |
test?: Budget |
RelocationPolicy
type
"exact" | "similar-v1"The matching policies, by name.
ReadinessMode
type
"page" | "page+backend" | "off"What is waited for after an action: the page, the page and the backend, or nothing.