Playwright

Why Playwright has become a strong option for modern web automation and which tasks it handles particularly well.

Updated:

Contents

Playwright controls browsers and tests web applications. In the JavaScript and TypeScript ecosystem, Playwright Test adds a runner, fixtures, assertions, projects, and reports. QA benefits from convenient commands, state isolation, and detailed failure history.

The tool suits user flows, interface state checks, and some API tasks. Automatic waiting does not replace understanding the application: an actionable button does not mean a business process has completed, and a successful HTTP response does not mean its data is correct.

Installation and reproducibility

For a new training project, use npm init playwright@latest. Choose JavaScript or TypeScript and browser installation. Use a Node version supported by the selected release. In a working repository, preserve package-lock.json and install dependencies with npm ci.

Browser build versions are tied to the installed Playwright version. After upgrading the package, update its browsers and check CI system dependencies. An arbitrary global executable can introduce differences between local machines and agents, so document that choice explicitly.

Locators and accessible names

Playwright supports locators by role, label, text, and test ID. A locator describes how to find an element in the page's current state. This is preferable to storing an old DOM node reference that the app may replace during rendering.

Choose attributes according to meaning. For a form, getByLabel connects the test to its accessible label; for a button, getByRole with a name expresses a user action. If several elements match, narrow the container. Automatically choosing the first match can hide ambiguity and click the wrong button.

A standalone example

In the created project, save tests/profile.spec.js. The example uses an in-memory page without a server. Run npx playwright test tests/profile.spec.js. The training rule rejects an empty name and displays success for a nonempty name; no actual persistence occurs.

javascript
const { test, expect } = require('@playwright/test');
test('validates the name after submission', async ({ page }) => {
  await page.setContent(`
    <label>Name <input id="name"></label>
    <button id="save">Save</button><p role="status"></p>
    <script>
      document.querySelector('#save').onclick = () => {
        const name = document.querySelector('#name').value.trim();
        setTimeout(() => {
          document.querySelector('[role=status]').textContent =
            name ? 'Saved' : 'Enter a name';
        }, 100);
      };
    </script>`);
  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByRole('status')).toHaveText('Enter a name');
  await page.getByLabel('Name').fill('Anna');
  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByRole('status')).toHaveText('Saved');
});

Expect an empty-value error followed by success for a valid value. toHaveText retries observation until the condition is met or times out. await is required for asynchronous actions and assertions: omitting it may cause incorrect ordering and unfinished checks.

What the tool actually waits for

Before an action, Playwright checks necessary conditions such as visibility and actionability, depending on the operation. It does not know whether a background calculation, email delivery, or queue processing has completed. Those outcomes require a separate observable condition.

Prefer web-first assertions for dynamic UI. Retrieving text immediately and comparing it with a plain assert checks only one moment. However, do not replace every wait with an enormous timeout: if the event should not occur, waiting longer cannot fix the scenario.

Moving to a real profile

Create a separate test user, open their account area, and submit a name containing only spaces. Expect an error and the original saved value. In another scenario, submit a new valid name, then retrieve the profile through the UI or API according to the test's purpose.

A test using network mocking confirms client handling of the supplied response. Keep a separate integration check without substituting this operation to verify actual persistence. Make the boundary between a controlled UI error and a real backend connection visible in the suite name.

Fixtures and independence

Playwright Test uses contexts to create isolated browser state for tests. This helps with cookies and storage but does not automatically isolate server records. Two contexts using one account can still change the same profile or cart.

Fixtures can prepare users, API clients, and required entities. Choose fixture scope deliberately. A worker-scoped shared resource must be safe for every test in that worker; mutable scenario data is better created separately.

Projects and the browser matrix

Projects describe configurations such as browser, viewport, locale, and other parameters. Run a risk-based matrix rather than automatically testing every possible combination. Confirm the critical flow first, then add areas sensitive to the selected environment.

Mobile emulation does not replace a real phone with its keyboard, permissions, and system integrations. Automated WebKit should also not be presented as full testing of users' Safari. Record coverage limitations alongside results.

Traces and investigation

Trace Viewer helps examine actions and page state around a failure. Save traces according to an agreed policy, such as on retry or failure, and make them accessible from CI. Before publishing, consider which data appears in network records and screenshots.

Start with the first unmet expectation: which request was sent, what returned, and what the UI displayed. Avoid blindly rewriting a locator when the backend is unavailable. A green rerun is a useful observation, but the original failure still needs classification.

For a multi-user scenario, create two separate contexts and label each participant's role in the report. Saving a storage state file to speed login must not merge accounts or expose session data in a public artifact. Confirm that the state belongs to the required environment and remains valid. If authentication setup fails, identify that stage: dozens of later account-area element errors are not independent interface defects.

Mistakes and practical conclusions

Typical problems include missing await, shared mutable accounts, force clicks without investigation, and checking only for a message. Edit generated tests: generators help you start, but do not select risks, data, or success criteria for the team.

A reliable Playwright suite combines isolation, clear locators, explicit business outcomes, and useful artifacts. The tool's quick start is valuable when the team manages server state and coverage boundaries with equal care.

Further reading

A reproducible local exercise

The prepared practice project pins @playwright/test 1.59.1 with a lockfile and uses a local Python fixture. Its seven Chromium cases exercise the nickname form with empty, short, boundary, oversized, whitespace and Unicode inputs. Each case gets its own page and declares the expected result. No external account or production site is involved.

plain text
npm ci
npx playwright install chromium
npm test
# If a test fails and produces a trace:
npx playwright show-trace test-results/<case-directory>/trace.zip

The config starts the local server and retains traces on failure. Locators use the field label and button role so the test communicates user intent. Assertions retry until their condition is met or the timeout expires; arbitrary sleep calls are unnecessary for this synchronous form.

plain text
await page.goto("/demo.html");
await page.getByLabel("Nickname").fill("ab");
await page.getByRole("button", { name: "Validate" }).click();
await expect(page.getByRole("alert")).toHaveText("Use 3–12 ASCII letters.");
await expect(page.getByLabel("Nickname")).toBeFocused();

For a deliberate failure exercise, change the expected error text in one local test, run only that test, inspect the retained trace, and then restore the assertion. A passing test proves this fixture meets these checks; it does not establish that a real product is accessible or comprehensively tested. Add the real workflow and backend assertions when adapting the example.

Continue learning

Download the local practice lab (ZIP)

Investigate flaky tests

Run checks in CI

Playwright: assertions

Playwright: locators