Platforms
Web
danwo drives your web app in a real Chromium browser, behind its proxy, with locators, an agent and a recording of every test.
On the web, danwo opens your app in Chromium and clicks through it like a user. The browser runs behind danwo's proxy, so every request the page makes, and every call your server makes, is recorded and can be faulted.
A web test
import { test } from "@danwo/sdk"
test("shows the credits offer", async ({ app, screen, expect }) => {
await app.open("/")
await expect(screen.getByRole("button", { name: "Buy 500 credits" })).toBeVisible()
})app.open("/") loads your app's address plus the path. screen finds elements the way a person
names them, and expect waits until a check holds.
What a test gets
| Fixture | What it is |
|---|---|
app | your app: open(path) and baseUrl |
screen | finds elements: getByRole, getByLabel, getByText, getByTestId… |
expect | checks that wait: toBeVisible, toHaveText, toHaveScreenshot… |
agent | plain-English steps: act, assert, extract, waitFor |
users | another person's browser in the same test |
time | moves the app's clock forward |
mail | reads the emails and codes your app sent |
Agent and locators explains when to use each style.
Each test gets a fresh browser
Every test, and every retry, opens a new browser context: no cookies, no storage, nothing left from the test before. To start signed in, use a saved session, which signs in once per worker and reuses the browser state.
Set the browser's language, time zone and size
browser.contextOptions in your config sets up every browser a run opens. That covers each test,
each saved session's sign-in, each other person a test opens, and the MCP server's sessions:
browser: {
contextOptions: {
locale: "de-DE",
timezoneId: "Europe/Berlin",
colorScheme: "dark",
reducedMotion: "reduce",
viewport: { width: 1440, height: 900 },
},
},| Option | What it does | Default |
|---|---|---|
locale | the browser's language: navigator.language, Accept-Language | the system's |
timezoneId | the page's time zone, such as America/New_York | the system's |
colorScheme | what prefers-color-scheme answers: light, dark | light |
reducedMotion | reduce asks the page to cut its animations | no-preference |
viewport | the page's size in pixels; null uses the window's size | 1280 × 720 |
httpCredentials | a user name and password for a site behind HTTP sign-in | none |
extraHTTPHeaders | headers sent with every request, to every host | none |
danwo checks these when the config loads. A misspelt key or a time zone that doesn't exist fails
the run with CONFIG_INVALID, naming the key.
With timezoneId set, time.set("2031-07-04T09:30") means 9:30 in that zone, as the page shows
it. A time with an offset or a Z means the same moment anywhere.
extraHTTPHeaders go to every host the page calls, Stripe.js and other providers included, so put
nothing there those hosts shouldn't see. Give httpCredentials an origin to send them to your
app alone, and read the password from the environment, not the config file.
The page waits for your backend
After each action danwo waits for the page to settle. A page that shows it is loading doesn't
count as settled: an element marked aria-busy, a progress bar with no value, or a status that
says "Loading". That wait stops after 3 seconds. With an environment, danwo also waits until
nothing your app started is still in flight at the proxy:
readiness | Waits for |
|---|---|
page+backend | the page, the backend's calls, and the page again if they ran (default with a proxy) |
page | the page alone |
off | nothing |
Add settle: true to also wait for webhooks to be delivered and background jobs to finish:
readiness: { mode: "page+backend", settle: true },What's recorded
Every test keeps a flight record: a video of the tab, each step with a screenshot, the page's
console and network, and every provider call and webhook behind it. Open it with
npx @danwo/cli serve --open. See Recordings.