October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Write Playwright Scripts for Websites: Actions, Locators, Assertions, and Codegen

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Write a useful Playwright website script as a short, observable workflow: open the page, locate a control the way a user would, perform an action, and assert the result. Playwright waits for elements to become actionable and its web-first assertions retry until the expected state appears, so ordinary tests do not need arbitrary sleep calls.

This guide shows a complete JavaScript Playwright Test example, explains locator choices, demonstrates Codegen, covers setup and browser selection, and includes fixes for the failures that make website scripts flaky.

The basic shape of a Playwright website script

A script is valuable when it proves an outcome, not merely when it clicks through a page. The core sequence is:

  1. Navigate to the page under test.
  2. Find a control with a resilient locator.
  3. Interact with it.
  4. Assert an observable result such as a heading, URL, text, visibility, or form value.

Here is a complete Playwright Test example in JavaScript:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test, expect } from '@playwright/test';

test('site navigation works', async ({ page }) => {
  await page.goto('https://example.com/');
  await page.getByRole('link', { name: 'Get started' }).click();
  await expect(
    page.getByRole('heading', { name: 'Getting started' })
  ).toBeVisible();
});

Replace the URL and accessible names with the ones used by your site. page.goto opens the page, getByRole identifies the link using its user-facing role and name, click performs the action, and toBeVisible verifies the result. If the link or heading is slow to appear, Playwright’s actionability checks and web-first assertion retrying handle the wait in the normal case.

Set up Playwright before writing the script

Install the test runner and browsers

Use the current Playwright installation guidance for your operating system and runtime because supported versions change. In a Node.js project, a typical setup installs the test package and then downloads the browser binaries:

npm install -D @playwright/test
npx playwright install

The browser download is separate from the package installation. Run it in every clean CI environment, or use your CI image’s documented Playwright setup. Do not copy an old operating-system or runtime compatibility table into a long-lived build script; check the live installation documentation when upgrading.

Create a first test file

Save the example as tests/navigation.spec.js. Run it with:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npx playwright test tests/navigation.spec.js

For a visible browser while developing, use headed mode:

npx playwright test tests/navigation.spec.js --headed

Use the project configuration to select the browsers your product supports. Playwright’s documented browser choices include Chromium, Firefox, and WebKit; there is no universal best choice. Match the set to your users, release risk, and CI budget.

Choose locators that survive UI changes

Playwright’s documentation calls locators “the central piece of Playwright’s auto-waiting and retry-ability.” A locator describes the element at the moment an action or assertion runs, rather than storing a fragile reference to a previous DOM state.

Prefer user-facing roles

await page.getByRole('button', { name: 'Save changes' }).click();
await expect(page.getByRole('status')).toHaveText('Saved');

Roles and accessible names reflect what a keyboard or screen-reader user can identify. They also make a test’s intent clear to the next maintainer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use labels and visible text for content controls

await page.getByLabel('Email address').fill('[email protected]');
await page.getByText('Continue').click();

getByLabel is appropriate for inputs associated with a visible label. getByText can be useful for a distinct piece of content, but avoid matching a short word that appears in several unrelated places.

Use test IDs as an explicit contract

await page.getByTestId('checkout-submit').click();

A test ID is a good choice when the team intentionally maintains it as a testing contract, especially for repeated or highly dynamic widgets. Keep the attribute stable and meaningful.

Use CSS and XPath only when their coupling is acceptable

await page.locator('[data-state="open"]').click();
await page.locator('xpath=//button[contains(., "Continue")]').click();

CSS and XPath remain available, but selectors tied to DOM nesting, generated classes, or presentation details tend to break during harmless redesigns. If you must use one, anchor it to a stable attribute and keep the selector short.

Write assertions that prove the intended outcome

Visibility and text

await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByRole('status')).toContainText('Signed in');

URL and navigation

await expect(page).toHaveURL(//account/overview/);

Form state

await expect(page.getByLabel('Email address')).toHaveValue('[email protected]');
await expect(page.getByRole('checkbox', { name: 'Subscribe' })).toBeChecked();

Prefer these web-first assertions to reading a value once and comparing it manually. They wait for the condition and produce a useful failure when it never becomes true. A fixed delay such as waitForTimeout(3000) merely guesses how long a page will take and makes a fast test slower while still failing on a slow run.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Record a workflow with Playwright Codegen

Codegen can open a browser and an Inspector while you perform the workflow. It generates actions and assertions, and prioritizes role, text, and test-ID locators.

npx playwright codegen https://example.com/

Use the Inspector to click through the scenario, then copy the generated test into your project. The CLI supports JavaScript, Playwright Test, and Python targets, as well as Chromium, Firefox, and WebKit. Choose the language and browser that match your application rather than assuming one target covers every environment.

Review generated code before committing it

  • Replace a broad text match with a role, label, or explicit test ID if several elements can match.
  • Delete exploratory clicks that are not part of the behavior you want to protect.
  • Keep the assertion that proves the business outcome, not just the final click.
  • Remove accidental waits and make test data deterministic.
  • Check that the generated locator still expresses the user-visible contract after a design change.

Codegen is a fast first draft, not a substitute for deciding what the test should guarantee.

Build a multi-step website script

The following example combines a form, navigation, and an assertion. It uses explicit labels and roles so each step remains readable:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test, expect } from '@playwright/test';

test('a user can search for a product', async ({ page }) => {
  await page.goto('https://example.com/shop');

  await page.getByRole('searchbox', { name: 'Search products' })
    .fill('headphones');
  await page.getByRole('button', { name: 'Search' }).click();

  await expect(page.getByRole('heading', { name: /headphones/i }))
    .toBeVisible();
  await expect(page).toHaveURL(/search/);
});

Use a regular expression for harmless capitalization or additional wording, but keep it specific enough to fail when the wrong page is shown. If the application performs a network request without a full navigation, assert the updated content rather than waiting for a URL that will never change.

Handle timing, popups, and dynamic pages

Wait for a meaningful condition

Use a locator assertion such as toBeVisible, toHaveText, or toHaveValue. For a known application state that is not directly visible, wait for a specific selector or a documented load condition rather than an arbitrary delay.

Work with a new tab or popup

const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open receipt' }).click();
const popup = await popupPromise;
await expect(popup).toHaveTitle(/Receipt/);

Start waiting for the event before the click so a fast popup cannot be missed.

Deal with consent dialogs deliberately

If a cookie dialog is part of the real user journey, locate its buttons by role and name and accept or reject it explicitly. If it is test infrastructure that should be handled consistently, put that logic in a fixture or setup hook. Do not hide a dialog with a brittle global CSS selector while claiming the test covers the consent experience.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use network controls sparingly

Mocking or blocking requests can make a test deterministic, but it can also conceal integration failures. Keep at least a smaller set of tests that exercises the real backend, and document every mocked response.

Make scripts reliable in CI

  • Use deterministic accounts and seed data; do not depend on another test’s order.
  • Keep each test focused on one user outcome so failures identify a specific regression.
  • Run the same browser projects locally and in CI when practical.
  • Save traces, screenshots, or videos on failure using your Playwright configuration so a failed run can be diagnosed without reproducing it immediately.
  • Give navigation and assertion timeouts enough room for your CI environment, but fix slow application behavior instead of masking it with very large values.
  • Install the exact browser binaries expected by the project after dependency changes.

Retries can distinguish a transient infrastructure failure from a repeatable product defect, but a test that passes only after repeated retries is a maintenance signal, not a success criterion.

Troubleshoot common failures

“Locator resolved to multiple elements”

Cause: the locator is too broad. Fix: use a role with an accessible name, a label, a parent scope, or a maintained test ID. Avoid adding an arbitrary “first” match unless position is genuinely the behavior being tested.

“Element is not visible” or “not actionable”

Cause: the element is hidden, covered, disabled, or still transitioning. Fix: assert visibility or enabled state, close the overlay through its real control, and wait for the meaningful state. Do not default to force: true; it bypasses checks and can make a test pass without reproducing a user action.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Timeout while waiting for text

Cause: the assertion names the wrong copy, the request failed, or the page is in a different state. Fix: inspect the failure trace, verify the URL and locator, and assert the stable portion of the result. If the text is genuinely asynchronous, keep the web-first assertion rather than inserting a sleep.

Browser executable is missing

Cause: package installation completed but browser binaries were not downloaded in the current environment. Fix: run npx playwright install as part of local or CI setup and verify that the runner has permission to use its cache.

Codegen produced a fragile selector

Cause: the recorded page had ambiguous text or unstable markup. Fix: edit the generated file to use a semantic role, label, or stable test ID, then run the test against a page variant that exercises the intended contract.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

When your goal is a clean image or PDF rather than an interaction test, ScreenshotNeo provides a single HTTP request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server also exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

cURL

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

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

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 failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

See the ScreenshotNeo API documentation for request options. The service supports full-page captures with lazy images loaded, CSS-selector element shots, dark mode, 12 device presets or any viewport, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS rendering, custom JavaScript and CSS, clicks before capture, hidden selectors, waits for a selector, delay or network idle, ad/tracker/request/resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Common screenshot-API parameter names also work, easing migration.

Plan Included shots Price
Free 1,000 per month $0; no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Yearly billing gives two months free, and every feature is available on every plan. Start with 1,000 free screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Playwright or a screenshot API?

Choose Playwright when you must interact with controls, submit forms, verify application state, or run the same user journey across Chromium, Firefox, and WebKit. Choose ScreenshotNeo when the deliverable is a screenshot or PDF and you want capture-side cleanup, HTTP automation, or MCP access without maintaining browser installation and orchestration. They can also complement each other: Playwright validates behavior, while an API capture step produces consistent visual assets after the behavior passes.

Frequently Asked Questions

Can Playwright scripts run without the Playwright Test runner?

Yes. Playwright’s browser automation library can be used from application code, while Playwright Test adds fixtures, assertions, projects, retries, and reporting. Use the runner when you are writing repeatable tests; use the library directly when browser control is part of a larger program.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which browser should I use for a first website script?

Start with the engine that matches your primary users and CI environment. Add Chromium, Firefox, or WebKit projects when cross-browser coverage is a real requirement; the documentation does not establish one universal default.

How should I test a page whose content changes on every run?

Assert stable contracts: roles, labels, headings, URL patterns, required status text, or known item attributes. Seed or isolate test data when possible, and avoid exact assertions against timestamps, randomized IDs, or rotating marketing copy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.