Playwright JavaScript Tutorial
Install Playwright, write resilient JavaScript tests, run every browser, and debug failures with UI Mode and traces.
Playwright JavaScript tests use the @playwright/test runner to open an isolated browser context, perform user actions, and assert the resulting page state. This tutorial starts with a working project, then covers resilient locators, web-first assertions, Chromium/Firefox/WebKit projects, Codegen, UI Mode, traces, CI, reliability, and common failures.
1. Install Playwright for JavaScript
Use the official project generator. It creates the test runner, configuration, starter test, and (optionally) a CI workflow.
npm init playwright@latest
Choose JavaScript when prompted, select a test directory such as tests, decide whether to add GitHub Actions, and allow browser installation. The equivalent commands for other package managers are:
yarn create playwright
pnpm create playwright
Playwright supports JavaScript and TypeScript. Check the current supported Node.js and operating-system versions in the official installation documentation before publishing a CI image. After package updates, reinstall the versioned browser binaries:
npx playwright install
# Linux CI images that need system libraries:
npx playwright install --with-deps chromium
You can install operating-system dependencies separately with npx playwright install-deps. Browser binaries track the Playwright release, so rerun installation after upgrading Playwright.
2. Project files and configuration
A generated project normally contains playwright.config.js, a tests directory, and package scripts. A minimal JavaScript configuration is:
// playwright.config.js
// @ts-check
const { defineConfig, devices } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'https://playwright.dev',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
baseURL lets tests call page.goto('/docs'). Projects let one suite run with different browsers, viewports, devices, or channels. Use trace: 'on-first-retry' to collect a trace when a test first retries in CI without tracing every successful test. Add // @ts-check to JavaScript files for editor type checking without converting them to TypeScript.
3. Write your first end-to-end test
Each test receives a fresh browser context. Cookies, local storage, and page state therefore do not leak between tests unless you explicitly share state.
// tests/docs.spec.js
// @ts-check
const { test, expect } = require('@playwright/test');
test('opens the installation guide', async ({ page }) => {
await page.goto('/docs/intro');
await expect(page).toHaveTitle(/Playwright/);
await expect(page.getByRole('heading', { name: /installation/i })).toBeVisible();
});
Playwright tests perform actions and assert state. Navigation, clicks, filling fields, keyboard presses, option selection, file uploads, and focusing are built-in actions:
test('submits a form', async ({ page }) => {
await page.goto('/signup');
await page.getByLabel('Email').fill('dev@example.com');
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page.getByRole('status')).toHaveText(/created/i);
});
Actions perform actionability checks and wait for an element to be ready. Avoid making waitForTimeout your default synchronization method.
4. Choose resilient locators
Prefer locators that describe how a user identifies an element. Start with:
| Locator | Use it for | Example |
|---|---|---|
getByRole |
Buttons, links, headings, checkboxes and other accessible roles | page.getByRole('button', { name: 'Save' }) |
getByLabel |
Form controls with labels | page.getByLabel('Email') |
getByText |
Visible user-facing text | page.getByText('Welcome') |
getByTestId |
A deliberate testing contract | page.getByTestId('cart-count') |
CSS and XPath selectors can be necessary for legacy markup, but they couple tests to implementation details. Keep a locator narrow enough to identify one intended element and assert the result after an action.
5. Use Codegen as a draft, not a finished test
Codegen opens a browser and the Playwright Inspector. Perform the flow in the browser, review the generated actions and locators, then copy the draft into your suite.
npx playwright codegen https://playwright.dev
Codegen prioritizes role, text, and test-id locators. Rename the test, remove incidental clicks, replace brittle selectors, and add assertions that express the requirement. The Codegen guide documents additional options for device emulation and saved authentication.
6. Write web-first assertions
Async expect matchers poll until the condition is true or the assertion timeout expires. This makes them safer than sleeping and reading the DOM once.
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();
await expect(page.getByRole('checkbox', { name: 'Subscribe' })).toBeChecked();
await expect(page.getByRole('alert')).toBeVisible();
await expect(page.locator('[data-testid="total"]')).toHaveText('$42.00');
Set a longer timeout only for a known slow condition:
await expect(page.getByRole('status')).toHaveText(/processed/i, { timeout: 15_000 });
Use the assertions reference for locator, page, and value matchers. A meaningful assertion is usually the right synchronization point.
7. Run tests locally
# Headless suite
npx playwright test
# One file
npx playwright test tests/docs.spec.js
# One browser project
npx playwright test --project=firefox
# See the browser while learning
npx playwright test --headed
# Open the HTML report
npx playwright show-report
Use a headed run to understand a flow. Keep automation headless unless visual interaction is required. Run all configured projects before merging when browser coverage matters; use --project=chromium for a quick focused loop.
8. Run Chromium, Firefox, and WebKit
Playwright supports Chromium, Firefox, and WebKit, plus branded Chrome and Edge channels and emulated tablet or mobile devices. The projects in the configuration select these environments:
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
Use separate projects when behavior, permissions, locale, timezone, or viewport differs. Keep the test itself browser-neutral unless a documented product requirement is browser-specific.
9. Debug with UI Mode and Trace Viewer
UI Mode is useful while developing locally:
npx playwright test --ui
It provides watch mode, test filtering, live step details, and time-oriented debugging. For CI failures, open the trace produced by the configuration:
npx playwright show-trace path/to/trace.zip
In Trace Viewer, inspect the failed assertion, action timeline, locator, DOM snapshot, console messages, and network information. Fix the locator, synchronization, or test data shown by that evidence instead of adding arbitrary sleeps. The Trace Viewer documentation explains the available panels.
10. CI workflow
The project generator can add a GitHub Actions workflow. Keep generated YAML aligned with the current Playwright release because CI templates change. The essential sequence is:
npm ci
npx playwright install --with-deps
npx playwright test
# upload playwright-report/ and test-results/ as CI artifacts
Run headlessly, upload the HTML report and trace artifacts, and use retries only as a diagnostic aid. A retry should produce evidence for investigation rather than hide a flaky test. Pin the Node.js and Playwright versions used by your team so browser binaries are reproducible.
11. Reliability and performance practices
- Use isolated test contexts and independent test data; never depend on execution order.
- Prefer role, label, text, and test-id locators over long CSS or XPath chains.
- Use web-first assertions instead of fixed delays.
- Keep each test focused on one user outcome and remove incidental setup.
- Run Chromium during rapid local iteration, then exercise configured browser projects in CI.
- Use parallel workers when the environment and test data are safe for concurrent access; reduce workers for shared or rate-limited systems.
- Capture traces on the first retry and screenshots only on failure to control artifact size and runtime.
- Reuse authenticated state only when it is deliberately created and isolated; do not let one test mutate another test’s account.
Playwright itself has no per-test usage charge. Your costs come from the CI machines, browser execution time, and the systems under test. More projects, retries, traces, and parallel workers increase runtime and storage needs.
12. Troubleshooting common errors
| Symptom | Likely cause | Fix |
|---|---|---|
Executable doesn't exist |
Browser binaries were not installed or Playwright was upgraded. | Run npx playwright install; in Linux CI use npx playwright install --with-deps. |
| Timeout waiting for locator | The locator is wrong, the element is not rendered, or the page is still loading. | Inspect the trace or UI Mode, choose a user-facing locator, and assert the state that signals readiness. |
| Strict mode violation | A locator matches multiple elements. | Make it specific with a role name, label, test id, or a narrowed locator; avoid blindly using nth(). |
| Works headed but fails headless | Timing, viewport, environment, or missing dependency differences. | Reproduce with the same project in headed mode, inspect the trace, install CI dependencies, and remove timing assumptions. |
| Flakes after a click | The next assertion races a navigation, animation, or network update. | Use the click followed by a web-first assertion for the resulting URL, role, text, or state. |
| Tests affect one another | Shared accounts, files, cookies, or mutable server data. | Use isolated fixtures and unique data; remember each test’s default context is isolated. |
| Trace or report missing in CI | Artifacts were not retained or the test never reached the configured retry. | Upload playwright-report and test-results; use trace: 'on-first-retry' or a temporary focused trace setting. |
| Browser version mismatch | The package and installed browsers are from different releases. | Install browsers from the same lockfile and rerun the Playwright install command after upgrades. |
13. Or skip the browser setup
If your goal is a reliable screenshot rather than an end-to-end test, ScreenshotNeo provides a single website screenshot API request. See the ScreenshotNeo API documentation for all options.
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}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
14. FAQ
Should a JavaScript Playwright test use CommonJS or ESM?
Use the module style generated for your project. The examples above use CommonJS, which works in a default npm project. If your package has "type": "module", use ESM imports and exports consistently.
Do I need to install Chrome separately?
No. npx playwright install installs the Playwright-managed browser binaries. Branded Chrome or Edge channels are optional project choices.
When should I use a test id?
Use a test id when the accessible name or visible text is not a stable contract. Treat the id as part of the application interface and keep it meaningful.
Why does an assertion pass locally but fail in CI?
Compare the project, browser, viewport, dependencies, data, and timing. A trace usually reveals whether the problem is a locator, synchronization, environment, or server response.
How do I test one browser while editing?
Run npx playwright test --project=chromium, optionally with --headed or --ui, then run the complete project matrix before merging.


