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.
Add it to your workspace
dagger module install dagger.io/js/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 in the lockfile (package-lock.json, pnpm-lock.yaml,
yarn.lock, or bun.lock). Without a lockfile it uses the version declared in
package.json, where a floating range like ^1.58.2 is read as its lower
bound and can install a newer Playwright than the image's browsers.
Run the check
dagger check --playwright # every Playwright project
dagger check --playwright-project=apps/web # one project
dagger check --playwright-project=apps/web --playwright-test-file=tests/login.spec.ts # one test file
The check is playwright/projects/tests/test. It installs your project's
dependencies and runs the project's own playwright test once per project,
over the selected test files. 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.
Projects and test files
| Flag | Collection | Key |
|---|---|---|
--playwright-project | projects | Directory holding the playwright.config, relative to the workspace root. The root is . |
--playwright-test-file | tests, on a project | Test file path, relative to the project |
List the keys with dagger list playwright-projects -a and dagger list playwright-test-files -a. Select collection items describes how to select items.
A project is a directory holding playwright.config.ts, .js, .mjs, .cjs,
.mts, or .cts. These are directories, not the browser projects inside a
config. Projects are found at or below the directory dagger runs from, plus
the nearest project that encloses it. Hidden directories and node_modules are
left out.
Test files are found by reading the config's testDir, testMatch, and
testIgnore when they are literal values, including those of each browser
project, and Playwright's defaults otherwise. A project where no test file is
found has the single key ., which runs the whole project.
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 list services to list their DAG addresses:
[modules.playwright.settings]
service = "dag://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 = "dag://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 module settings playwright, then change one
with dagger module settings playwright <key> <value>. They live in dagger.toml
under [modules.playwright.settings], and apply to every project. To test
projects against different services, install the module twice under different
names.
serviceis the DAG address ("dag://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 = "dag://base-images/chromium".packageManager(default empty) is the package manager that installs dependencies:npm,yarn,pnpm, orbun. Empty detects it from thepackageManagerfield ofpackage.json, then from the lockfile, then uses npm.installFlags(default empty) are extra arguments for the install command, such as["--ignore-scripts"].setup(default empty) are shell commands run in the project directory after dependencies are installed and before the tests, such as["pnpm --filter @my/lib build"]to build workspace packages the tests import.environment(default empty) sets environment variables in the test containers, asKEY=VALUE.localhostProxy(defaultfalse) enables the localhost proxy described under secure contexts above.args(default[]) are extraplaywright testarguments, e.g.["--project", "chromium"].shards(default1) splits each project's run across that many parallel containers against the same wired service. With every test file selected, each container runs one Playwright shard. With some selected, the selected files are split into up to that many groups. Every failing container is reported.
[modules.playwright.settings]
service = "dag://myapp/serve"
shards = 4
Get the HTML report
To inspect a failing run, call report on a project. It runs the project's
tests tolerating failures, with the html reporter added, and returns the HTML
report directory. Export it to your machine:
dagger api call playwright project --path=apps/web report export --path=./playwright-report
project returns the project that contains a path.
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.