Playwright scripts follow a dependable pattern: launch a browser, create a page, navigate, interact through resilient locators, assert an observable result, and close the browser. The examples below show both the standalone Playwright Library API and the @playwright/test runner, plus waiting, network interception, screenshots, debugging, and failure recovery.
Install Playwright and choose an execution style
Use the library when you need a small automation program with explicit browser lifecycle control. Use the test runner when you want fixtures, parallel test execution, retries, web-first assertions, reports, and interactive debugging.
Standalone Library setup
mkdir playwright-examples
cd playwright-examples
npm init -y
npm install playwright
npx playwright install
The install command adds the Node package; npx playwright install downloads the browser binaries. Chromium, Firefox, and WebKit are available. Keep the package and browser versions aligned by installing them from the same Playwright release.
Test-runner setup
npm init playwright@latest
Accept the prompts to create a project, choose JavaScript or TypeScript, and decide whether to add a workflow file. The generated project includes playwright.config, a tests directory, and scripts for headed, headless, and report runs.
#1 Best Overall
A complete standalone browser script
This CommonJS example demonstrates the complete lifecycle. It uses Firefox, but you can replace firefox with chromium or webkit.
const { firefox } = require('playwright');
(async () => {
const browser = await firefox.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
});
const page = await context.newPage();
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByRole('link', { name: 'More information' }).click();
console.log('Destination:', await page.url());
} finally {
await browser.close();
}
})();
browser is the process, context is an isolated browser profile, and page is a tab. A context is the right place for cookies, permissions, locale, timezone, and proxy settings. The finally block closes the browser even when navigation or an interaction fails.
A test-runner example with an assertion
A test should prove an outcome, not merely perform a click. This example uses illustrative credentials; do not use them for a real account.
import { test, expect } from '@playwright/test';
test('sign-in form accepts credentials', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('User Name').fill('John');
await page.getByLabel('Password').fill('secret-password');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome, John!')).toBeVisible();
});
The page fixture is created and disposed by the runner. Run it with npx playwright test. To see the browser, use npx playwright test --headed; to run one file, add its path.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Choose locators that survive UI changes
Locators are evaluated when an operation runs, so they can follow a framework rerender. Prefer selectors that describe what a user sees:
Rank #2
getByRole()for buttons, links, headings, checkboxes, and other accessible controls.getByLabel()for form fields associated with a visible label.getByText(),getByPlaceholder(), andgetByAltText()when those values are meaningful and stable.getByTestId()when your team has deliberately made a test contract such asdata-testid="status".
await page.getByRole('button', { name: 'Add to cart' }).click();
await page.getByLabel('Quantity').fill('2');
await page.getByPlaceholder('Search products').fill('keyboard');
await expect(page.getByTestId('cart-count')).toHaveText('1');
Avoid long CSS or XPath chains tied to nesting, generated class names, or visual layout. CSS and XPath remain useful for legacy pages, shadow-DOM boundaries, or elements without an accessible contract, but they should be the exception. If several controls share a role, narrow the locator with filter, hasText, or a parent locator rather than relying on “the third button.”
Actions and web-first assertions
Playwright actions wait for an element to be actionable, while web-first assertions retry until the condition is met or the assertion timeout expires. The documented default assertion timeout is five seconds.
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByTestId('status')).toHaveText('Submitted');
await expect(page.getByRole('heading', { name: 'Thank you' })).toBeVisible();
Use assertions for the state the user or another system can observe: visible text, URL, a checked box, a count, or an enabled control. A single immediate DOM read can race an asynchronous update.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWhen a click opens a new page
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open report' }).click();
const popup = await popupPromise;
await expect(popup).toHaveTitle(/Report/);
When navigation is expected
await Promise.all([
page.waitForURL('**/dashboard'),
page.getByRole('button', { name: 'Continue' }).click(),
]);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
Do not add a fixed sleep as the primary synchronization method. If an application has a real readiness signal, wait for that selector or assert the resulting state.
Waiting for asynchronous page state
Navigation options such as waitUntil: 'domcontentloaded' wait for the initial document, not necessarily for client-rendered data. Prefer a condition tied to the feature under test.
Rank #3
await page.goto('https://example.com/products');
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
await expect(page.getByTestId('product-card').first()).toBeVisible();
For a one-off diagnostic, page.waitForSelector() can wait for a selector, and page.waitForLoadState() can wait for a load state. Keep the timeout close to the operation that genuinely needs more time rather than making every action slow.
await page.getByRole('button', { name: 'Load more' }).click();
await page.waitForSelector('[data-testid="loading"]', { state: 'hidden' });
await expect(page.getByTestId('product-card')).toHaveCount(20);
Mock, inspect, or block network requests
Routes let a test observe traffic, replace a response with fixture data, modify a real response, or abort selected requests. Install the route before navigation so the initial request is intercepted.
Recommended Free Tools
Replace an API response with fixture data
import { test, expect } from '@playwright/test';
test('renders mocked products', async ({ page }) => {
await page.route('**/api/products', route => route.fulfill({
json: [{ id: 1, name: 'Product 1' }],
}));
await page.goto('https://example.com/products');
await expect(page.getByText('Product 1')).toBeVisible();
});
Modify or inspect a live response
await page.route('**/api/profile', async route => {
const response = await route.fetch();
const json = await response.json();
json.plan = 'test-plan';
await route.fulfill({ response, json });
});
Abort unwanted resources
await page.route('**/*', route => {
const type = route.request().resourceType();
if (type === 'image' || type === 'font') return route.abort();
return route.continue();
});
Mocking makes a UI test deterministic and fast, but it does not verify that the real service returns the expected contract. Keep separate integration coverage for the live API.
Capture evidence from a Playwright run
Playwright can save a screenshot or trace from a test. Capture after the assertion that defines the useful state.
await expect(page.getByTestId('status')).toHaveText('Submitted');
await page.screenshot({ path: 'artifacts/submitted.png', fullPage: true });
For a test-runner project, enable tracing around a scenario when diagnosing intermittent failures, then inspect the trace with the Playwright tooling. The HTML Reporter shows each test, steps, attachments, and failure details.
Or skip the browser setup
If the goal is a clean screenshot rather than interactive browser assertions, ScreenshotNeo returns an image or PDF from one HTTP request. Its preprocessing accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorscURL
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(`${res.status} ${res.statusText}`);
const body = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', body);
See the parameter reference and response details in the ScreenshotNeo documentation. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every plan includes the full feature set, including full-page and lazy-image capture, CSS-selector element shots, device presets, custom JavaScript and CSS, request blocking, cookies and headers, caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, and a usage API.
There is no card requirement for the free allowance of 1,000 screenshots per month. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it.
Debug failing scripts interactively
Use headed mode and the Inspector
npx playwright test --headed
PWDEBUG=1 npx playwright test tests/login.spec.ts
UI Mode and Inspector let you pause, step through actions, inspect locators, view the DOM snapshot, and examine calls and network activity. They are particularly useful when a selector matches zero elements or the page is in a different state than expected.
Read the HTML report
npx playwright show-report
Open the report after a run to inspect the failing step, error message, attachments, and (when enabled) trace. Save screenshots, videos, or traces only for useful states to keep artifacts manageable.
Common failures and precise fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable not found | The package is installed but browser binaries are missing. | Run npx playwright install; in a Linux CI image, install the required system dependencies as well. |
| Locator resolves to zero elements | The page is not at the expected state, the accessible name differs, or the control is inside a frame. | Pause with Inspector, verify the URL and visible name, then use the correct role/label or frameLocator(). |
| Strict-mode violation | A locator matches multiple elements. | Make the locator unique with a meaningful name, filter(), or a test ID; use first() only when order is intentional. |
| Timeout after a successful click | The assertion checks the wrong outcome or the app is waiting on an API that failed. | Inspect the trace and network log, assert the post-click state, and mock the dependency when the test is intended to be deterministic. |
| Flaky dynamic content | A fixed delay races the application. | Replace the sleep with a web-first assertion, URL wait, response wait, or a loading indicator transition. |
| Mock never runs | The route was registered after navigation or the pattern does not match the actual URL. | Register before goto and inspect request.url(); include query strings with a glob such as **/api/products**. |
Make examples reliable in CI
- Use isolated contexts and test data; do not let one test depend on another test’s cookies or database state.
- Set explicit viewport, locale, timezone, and permissions when the UI changes with those values.
- Keep assertions close to the action that should produce the state.
- Use retries as a diagnostic safety net, not as a substitute for fixing synchronization.
- Mock unstable third-party services and retain a smaller set of tests against the real integration.
- Store traces or screenshots on failure, then remove old artifacts in CI to control storage.
Parallel workers improve throughput but can expose shared-account and shared-record races. Give each worker independent data or serialize the tests that intentionally share state.
Useful patterns to adapt
Upload a file
await page.getByLabel('Profile photo').setInputFiles('fixtures/avatar.png');
await expect(page.getByText('avatar.png')).toBeVisible();
Handle a dialog
page.on('dialog', async dialog => {
if (dialog.type() === 'confirm') await dialog.accept();
else await dialog.dismiss();
});
await page.getByRole('button', { name: 'Delete' }).click();
Reuse an authenticated state
await page.context().storageState({ path: 'auth.json' });
Generate that state in a controlled setup step and protect the file because it can contain active cookies. Load it in a new context with browser.newContext({ storageState: 'auth.json' }).
FAQ
Should I write a library script or a test?
Choose a library script for one-off automation or a service with its own lifecycle. Choose the test runner when you need fixtures, assertions, reporting, retries, and repeatable suites.
Can Playwright test APIs without opening a browser?
Yes. The test runner includes an API request fixture for direct HTTP calls, while browser routes are best when you need to control what a page’s fetch or XHR receives.
Free tools Windows power users keep installed
One-click scans. No signup required.
How do I keep credentials out of examples?
Use environment variables or a secret store, create dedicated test accounts, and never commit storage-state files or real passwords.
Which browser should run in CI?
Run the browser engine that matches your users when compatibility matters. Chromium is a common baseline, while Firefox and WebKit runs reveal engine-specific behavior.
Frequently Asked Questions
What is the smallest useful Playwright script?
Launch a browser, create a page, call goto, perform one locator-based action, assert a result when correctness matters, and close the browser in a finally block.
Why is a role locator better than a CSS class?
A role with an accessible name reflects the control’s user-facing contract and is less coupled to framework-generated class names or DOM nesting.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When should I mock a request?
Mock it when the test is about rendering or interaction and a live dependency would add nondeterminism; retain separate integration coverage for the real service.
Quick Recap
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.

