mrkeyoor.com_
Wed 05 Aug 05:02 UTC
npmTestingupdated 05 Aug 2026

playwright

Browser automation from Microsoft that drives Chromium, Firefox, and WebKit with one API. The playwright package is the library for scripts (scraping, screenshots, PDFs); the companion @playwright/test package adds a full test runner with parallelism, auto-waiting locators, retrying assertions, and trace capture. Each test runs in a fresh browser context, so state never bleeds between tests. It has become the default choice for end-to-end testing in the JavaScript world, and Microsoft now ships an MCP server and CLI on top of it for AI agents.

Verdict

The current default for browser testing and automation, and it earns that spot: the API is well designed and the tooling around failures (traces, UI mode) is best in class. Budget for the browser-binary weight in CI, and do not reach for it when a plain unit or API test would do.

API stability4/5Steady 1.x releases since 2020 with slow, well-signposted deprecations; but browser builds are pinned per release, so upgrading the library changes the browsers under you whether you asked or not
Docs5/5playwright.dev covers every class and option with runnable examples, a full test-runner guide, and Trace Viewer docs; one of the best documented tools in the JS ecosystem
Maintenance5/5Microsoft-staffed team, pushes on the day of this review, monthly release cadence (1.62.1 shipped July 2026), and only 164 open issues and PRs on a 94k-star repo
Ecosystem5/578M weekly downloads, official Python/.NET/Java ports, a VS Code extension, and first-party MCP and CLI layers for agents; most CI providers document it directly

Use it if

  • You are writing end-to-end tests and want auto-waiting and web-first assertions instead of hand-rolled sleeps and flaky waits
  • You need real cross-engine coverage: Chromium, Firefox, and WebKit all run headless on Linux, macOS, and Windows
  • You are scraping or automating sites that need a real browser: JS rendering, login flows, network interception, PDF generation
  • You want built-in test isolation, parallel execution, and trace/video artifacts on failure without wiring up extra tooling
  • You are building browser tooling for AI agents; the official MCP server and CLI sit directly on this library
Skip it if

Setup reality

npm init playwright@latest scaffolds config, an example test, and a CI file, which is genuinely smooth. The catch is browsers: npx playwright install pulls custom Chromium, Firefox, and WebKit builds that live outside node_modules, so CI needs the install step (plus system deps via install --with-deps on Linux) or the official Docker image, and caching them across builds is your problem. Browser builds are version-locked to the library, so upgrades re-download everything. Also know the split: the playwright package has no test runner; for tests you install @playwright/test and should not mix the two in one project.

Patterns

Write and run a basic testwrite-first-test

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

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

Tests need @playwright/test, not the playwright package; run with npx playwright test.

Find elements with user-facing locatorslocate-elements

await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('ada@example.com');
await page.getByPlaceholder('Search...').fill('printers');
await expect(page.getByTestId('login-form')).toBeVisible();

Prefer getByRole over CSS selectors; locators auto-wait and retry, so no manual waitForSelector.

Log in once and reuse the session across testsreuse-auth-state

// in a setup test, after logging in:
await page.context().storageState({ path: 'auth.json' });

// in your test file:
test.use({ storageState: 'auth.json' });

Saves cookies and localStorage only; sessionStorage and in-memory tokens do not survive.

Mock an API responsemock-network

await page.route('**/api/users', (route) =>
  route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([{ id: 1, name: 'Ada' }]),
  })
);
await page.goto('/users');

Register the route before the navigation that triggers the request, or the real call goes through.

Block images to speed up scrapingblock-resources

await page.route('**/*.{png,jpg,jpeg,webp}', (route) => route.abort());
await page.goto('https://example.com');

Blocking assets cuts load time a lot, but some sites lazy-load content based on image events.

Take a screenshot from a scripttake-screenshot

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://playwright.dev/');
await page.screenshot({ path: 'shot.png', fullPage: true });
await browser.close();

Always close the browser in scripts; orphaned headless processes pile up fast on servers.

Render a page to PDFgenerate-pdf

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.pdf({ path: 'page.pdf', format: 'A4' });
await browser.close();

page.pdf works in headless Chromium only, not Firefox or WebKit.

Emulate a mobile deviceemulate-mobile

import { chromium, devices } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext(devices['iPhone 15']);
const page = await context.newPage();
await page.goto('https://playwright.dev/');

This emulates viewport, user agent, and touch; it is not a real iOS Safari engine.

Wait for a specific network responsewait-for-response

const responsePromise = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Load orders' }).click();
const response = await responsePromise;
const data = await response.json();

Start waitForResponse before the click; awaiting it only afterwards can miss a fast response.

Capture traces for failed CI runstrace-on-failure

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  use: { trace: 'on-first-retry' },
});

Open the resulting zip with npx playwright show-trace; it replays every action, DOM snapshot, and network call.

Debug a failing test interactivelydebug-tests

npx playwright test --ui        # watch mode with time travel
npx playwright test --debug     # headed with inspector
npx playwright codegen https://example.com  # record actions as code

UI mode is usually faster than sprinkling page.pause() calls through the test.

Install browsers in CIinstall-browsers-ci

npm ci
npx playwright install --with-deps chromium

Install only the browsers you test to save time and disk; binaries are version-locked, so re-run this after every playwright upgrade.

Alternatives

PackageRegistryPick it when
cypressnpmYou want an interactive in-browser test-writing experience and accept Chromium-family plus Firefox coverage
puppeteernpmChrome-only automation or scraping where you do not need a test runner or cross-engine support
selenium-webdrivernpmYou must run against real vendor browser installs or a Selenium Grid you already operate