Playwright with JavaScript and Node

Updated June 2026
JavaScript and Node.js are Playwright's native environment, where the framework offers its most complete feature set including the Playwright Test runner, fixtures, parallel execution, and built-in TypeScript support. This guide walks you through project setup, configuration, test architecture, page objects, and advanced runner features.

Playwright was originally written in TypeScript and runs on Node.js, which makes JavaScript the first-class language for the framework. The @playwright/test package includes everything you need: the automation library, a test runner with parallel execution, an assertion library with auto-retrying, and debugging tools like Trace Viewer and UI Mode. No additional testing frameworks are required.

Step 1: Set Up a Node.js Project with Playwright

You need Node.js version 18 or later. Create a new directory for your project and initialize Playwright with the scaffolding command: npm init playwright@latest. This interactive setup asks whether you want TypeScript or JavaScript, where to store test files, whether to generate a GitHub Actions workflow, and whether to install browsers immediately.

The scaffolding creates a playwright.config.ts file with sensible defaults, a tests/ directory with an example test, and a package.json with @playwright/test as a dev dependency. Browser binaries for Chromium, Firefox, and WebKit are downloaded to a shared cache directory.

For existing projects, install Playwright directly with npm install -D @playwright/test followed by npx playwright install to download browsers. Then create the configuration file manually or copy it from the Playwright documentation. The minimum configuration specifies which browsers to test and where to find test files.

Verify the setup by running npx playwright test. The example test should pass across all configured browsers, confirming that the installation is complete and the test runner works correctly. If any browser fails to launch, run npx playwright install --with-deps to install missing system dependencies.

Step 2: Configure the Playwright Test Runner

The playwright.config.ts file controls every aspect of test execution. Understanding its key settings lets you optimize the runner for your project's needs.

The projects array defines which browsers to test. Each project specifies a browser name and optional configuration like viewport size, device emulation, or custom launch options. The default setup includes Chromium, Firefox, and WebKit, but you can add mobile device emulation projects or browser-specific configurations.

The use object sets default options applied to all tests. Common settings include baseURL (so tests can use relative URLs instead of full addresses), screenshot (capture on failure or always), video (record test execution), and trace (capture debugging traces). Setting baseURL means your tests call await page.goto('/login') instead of hardcoding the full URL, making them portable across environments.

Timeout configuration happens at three levels. timeout sets the maximum time for an entire test (default 30 seconds). expect.timeout sets how long assertions retry before failing (default 5 seconds). actionTimeout sets how long actions like click or fill wait for elements (default no limit, relies on the test timeout). Adjust these based on your application's responsiveness.

Retry and parallelism settings control how tests run. retries specifies how many times to retry failed tests (useful for dealing with genuinely flaky infrastructure). workers sets the number of parallel worker processes, defaulting to half your CPU cores. In CI, you might set workers: 1 for deterministic ordering or increase it for faster execution on multi-core runners.

Reporter configuration determines how results are displayed. The default list reporter shows results in the terminal. The html reporter generates a detailed HTML report you can open in a browser. You can use multiple reporters simultaneously, for example list for terminal output and html for detailed reports on failure.

Step 3: Write Tests with Fixtures

Fixtures are Playwright Test's mechanism for providing test dependencies. Instead of using beforeEach/afterEach hooks to set up and tear down resources, fixtures declare what a test needs, and the runner provides it automatically.

The built-in page fixture gives each test a fresh browser page in an isolated context. The context fixture provides the browser context itself, useful when you need to create additional pages or modify context settings. The browser fixture gives access to the browser instance, and request provides an API request context for making HTTP calls without a browser.

Tests are defined with the test function, which receives fixtures as destructured parameters. A test that only needs a page writes test('name', async ({ page }) => { ... }). A test that needs both a page and API access writes test('name', async ({ page, request }) => { ... }). The runner creates and destroys these resources automatically.

Custom fixtures let you encapsulate shared setup logic. Define them by extending the base test object with test.extend(). For example, you could create a loggedInPage fixture that navigates to the login page, fills in credentials, submits the form, and returns the authenticated page. Any test that destructures loggedInPage receives an already-authenticated browser page.

Fixture scoping controls resource lifetime. By default, fixtures are per-test (created fresh for each test). Worker-scoped fixtures are created once per worker process and shared across all tests in that worker. Use worker scope for expensive resources like database connections or authenticated sessions that can be safely shared.

Step 4: Implement Page Object Model

As your test suite grows, raw test files with inline selectors become hard to maintain. The Page Object Model (POM) pattern organizes your automation code by creating a class for each page or component of your application. Each class encapsulates the selectors and interactions for that page, so tests read like high-level user stories.

A page object class receives a Playwright Page instance in its constructor and exposes methods for each user action. For a login page, the class might have methods like navigate(), fillEmail(email), fillPassword(password), and submit(). Selectors are defined as class properties, so they are maintained in one place and shared across all methods.

Tests that use page objects are shorter and more readable. Instead of writing await page.locator('#email').fill('user@example.com'), you write await loginPage.fillEmail('user@example.com'). When the application's markup changes, you update the selector in one place (the page object) rather than in every test that interacts with that element.

Combine page objects with custom fixtures for a clean architecture. Create a fixture that instantiates your page objects and provides them to tests. This way, tests declare which page objects they need, and the fixture system handles creation and cleanup. The result is a test suite where individual tests are short, focused, and insulated from implementation details.

For complex applications, consider component objects (for reusable UI components like navigation bars, modals, or forms) alongside page objects. A header component object can be used across multiple page objects, keeping selector definitions DRY without over-abstracting your test infrastructure.

Step 5: Run Tests in Parallel and Debug

Playwright Test runs test files in parallel across worker processes by default. Each worker gets its own browser instance with fully isolated state. Tests within the same file run sequentially by default, while different files run in parallel across workers.

Control parallelism with the --workers flag or the workers config option. More workers means faster execution but higher resource consumption. In CI, match the worker count to available CPU cores. Locally, the default (half your cores) provides a good balance between speed and leaving resources for your application under test.

For large test suites in CI, sharding distributes tests across multiple CI machines. Run npx playwright test --shard=1/4 on four machines (with shard values 1/4 through 4/4), and each machine runs a quarter of the tests. Combine this with the merge-reports command to consolidate results into a single HTML report.

Debugging options include headed mode (--headed) to see the browser, the Playwright Inspector (--debug) for step-by-step execution, and UI Mode (--ui) for an interactive visual runner. UI Mode is the most powerful option, providing a test tree, action timeline, DOM snapshots, network logs, and source code view in a single interface.

Trace Viewer handles debugging in CI where you cannot interact with the browser. Configure trace: 'on-first-retry' in your config to capture traces only when tests fail. Open traces with npx playwright show-trace trace.zip to see a complete timeline of what happened during the test, including screenshots at every step, network requests, console logs, and the exact assertion that failed.

Step 6: Add TypeScript Support

Playwright Test has built-in TypeScript support that requires zero configuration. When you initialize a project with npm init playwright@latest and choose TypeScript, the configuration file and example tests are generated as .ts files. Playwright compiles TypeScript on the fly using its bundled compiler, so you do not need a separate build step.

TypeScript provides type safety for all Playwright APIs. Your editor shows auto-completion for page methods, locator options, and assertion matchers. Type errors are caught at compile time rather than at runtime, which is especially valuable for page object classes where method signatures and return types help document the expected behavior.

For projects that already use JavaScript, migration to TypeScript is incremental. Rename files from .spec.js to .spec.ts, add type annotations where helpful, and let TypeScript's type inference handle the rest. Playwright's API types are comprehensive, so most code works without modification after renaming.

Custom type definitions help when extending fixtures or creating page objects. Define interfaces for your page object methods and fixture types, then use them in test.extend<MyFixtures>() to get full type checking for custom fixtures. This catches mismatches between fixture definitions and test usage at compile time.

Key Takeaway

JavaScript and Node.js provide the richest Playwright experience, with the full Playwright Test runner, fixtures, parallel execution, and TypeScript support included out of the box. Use fixtures to manage test dependencies cleanly, implement page objects as your suite grows, and leverage UI Mode and Trace Viewer for efficient debugging.