ScreenshotNeo

BlogGuides

Playwright Test: How to Write and Run Browser Tests

Write an isolated Playwright browser test, run it across browsers, and debug failures with practical commands and a CI setup.

By the ScreenshotNeo team4 October 202610 min read

Playwright Test browser tests combine browser actions with assertions about the resulting page state. Install the Playwright Test package and matching browser binaries, write tests using the isolated page fixture, and run them with npx playwright test. The same test suite can target Chromium, Firefox, WebKit, and configured device profiles.

This guide covers the first test, project setup, locators and assertions, browser selection, debugging, CI, performance, reliability, and common failures.

1. Install Playwright Test

For an existing npm project, install the test package and browser binaries:

npm init playwright@latest

The setup command prompts for choices such as TypeScript or JavaScript, test directory, and whether to add a CI workflow. If you want to install explicitly in an existing project, use:

npm install --save-dev @playwright/test
npx playwright install

Keep the installed Playwright package and browser binaries aligned. After upgrading the package, run the browser installation command again if the required browser revision is missing. On Linux CI, install operating-system dependencies too:

npx playwright install --with-deps

See the official installation guide and browser installation documentation.

2. Write your first browser test

Create tests/get-started.spec.ts and add this complete test:

import { test, expect } from '@playwright/test';

test('get started link opens the installation guide', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await page.getByRole('link', { name: 'Get started' }).click();
  await expect(
    page.getByRole('heading', { name: 'Installation' })
  ).toBeVisible();
});

Run it with:

npx playwright test tests/get-started.spec.ts

test names the scenario. The page fixture is the browser page provided to the test. getByRole locates a link by its accessible role and name, click() performs the user action, and expect(...).toBeVisible() waits for the expected heading to appear.

Playwright creates a fresh BrowserContext for each test using the built-in page fixture, which helps keep cookies, local storage, and page state isolated between tests. Tests should still avoid changing shared external data in ways that can collide with other tests.

3. Choose stable locators and assertions

Prefer locators based on how a user or assistive technology identifies an element. Role and accessible name are usually a strong starting point:

const submit = page.getByRole('button', { name: 'Save changes' });
await submit.click();
await expect(page.getByText('Changes saved')).toBeVisible();

Other useful locator methods include getByLabel() for form fields, getByPlaceholder() where placeholder text is meaningful, getByText() for visible text, and getByTestId() for an intentionally assigned test identifier. CSS and XPath selectors are available, but selectors coupled to generated classes or fragile DOM structure tend to break when the UI is refactored. The Playwright best practices recommend user-facing locators where practical.

Use web-first assertions so the test waits for the UI condition instead of checking too early:

await expect(page).toHaveURL(/\/account/);
await expect(page).toHaveTitle(/Account/);
await expect(page.getByRole('status')).toHaveText('Saved');
await expect(page.getByRole('button', { name: 'Continue' })).toBeEnabled();

Do not use fixed sleeps such as waitForTimeout(5000) to guess when a page is ready. Wait for a meaningful state: a locator to become visible, a URL change, a response, or a specific application condition. Playwright also waits for actionability before actions such as clicking, including conditions such as visibility and stability.

4. Add configuration and browser projects

Playwright configuration controls test discovery, shared options, timeouts, reporters, and projects. Here is a runnable TypeScript configuration for Chromium, Firefox, and WebKit:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  expect: { timeout: 5_000 },
  fullyParallel: true,
  reporter: [['list'], ['html', { open: 'never' }]],
  use: {
    baseURL: 'https://playwright.dev',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

With baseURL configured, a test can navigate using a relative path such as await page.goto('/'). Projects are named configurations: they can represent browser engines, branded Chrome or Edge, or emulated mobile and tablet devices. Select coverage that reflects the browsers and screen sizes your application supports; running every configuration on every code change is a policy choice, not a requirement.

Common configuration options include:

  • testDir and testMatch: where tests live and which filenames are discovered. Typical names are *.spec.ts or *.test.ts.
  • use: shared browser context options such as baseURL, viewport, locale, screenshot, video, and trace behavior.
  • projects: browser and device configurations, plus project dependencies when one project must prepare state for another.
  • reporter: console, HTML, or other configured result reporting.
  • workers, fullyParallel, and retries: control concurrency and retry behavior. Tune these to the suite and environment.

See projects, test configuration, and the browser guide for supported settings and device profiles.

5. Run tests locally

Playwright runs tests headlessly by default. The main commands are:

# Run the configured suite
npx playwright test

# Run one file
npx playwright test tests/get-started.spec.ts

# Run tests whose title matches a pattern
npx playwright test --grep "installation"

# Run one browser project
npx playwright test --project=chromium

# Show the browser while tests run
npx playwright test --headed

# Open interactive UI mode
npx playwright test --ui

# Open the HTML report from the most recent run
npx playwright show-report

Use --help to see CLI options available in your installed version:

npx playwright test --help

For step-by-step inspection, npx playwright test --debug launches the Playwright Inspector. UI mode is useful for selecting individual tests and inspecting steps; headed mode simply makes the browser visible. Refer to running and debugging tests and the command-line reference.

6. Run the suite in continuous integration

A baseline CI sequence is: install the lockfile dependencies, install the matching browser binaries and Linux dependencies, then run the tests. For npm, the commands are:

npm ci
npx playwright install --with-deps
npx playwright test

A minimal GitHub Actions workflow can look like this:

name: Playwright tests

on:
  push:
  pull_request:

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Use your repository’s lockfile and supported Node version. The Playwright CI guidance recommends one worker for stability and reproducibility on CI; larger systems can distribute work across jobs with sharding. A self-hosted runner with more capacity may support additional parallelism, but measure the effect on reliability and total completion time. Avoid assuming browser binary caching will always save time: cache restoration can take as long as downloading, and Linux system dependencies cannot be cached in the same way. If running headed browsers on Linux, Xvfb is required; the Playwright Docker image and GitHub Action include it.

For provider-specific setup, report artifact handling, sharding examples, and more CI guidance, see the official continuous integration guide.

7. Debug a failing test

  1. Re-run only the failing file or test title to reduce noise: npx playwright test tests/cart.spec.ts --grep "adds an item".
  2. Run with --ui or --debug to inspect actions, locators, and page state. Use --headed when simply seeing the browser is enough.
  3. Open the HTML report with npx playwright show-report and inspect the failing step and its attachments.
  4. Check whether the test is waiting for a real UI condition or relying on a fixed delay. Replace timing guesses with a locator assertion or another state-based wait.
  5. If the browser will not launch on CI, print launch diagnostics with DEBUG=pw:browser npx playwright test.

Traces, screenshots, and videos can be configured in use to retain evidence when failures occur. For example, trace: 'on-first-retry' captures trace data after a test first fails and is retried. Keep artifact retention appropriate for the sensitivity of pages and test data.

8. Manage speed and reliability

Playwright runs test files in parallel by default, while tests in a file run in order unless parallel execution is configured. Parallel workers shorten elapsed time when there is spare CPU and memory, but can slow the machine or create collisions when tests share accounts, records, or rate-limited services. Use isolated test data and tune worker count to available capacity. The parallelism guide describes workers and sharding.

Retries can reveal intermittent failures, but should not hide them. When a test fails and is retried, Playwright discards the worker and starts a new one. Track flaky results and investigate timing, shared state, network dependencies, and environment differences. A passing retry is useful diagnostic information, not proof that the original failure was harmless. See retries.

  • For faster local feedback: run the relevant file or use --grep, then run broader browser coverage before merging.
  • For stable CI: begin with one worker; add parallelism or shard only when the CI system and test data support it.
  • For predictable results: pin dependencies with a lockfile, install matching browser builds, and avoid shared mutable test accounts.
  • For useful failure evidence: retain reports and configure traces or screenshots on failure or retry.

Browser tests consume compute and can incur CI runtime costs. Their cost depends on suite length, browser coverage, worker count, and the CI provider’s pricing; there is no universal best worker count. Start with the coverage users need, then use observed run duration and failure patterns to decide where more parallelism or sharding is worthwhile.

9. Common errors and fixes

Symptom Likely cause Fix
No tests found The file is outside testDir, does not match testMatch, or the command path/pattern is wrong. Check playwright.config.ts, use a .spec.ts or .test.ts filename, and run npx playwright test --list.
Browser executable is missing The package is installed but its matching browser binary is not. Run npx playwright install, or npx playwright install --with-deps on Linux CI.
Click times out because the locator is not actionable The element is hidden, covered, moving, disabled, or the locator matches the wrong element. Inspect the page in UI or debug mode; improve the locator and wait for the intended state rather than adding a fixed sleep.
Strict mode reports multiple matches The locator identifies more than one element. Use a more specific role/name or scope the locator to a containing region. Use first() only when the ordering is part of the intended behavior.
Test passes locally but fails in CI Different browser binaries, missing OS dependencies, resource pressure, timezone or locale differences, or shared data. Install browsers with --with-deps, align versions and settings, reduce workers to diagnose, and isolate test data.
Test is flaky after adding a retry A race, external dependency, shared state, or timing assumption remains. Inspect the trace and retry result; replace sleeps with state-based waits and remove test coupling.
Navigation or assertion times out The destination is slow, unavailable, blocked, or the asserted condition never occurs. Check the URL and application state, distinguish navigation readiness from the UI condition you need, and inspect the report or trace.
Browser launch fails on Linux Required system libraries are absent or headed execution lacks a display server. Use npx playwright install --with-deps; for headed Linux runs, provide Xvfb or use an environment that includes it.

10. Capture a visual reference without maintaining browser setup

Playwright is the right fit when the test must interact with your application and assert behavior. If you need a clean screenshot of a URL for documentation, review, or a visual reference, ScreenshotNeo provides a website screenshot API and MCP server. Its request options and setup are documented at ScreenshotNeo docs.

Or skip the browser setup

This one GET request returns an image or PDF for the target URL. The examples save a WebP response; use the documented output options when you need PNG, JPEG, or PDF.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(async fs => {
  await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
});

Cookie banners are accepted and removed before the shot, along with known newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers say the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, or any MCP client. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. All features are on every plan.

Sign up for 1,000 free screenshots a month, with no card required.

11. Frequently asked questions

Can I use Playwright Test with JavaScript?

Yes. The test runner supports JavaScript and TypeScript. The setup wizard can scaffold either; adapt imports and file extensions to your project.

Does Playwright Test run a real browser?

Yes. It launches supported browser builds such as Chromium, Firefox, or WebKit according to the configured projects.

Should I use browser tests for every UI detail?

Use browser tests for user-visible flows and integration behavior that needs a real browser. Keep narrower logic checks at the appropriate lower level so the browser suite stays focused and maintainable.

Can I run only one browser in CI?

Yes. Select a project with --project, or define a CI job that runs the project appropriate for that stage. Broader browser coverage can run on a separate schedule or job.

Further reading