Skip to main content

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 : undefined clamp with a bounded value like 4. The container is isolated, so the usual shared-CI reason to serialize doesn't apply, and a 1 clamp 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 args setting. Those browsers (channel: 'msedge', channel: 'chrome') are not present in the Playwright images; the bundled chromium covers 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 one playwright.config.*.
  • service is the module reference ("module:function") of the service under test.
  • serviceHostname (default frontend) is the hostname the service is bound as inside the test container.
  • baseImageAddress (default: derive) overrides the derived mcr.microsoft.com/playwright:v<version>-noble image.
  • baseCtr is a full Container override. It is also wireable, e.g. baseCtr = "base-images:chromium".
  • packageManager (default npm) is the package manager used to install dependencies. Set it to yarn, pnpm, or bun to match your project.
  • localhostProxy (default false) enables the localhost proxy described under secure contexts above.
  • args (default []) are extra playwright test arguments, e.g. ["--project", "chromium"].
  • shards (default 1) 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.