Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsThe fastest way to write a useful Playwright script is to build one complete user flow: install Playwright and its browser binaries, open a page, locate controls by the way a user sees them, perform an action, assert the visible result, and close the browser. This guide uses JavaScript with Node.js and shows both a standalone script and a Playwright Test version, plus debugging, locator, waiting, isolation, and failure-recovery practices.
What you will build
The example below opens a browser, visits a page, clicks a link, verifies that the destination is visible, and closes the browser. The URL and link name are deliberately simple; replace them with controls and outcomes from your own application. A test is valuable only when it can fail if the user-facing behavior is broken.
- Standalone script: you control browser startup and cleanup yourself.
- Test runner: Playwright Test manages fixtures, contexts, assertions, reporting, and lifecycle.
Playwright can launch Chromium, Firefox, and WebKit from a Node.js script. The examples use Chromium unless a command says otherwise.
Install Playwright and its browsers
Start a Node.js project
- Install a current Node.js release and create a directory for the automation.
- Run
npm init -yinside that directory. - Install the library with
npm install playwright. - Download the browser binaries with
npx playwright install. Without this step, the package may be installed while the executable needed to launch a browser is missing.
For a test suite rather than a one-off script, install the runner with npm init playwright@latest. Follow the prompts for JavaScript or TypeScript, test-folder location, and whether to add a continuous-integration workflow. The generated project includes a configuration file and an example test.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Write a minimal standalone JavaScript script
Create smoke.js
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.getByRole('link', { name: 'More information' }).click();
await page.getByRole('heading', { name: /iana/i }).waitFor();
console.log('The destination heading is visible.');
} finally {
await browser.close();
}
})();
Run it with node smoke.js. The try/finally ensures the browser closes even when navigation, clicking, or verification throws an error. In your application, replace the illustrative URL, accessible link name, and heading with the real user journey.
Use a visible assertion instead of a log
A log confirms only that execution reached that line. A web-first assertion waits and retries until the expected condition is true, or fails with a useful timeout. Add the assertion library and use it in a script like this:
const { chromium, expect } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.getByRole('link', { name: 'More information' }).click();
await expect(page.getByRole('heading', { name: /iana/i })).toBeVisible();
} finally {
await browser.close();
}
})();
Do not replace this with expect(await locator.isVisible()).toBe(true). That expression checks once, immediately, and can race a UI that is still rendering.
Choose locators that survive UI changes
Locators express how a user identifies an element. Prefer, in this order, a role and accessible name, a label, visible text, or a deliberate test id that represents a stable contract.
Rank #2
Role and accessible name
await page.getByRole('button', { name: 'Save changes' }).click();
await page.getByRole('textbox', { name: 'Email address' }).fill('dev@example.test');
Labels and text
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD);
await expect(page.getByText('Profile updated')).toBeVisible();
Test ids for deliberate contracts
await page.getByTestId('cart-submit').click();
Use a test id when the accessible wording is not stable or when a component has no useful semantic role. Avoid generated CSS classes, long XPath expressions, and selectors tied to deep DOM structure. Those selectors describe implementation details rather than the behavior an end user depends on.
Chain and filter to narrow a component
const row = page.getByRole('listitem').filter({ hasText: 'Ada Lovelace' });
await row.getByRole('button', { name: 'Remove' }).click();
Locators auto-wait and retry actionability checks. A click therefore waits for the element to be attached, visible, enabled, and ready to receive the action.
Navigation, actions, and assertions in a realistic flow
Keep setup, action, and outcome distinct so a failure tells you what broke.
const { chromium, expect } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
locale: 'en-US'
});
const page = await context.newPage();
await page.goto('https://your-app.example/login', {
waitUntil: 'domcontentloaded',
timeout: 30_000
});
await page.getByLabel('Email').fill(process.env.TEST_EMAIL);
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD);
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await page.getByRole('link', { name: 'Settings' }).click();
await expect(page).toHaveURL(//settings$/);
await expect(page.getByRole('heading', { name: 'Settings' })).toBeVisible();
} finally {
await browser.close();
}
})();
Keep credentials in environment variables or a test secret store, not in source control. If a test changes server data, create the required data during setup and remove it afterward so another run cannot inherit a hidden dependency.
Turn the flow into a Playwright Test
The runner is usually the better choice for a maintainable end-to-end suite because it creates isolated browser contexts, provides fixtures and web-first assertions, and produces reports and traces.
Create a test file
import { test, expect } from '@playwright/test';
test('user can open settings', async ({ page }) => {
await page.goto('https://your-app.example/login');
await page.getByLabel('Email').fill(process.env.TEST_EMAIL!);
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await page.getByRole('link', { name: 'Settings' }).click();
await expect(page).toHaveURL(//settings$/);
});
Run the suite with npx playwright test. Use npx playwright test --headed to watch a browser, npx playwright test --debug to open the inspector, and npx playwright show-report to inspect the HTML report after a run. A test file should describe a user outcome, not merely a sequence of DOM operations.
Standalone script versus runner
| Approach | Best fit | Lifecycle and diagnostics |
|---|---|---|
| Library script | One-off automation, data tasks, or a small smoke check | You launch and close browsers; add your own assertions, retries, and reporting. |
| Playwright Test | Repeatable end-to-end regression tests | Fixtures provide isolated contexts; the runner supplies retries, reports, traces, and parallel execution controls. |
Generate a first draft with Codegen
Run npx playwright codegen playwright.dev to open a browser and the Playwright inspector. Interact with the page; the inspector records actions and proposes locators, prioritizing roles, text, and test ids. If several elements match, it refines the locator.
Generated code is a starting point, not a finished test. Delete accidental clicks, replace selectors that depend on unstable markup, move credentials into environment configuration, and add an assertion for the business outcome. A recording that ends after a click does not prove that the click worked.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Rank #4
Waiting without making tests flaky
Use locator and navigation waits
Playwright automatically waits for locator actions and web-first assertions. Use explicit waits only for a condition that Playwright cannot observe directly, such as a known application event.
await page.getByRole('button', { name: 'Refresh' }).click();
await expect(page.getByRole('status')).toHaveText('Updated');
Avoid arbitrary sleeps such as waitForTimeout(5000). They slow passing runs and still fail when a slower environment needs more time.
Wait for a specific selector or URL
await page.waitForURL('**/checkout');
await expect(page.getByRole('heading', { name: 'Order complete' })).toBeVisible();
Choose the narrowest observable condition that proves the user action completed. Waiting for a generic network-idle state can be misleading on pages with analytics, streaming, or long-polling requests.
Isolation, authentication, and test data
- Give each test its own browser context so cookies, local storage, and permissions cannot leak between tests.
- Keep authentication setup explicit. A reusable signed-in state can save time, but refresh it when permissions or account data change.
- Use dedicated accounts and deterministic records. Tests that depend on another test’s mutations are order-sensitive and difficult to diagnose.
- Test what a user can see or do. Do not make the assertion depend on private implementation state when a visible outcome is available.
For a standalone script, create a fresh context with browser.newContext() rather than reusing a context across unrelated flows. For the runner, use fixtures and project configuration to define the intended browser, base URL, storage state, and retries.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Best Value
Debug common failures
| Symptom | Likely cause | Fix |
|---|---|---|
browserType.launch cannot find an executable |
Browser binaries were not downloaded. | Run npx playwright install; in a restricted CI image, install the browsers during image setup. |
| Locator resolves to several elements | The locator is too broad. | Add the role’s accessible name, chain from a component, or filter by stable text or test id. |
| Timeout while clicking | The element is hidden, disabled, covered, detached, or never rendered. | Check the trace or inspector, use a user-facing locator, and assert the prerequisite state before clicking. |
| Assertion is intermittently false | An immediate boolean check races rendering or an asynchronous update. | Use a web-first assertion such as toBeVisible, toHaveText, or toHaveURL. |
| Works locally but fails in CI | Different viewport, browser, timezone, permissions, data, or timing. | Set required context options explicitly, use deterministic test data, run headed or with traces, and avoid fixed sleeps. |
| Test passes alone but fails in a suite | Shared cookies, storage, server records, or test order. | Use a new context per test and clean up or uniquely namespace created data. |
| Codegen output breaks after a redesign | Generated selectors relied on incidental markup. | Replace them with roles, labels, stable text, or an intentional test id. |
Headed debugging and evidence from a failed run
Switch to headed mode when you need to see overlays, focus, viewport behavior, or a redirect: launch with headless: false in a library script or run npx playwright test --headed. The inspector can pause at an action and inspect locator suggestions. The HTML report and trace viewer show steps, screenshots, console messages, and network context for runner failures. Use those artifacts to identify the first incorrect assumption rather than increasing every timeout.
Python option
Playwright also provides synchronous and asynchronous Python APIs. Install it with pip install playwright, then download browsers with playwright install. For end-to-end tests, the official pytest plugin is the usual route.
from playwright.sync_api import sync_playwright, expect
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com")
page.get_by_role("link", name="More information").click()
expect(page.get_by_role("heading", name="IANA-managed Reserved Domains")).to_be_visible()
browser.close()
Run pytest-based tests with pytest. The same principles apply: user-facing locators, web-first assertions, explicit data, and isolated contexts.
Or skip the browser setup
If your goal is a rendered screenshot rather than interaction or an assertion, ScreenshotNeo can return the image through one HTTP request. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for output formats and options. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Final review checklist
- The script installs both the package and browser binaries.
- Every important action uses a role, label, text, or intentional test id.
- Assertions verify a visible, user-relevant outcome and retry while the UI settles.
- Contexts, credentials, and test data are isolated.
- The flow is useful in headless CI and debuggable with headed mode, the inspector, reports, or traces.
- Cleanup runs even when a step fails.
Frequently Asked Questions
Can Playwright write the entire test for me?
Codegen records interactions and proposes locators, but you must remove accidental actions, stabilize selectors, add assertions, and make authentication and data setup explicit.
Which browser should I launch first?
Start with Chromium for a quick smoke flow, then add Firefox and WebKit projects when your compatibility requirements call for them.
Should I use JavaScript or Python?
Use the language your application and team can maintain. The locator, assertion, isolation, and debugging practices are the same; Python also has synchronous and asynchronous APIs and a pytest plugin.
Recommended Free Tools
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.

