Playwright is a modern web test runner. With included features such as:
- Cross browser support, including mobile browsers
- Multi tab, origin, and user support
- Automatic waiting
- Test generation
- Screenshots and videos
Requirements
Section titled “Requirements”The @nx/playwright plugin supports the following package versions.
| Package | Supported Versions |
|---|---|
@playwright/test | ^1.36.0 |
Nx generators install the latest supported versions automatically when scaffolding new projects.
Setting up @nx/playwright
Section titled “Setting up @nx/playwright”Installation
Section titled “Installation”In any Nx workspace, you can install @nx/playwright by running the following command:
nx add @nx/playwrightThis will install the correct version of @nx/playwright.
How @nx/playwright infers tasks
Section titled “How @nx/playwright infers tasks”The @nx/playwright plugin will create a task for any project that has a Playwright configuration file present. Any of the following files will be recognized as a Playwright configuration file:
playwright.config.jsplaywright.config.tsplaywright.config.mjsplaywright.config.mtsplaywright.config.cjsplaywright.config.cts
The configuration directory must also contain a package.json or project.json.
View inferred tasks
Section titled “View inferred tasks”To view inferred tasks for a project, open the project details view in Nx Console or run nx show project my-project --web in the command line.
@nx/playwright configuration
Section titled “@nx/playwright configuration”The @nx/playwright/plugin is configured in the plugins array in nx.json.
{ "plugins": [ { "plugin": "@nx/playwright/plugin", "options": { "ciTargetName": "e2e-ci", "targetName": "e2e" } } ]}The targetName and ciTargetName options control the name of the inferred Playwright tasks. The default names are e2e and e2e-ci.
When your Playwright config declares a webServer that sets reuseExistingServer to true and whose command runs an Nx task, Nx runs that task as a dependency of the Playwright tasks. The command must be nx run <project>:<target> or nx <target> <project>, optionally prefixed by a package manager runner such as npx or pnpm exec, with no extra arguments.
The Playwright docs suggest setting reuseExistingServer to !process.env.CI. Set it to true instead, since Nx already runs the web server as a task dependency. With false, Nx skips that task and Playwright starts its own server inside each Playwright task.
The Playwright tasks wait for the serve task's readyWhen check before Playwright starts, so Playwright finds the server running and reuses it. The Vite, webpack, Rspack, Rsbuild, Angular, Next.js, Expo, Nuxt, React Router, React Native, and Storybook plugins infer readyWhen on the server targets they can check. Generated Angular dev server, Angular Storybook, and @nx/web:file-server targets set it too. So do the Next.js serve-custom-server target and the serve target of generated Express, Fastify, Koa, and NestJS apps. A storybook dev target without a set port counts as ready once it has started, and an Angular Storybook target waits for its port. For the details, see wait for Storybook to be ready. The tasks get the address the server reported in the NX_READY_URL environment variable, which generated Playwright configs use for baseURL and webServer.url. To make another serve target report readiness, see signal readiness from the task.
For the webServer shapes Nx leaves to Playwright, see Playwright web server readiness.
To start the Playwright tasks as soon as the serve task starts instead, set the waitForServerReady plugin option to false. waitForServerReady, waitForWebServer, webServerTimeout, and the @nx/playwright:wait-for-webserver executor are deprecated and will be removed in Nx 25.
In a Playwright config you wrote yourself, pass the address through getReadyUrl from @nx/devkit for both baseURL and webServer.url. It returns the address unchanged when Nx reports none, and otherwise moves it to the reported port:
import { defineConfig } from '@playwright/test';import { getReadyUrl, workspaceRoot } from '@nx/devkit';
const baseURL = getReadyUrl('http://localhost:4200');
export default defineConfig({ use: { baseURL }, webServer: { command: 'npx nx run myapp:serve', url: baseURL, reuseExistingServer: true, cwd: workspaceRoot, },});With webServer.url set the same way, Playwright reuses the server Nx started instead of starting another.
Splitting E2E tests
Section titled “Splitting E2E tests”The targetName task runs the complete Playwright suite and caches the configured test and reporter outputs. The ciTargetName task uses the Atomizer.
Both options accept task names as strings. To run the non-atomized suite in CI, invoke the targetName task (the default is e2e) instead of the ciTargetName task.
An atomized task reruns when the app, any project the app depends on, or any test file changes. To scope it to the code its test file exercises, see skip unaffected split e2e tests.
Use include and exclude glob patterns on the plugin entry to scope inference. A target defaults plugin filter must use the exact identifier @nx/playwright/plugin.
E2E testing
Section titled “E2E testing”By default, when creating a new frontend application, Nx will prompt for which e2e test runner to use. Select playwright or pass in the arg --e2eTestRunner=playwright
nx g @nx/web:app apps/frontend --e2eTestRunner=playwrightAdd Playwright e2e to an existing project
Section titled “Add Playwright e2e to an existing project”To generate an E2E project for an existing project, run the following generator
nx g @nx/playwright:configuration --project=your-app-nameThe generator prompts for the web server option to add to the Playwright config. Pass --webServerCommand and --webServerAddress to skip the prompts.
nx g @nx/playwright:configuration --project=your-app-name --webServerCommand="npx nx serve your-project-name" --webServerAddress="http://localhost:4200"Choosing the web server per environment
Section titled “Choosing the web server per environment”Most application generators pass two servers to the Playwright configuration: the dev server for local runs and a production build for CI. The generated config reads the CI environment variable and picks between them.
import { getReadyUrl, workspaceRoot } from '@nx/devkit';
const isCI = !!process.env['CI'];// getReadyUrl moves the address to the port the server task reported.const webServerAddress = getReadyUrl( isCI ? 'http://localhost:4300' : 'http://localhost:4200');
export default defineConfig({ // ... rest of the config webServer: { command: isCI ? 'npx nx run <your-app-name>:preview' : 'npx nx run <your-app-name>:dev', url: webServerAddress, reuseExistingServer: true, cwd: workspaceRoot, },});The dev server avoids a rebuild on every change, and the CI run tests the output you ship.
These applications run against the dev server in CI as well:
- React applications that use React Router (
--useReactRouter) - Expo applications
Web applications generated with --bundler=none have no dev server, so they run against serve-static locally as well.
Pass --ciWebServerCommand and --ciWebServerAddress to choose the CI side yourself. Each one defaults to its local counterpart, so leaving both unset generates a config with a single server.
nx g @nx/playwright:configuration --project=<your-app-name> --webServerCommand="npx nx run <your-app-name>:dev" --webServerAddress="http://localhost:4200" --ciWebServerCommand="npx nx run <your-app-name>:preview" --ciWebServerAddress="http://localhost:4300"The inferred Playwright targets include the CI environment variable in their inputs, so local and CI runs don't share cached results. An inputs list you set for these targets in targetDefaults or in a project configuration replaces the inferred inputs unless it contains "...". Add { "env": "CI" } to that list, or add "..." to keep the inferred inputs.
Testing applications
Section titled “Testing applications”Run nx e2e <your-app-name> to execute e2e tests with Playwright
By default, Playwright will run in headless mode. You will have the result of all the tests and errors (if any) in your terminal. Test output such as reports, screenshots, and videos will be accessible in dist/.playwright/apps/<your-app-name>/. This can be configured with the outputDir configuration options.
If a test passes and fails without a code change, use the flaky Playwright test guide to reproduce the failure, inspect its trace, and fix its cause.
Watching for changes
Section titled “Watching for changes”With, nx e2e frontend-e2e --ui Playwright will start in headed mode where you can see your application being tested.
From, there you can toggle on the watch icon which will rerun the tests when the spec file updates.
nx e2e <your-app-name> --uiYou can also use --headed flag to run Playwright where the browser can be seen without using the Playwright UI
Specifying a project/target browser
Section titled “Specifying a project/target browser”The default generated Playwright configuration will contain a projects property that contains a list of browsers to run the tests against.
It should look similar to this:
export default defineConfig({ ..., projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, },
{ name: "firefox", use: { ...devices["Desktop Firefox"] }, },
{ name: "webkit", use: { ...devices["Desktop Safari"] }, } ]});By default, Playwright will run tests against all browsers in the projects list.
You can specify a specific browser to run the tests against by passing the --project flag to the nx e2e command.
nx e2e frontend-e2e -- --project=firefoxSpecifying a base url
Section titled “Specifying a base url”The baseURL property within the Playwright configuration can control where the tests visit by default.
import { defineConfig } from '@playwright/test';
export default defineConfig({ // Rest of your config...
// Run your local dev server before starting the tests webServer: { command: 'npx nx serve <your-app-name>', url: 'http://localhost:4200', reuseExistingServer: true, }, use: { baseURL: 'http://localhost:4200', // url playwright visits with `await page.goto('/')`; },});In order to set different baseURL values for different environments you can pass them via environment variables and Nx configurations or optionally via setting them per the environment they are needed in such as CI
import { defineConfig } from '@playwright/test';
const baseUrl = process.env.BASE_URL ?? (process.env.CI ? 'https://some-staging-url.example.com' : 'http://localhost:4200');
export default defineConfig({ // Rest of your config...
use: { baseURL: baseUrl, // url playwright visits with `await page.goto('/')`; },});Leave webServer out when the tests run against a deployed environment. There's no local server for Playwright to start, and pointing webServer.url at a URL that already answers makes Playwright fail unless reuseExistingServer is true.
By default Nx, provides a nxE2EPreset with predefined configuration for Playwright.
import { defineConfig } from '@playwright/test';import { nxE2EPreset } from '@nx/playwright/preset';import { getReadyUrl, workspaceRoot } from '@nx/devkit';
// For CI, you may want to set BASE_URL to the deployed application.const baseURL = process.env['BASE_URL'] || getReadyUrl('http://localhost:4200');
/** * Read environment variables from file. * https://github.com/motdotla/dotenv */// require('dotenv').config();
/** * See https://playwright.dev/docs/test-configuration. */export default defineConfig({ ...nxE2EPreset(__filename, { testDir: './e2e' }), /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { baseURL, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', }, webServer: { command: 'npx nx serve <your-app-name>', url: baseURL, reuseExistingServer: true, cwd: workspaceRoot, },});This preset sets up the outputDir and HTML reporter to output in dist/.playwright/<path-to-project-root> and sets up chromium, firefox, webkit browsers to be used a browser targets. If you want to use mobile and/or branded browsers you can pass those options into the preset function
export default defineConfig({ ...nxE2EPreset(__filename, { testDir: './e2e', includeMobileBrowsers: true, // includes mobile Chrome and Safari includeBrandedBrowsers: true, // includes Google Chrome and Microsoft Edge }), // other settings});If you want to override any settings within the nxE2EPreset, You can define them after the preset like so
const config = nxE2EPreset(__filename, { testDir: './e2e', includeMobileBrowsers: true, // includes mobile Chrome and Safari includeBrandedBrowsers: true, // includes Google Chrome and Microsoft Edge});export default defineConfig({ ...config retries: 3, reporters: [...config.reporters, /* other reporter settings */],});See the Playwright configuration docs for more options for Playwright.
Set up CI
Section titled “Set up CI”In CI, Nx runs nx affected to rebuild and retest only the projects a change touches, and caches results to skip repeated work.
For a complete pipeline, see Set up CI.