Playwright
The Playwright module runs your Playwright browser tests in a container whose browsers always match your Playwright version, the same way locally, in CI, and in Dagger Cloud. Its distinctive feature is first-class module wiring. If another module in your workspace serves your app, one line of configuration points the tests at it. No glue module, no port juggling.
Official module: dagger/playwright
Add it to your workspace
dagger install github.com/dagger/playwright
Pin your project's @playwright/test version exactly (or commit a lockfile).
The module runs your tests in the mcr.microsoft.com/playwright image
matching the version installed per package-lock.json. Without a lockfile it
uses the version declared in package.json, where a floating range like
^1.58.2 can install a newer Playwright than the image's browsers.
Run the check
dagger check # run every check in the workspace
dagger check playwright:test # just the Playwright suite
playwright:test finds the directory containing playwright.config.*,
installs your project's dependencies, and runs npx playwright test. If your
config declares a webServer,
Playwright starts your app inside the container exactly as it does on your
machine, with no further setup needed.
Wire in the service under test
If another module already serves your app, wire it into the tests instead of
duplicating that knowledge in webServer. Two steps:
1. Set the service setting to a module reference. That is the install
name of another module in your dagger.toml, and a function on it that returns
a Service. Run dagger up -l to list the candidates in copyable form:
[modules.playwright.settings]
service = "myapp:serve"
2. Add PLAYWRIGHT_BASE_URL to your playwright.config. The module
binds the service into the test container and communicates its address
through this environment variable. If your config doesn't read it, your
tests will ignore the wired service and keep targeting whatever baseURL
hardcodes:
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
},
Keep your local URL as the fallback so the same config works on your machine.
By default the service is bound as frontend; set serviceHostname if your
tests need a different hostname.
Secure contexts (service workers, WebCrypto, PWA testing)
Browser APIs that require a secure context don't work against
http://frontend:<port>, since only localhost or HTTPS origins qualify. If
any of your tests exercise service workers, WebCrypto, or other secure-context
APIs, enable the localhost proxy:
[modules.playwright.settings]
service = "myapp:serve"
localhostProxy = true
Then point those tests at PLAYWRIGHT_LOCALHOST_BASE_URL, which the proxy
sets, for example as the baseURL of a dedicated project in your config:
{
name: 'chromium-pwa',
use: {
...devices['Desktop Chrome'],
baseURL: process.env.PLAYWRIGHT_LOCALHOST_BASE_URL || 'http://localhost:3000',
},
},
Prepare your config for the container
The module sets CI=true, so review what your playwright.config keys off
process.env.CI, such as retries, forbidOnly, and especially workers:
- Replace a
workers: process.env.CI ? 1 : undefinedclamp with a bounded value like4. The container is isolated, so the usual shared-CI reason to serialize doesn't apply, and a1clamp can make the suite many times slower. Don't go unbounded either, since too many workers starve the browsers and blow test timeouts. - Remove branded-browser projects or exclude them with the
argssetting. Those browsers (channel: 'msedge',channel: 'chrome') are not present in the Playwright images; the bundledchromiumcovers the same engine.
Configure it
List the current settings with dagger settings playwright, then change one
with dagger settings playwright <key> <value>. They live in dagger.toml
under [modules.playwright.settings]:
sourcePath(default: discover) is the workspace path of the Playwright project. Set it when the workspace holds more than oneplaywright.config.*.serviceis the module reference ("module:function") of the service under test.serviceHostname(defaultfrontend) is the hostname the service is bound as inside the test container.baseImageAddress(default: derive) overrides the derivedmcr.microsoft.com/playwright:v<version>-nobleimage.baseCtris a fullContaineroverride. It is also wireable, e.g.baseCtr = "base-images:chromium".packageManager(defaultnpm) is the package manager used to install dependencies. Set it toyarn,pnpm, orbunto match your project.localhostProxy(defaultfalse) enables the localhost proxy described under secure contexts above.args(default[]) are extraplaywright testarguments, e.g.["--project", "chromium"].shards(default1) splits the check across that many parallel containers. Shards run concurrently against the same wired service and fail fast on the first failure.
[modules.playwright.settings]
service = "myapp:serve"
shards = 4
Get the HTML report
To inspect a failing run, call report. It runs the suite tolerating
failures and returns the HTML report directory. Include the html reporter
in your config, then export the report to your machine:
dagger api call playwright report -o ./playwright-report
Working with other modules
Playwright covers browser-level end-to-end testing; pair it with
Jest or Vitest for unit tests. Any module whose
function returns a Service can be the app under test. That's the
wiring contract, not a special
integration.