A Dagger module — written in the .dang module language —
that runs Playwright browser tests against your
project, with first-class workspace service
wiring: point it at
any module function that returns a Service and your tests run against it.
| Function | Description |
|---|---|
test |
Run the test suite, optionally sharded across parallel containers (a @check). |
report |
Run the suite tolerating failures; returns the HTML report Directory. |
base |
The prepared test container (workspace mounted, deps installed, service bound). |
imageAddress |
The resolved Playwright image (useful to debug version derivation). |
Install the module in your workspace:
dagger install github.com/dagger/playwrightRun the tests:
dagger check # run every check in the workspace
dagger check playwright:test # just the Playwright suiteExport the HTML report after a failing run:
dagger api call playwright report -o ./playwright-reportThe module is aware of where in the workspace you invoke it: project discovery
searches for playwright.config.* at or below your current directory, and
sourcePath resolves relative to it (absolute paths resolve from the
workspace root). The whole workspace is still mounted into the test container,
with the project directory as the working directory, so configuration and
dependencies that live above the project — monorepo roots, shared configs —
keep resolving.
Dependencies are installed at the nearest package.json at or above the
project, so a project without its own package.json installs from its
monorepo root. When the workspace has none at all, the install is skipped
and npx fetches Playwright on demand.
If another module in your workspace serves your app, wire it into the tests in
dagger.toml — no glue module needed:
[modules.playwright.settings]
service = "myapp:serve"The service is bound into the test container as frontend (configurable via
serviceHostname) and PLAYWRIGHT_BASE_URL is set to its first exposed port.
Your playwright.config must consume it:
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
},Without a wired service, no binding happens and your config's own webServer
(or hardcoded baseURL) is used as-is.
Browser APIs that require a secure
context
don't work against http://frontend:<port> — only localhost or HTTPS
origins qualify. Enable the localhost proxy to reach the wired service on a
secure-context origin:
[modules.playwright.settings]
service = "myapp:serve"
localhostProxy = trueThis proxies localhost:<port> to the service (via socat, installed with apt —
the default images qualify) and sets PLAYWRIGHT_LOCALHOST_BASE_URL for your
tests to use.
Configured under [modules.playwright.settings] in dagger.toml (or as flags
on dagger api call playwright):
sourcePath(default: discover): path of the Playwright project, relative to your current directory (absolute paths resolve from the workspace root). By default the module finds the directory containingplaywright.config.*at or below your current directory; setting this is required when the workspace holds more than one Playwright project.service: module reference ("module:function") of the service under test.serviceHostname(defaultfrontend): hostname the service is bound as.baseImageAddress(default: derive): the image tests run in. By default it is derived from your project's@playwright/testversion (mcr.microsoft.com/playwright:v<version>-noble), so browsers always match your Playwright version. Derivation prefers the version installed perpackage-lock.json; without a lockfile it falls back to the version declared inpackage.json, so pin that exactly — a floating range like^1.58.2can install a newer Playwright than the derived image's browsers.baseCtr: a fullContaineroverride, also wireable (baseCtr = "base-images:chromium").npx playwrightmust work in it after dependency install.packageManager(defaultnpm): how project dependencies are installed (npm,yarn,pnpm,bun). yarn and pnpm are enabled via corepack.localhostProxy(defaultfalse): see secure contexts above.args(default[]): extra arguments for everyplaywright testinvocation, e.g.["--project", "chromium"].shards(default1): number of parallel shard containerstestruns — this is how you shard theplaywright:testcheck.
Two things to know about the test environment:
- The module sets
CI=true, so anything yourplaywright.configkeys offprocess.env.CI(workers, retries,forbidOnly) applies. In particular, aworkers: process.env.CI ? 1 : undefinedclamp serializes the whole suite — prefer a bounded value like4(the container is isolated, but unbounded workers can starve the browsers and blow test timeouts). - Branded browser channels (
msedge,chrome) are not present in the Playwright images — remove those projects or scope runs withargs.
[modules.playwright.settings]
shards = 4Shards run in parallel containers against the same wired service and fail fast
on the first failing shard. (report is single-run; shard report merging is
not yet supported.)
This repo is its own e2e fixture: .dagger/modules/e2e runs the toolchain
against the minimal Playwright project in fixture/, with
.dagger/modules/fixtures' static server bound as the service under test,
so dagger check exercises discovery, version derivation, service wiring,
the localhost proxy, and sharding end to end.