Playwright Overview
Introduction

During the past week I explored Playwright, a framework that lets you perform several kinds of testing such as end-to-end automation and visual regression on web applications. What stands out to me is the essentially zero-configuration setup: the package ships with many useful utilities right away. One of them is codegenwhich records your interactions and produces test code automatically. When you bootstrap a project, Playwright also creates a ready-to-use CI pipeline definition, supports visual snapshots, and can execute tests across Chromium, Firefox, and WebKit. While experimenting, I realized that this tool solved a problem I had encountered earlier, so I decided to share the experience.
Previously I wrote tests with React Testing Librarywhich is great for unit-level component checks. I ended up stretching it beyond its intended scope, writing overly long tests that became flaky – they sometimes passed and sometimes failed without any code change. The flakiness stemmed from the library’s virtual DOM implementation; rapid page refreshes could leave the emulated browser in an inconsistent state, causing false negatives.
If I had been able to write a few Playwright scenarios back then, the tests would have been more reliable and concise. Beyond reliability, Playwright is approachable, offers many debugging aids, includes the codegen helper, deploys easily, supports visual testing, and runs on several browsers. Below I list the reasons I favor this framework and then demonstrate how it works.
Why automated testing matters
Automated testing is a cornerstone of a healthy development workflow because it lets you modify code with confidence that critical functionality will stay intact. Have you ever felt a knot of anxiety after a change, fearing that something essential might break? That unease isn’t just imposter syndrome; it stems from the impracticality of manually covering every possible scenario. While you can never guarantee flawless behavior in every edge case, automated tests dramatically increase the odds that regressions are caught early.
Running the same suite after each change provides repeatable verification. A classic illustration is Google’s internal web-server testing, which demonstrated how automation boosts productivity, reduces bugs, and raises developer confidence. By adopting automated tests you lower stress, keep the application adaptable, and enable faster, safer deployments. When the suite passes, you can ship with assurance.
The following sections describe how Playwright makes writing such tests straightforward and low-maintenance.
Getting started with Playwright
First, ensure you have Node 16 or newer. Install the browsers and Playwright binaries with:
npx playwright install
This command downloads Chromium, Firefox and WebKit binaries needed for test execution. To add Playwright to an existing project, run:
npm init playwright
The command scaffolds a playwright.config.ts file and other starter files, allowing you to adjust defaults such as timeout values, test directory, or reporter format. Create test files ending with .spec.ts inside a tests folder and run them via:
npx playwright test
npx playwright show-report # to view the HTML report
At this point you can start authoring tests. Remember that Playwright drives a real browser, so you have access to browser-level actions like navigation, which differ from pure DOM-only tools like React Testing Library.
A typical Playwright test uses three core commands:
page.goto(url): opens the specified address, similar to typing a URL in a real browser.page.locator(selector): finds elements using CSS-style selectors or accessibility-oriented queries.expect(... ): performs assertions on the located element or page state.
Below is a concise example that adds products to a cart on the demo site automationexercise.com.
import { test, expect } from "@playwright/test";
test("shopping cart flow", async ({ page }) => {
// Open the site
await page.goto("[https://automationexercise.com/"](https://automationexercise.com/"));
// Click the first product link
await page.locator(".features_items .choose a").first().click();
// Dismiss a possible advertisement modal
await page.mouse.click(0, 0);
// Set quantity to 3
await page.locator("#quantity").fill("3");
// Add the item to the basket
await page.locator(".product-information button").click();
// Close the confirmation modal
await page.locator(".modal button").click();
// Verify the modal is no longer visible
await expect(page.locator(".modal")).not.toBeVisible();
});
Step-by-step explanation:
- Navigate to the homepage.
- Locate the first product link inside the feature list and click it.
- Click the top-left corner to dismiss a possible ad overlay.
- Fill the quantity input with the value
3. - Press the Add to cart button.
- Click the button inside the resulting modal to close it.
- Assert that the modal has disappeared.
Playwright automatically waits for navigation, element stability and modal disappearance, so you rarely need explicit waits. This auto-waiting contributes to a smooth developer experience.
Explicit waiting when needed
Sometimes you must pause until a specific condition is true, for example waiting for a dropdown to become visible. Playwright provides locator.waitFor() for such cases. The following test demonstrates waiting for at least one search result to appear on the Playwright documentation site.
test("search yields at least one result", async ({ page }) => {
await page.goto("[https://playwright.dev/"](https://playwright.dev/"));
await page.getByRole("button", { name: "Search" }).click();
const searchBox = page.getByPlaceholder("Search docs");
await searchBox.click();
await searchBox.fill("havetext");
// Wait for the first result entry to become visible
await page.locator(".DocSearch-Dropdown-Container section").nth(1).waitFor({ state: "visible" });
const resultCount = await page.locator(".DocSearch-Dropdown-Container section").count();
await expect(resultCount).toBeGreaterThan(0);
});
The script:
- Opens
playwright.dev. - Clicks the search icon.
- Focuses the search input.
- Types
havetext. - Waits until a result row appears.
- Counts the rows.
- Asserts that the count is above zero.
Tools that accompany Playwright
One of Playwright’s strongest points is the ecosystem of utilities it ships with.
Code generation
Running npx playwright codegen <url> launches a browser that records your actions and prints the corresponding test code. The output can serve as a quick starting point, though you’ll often refine selectors, add regexes, or adjust logic to make the test robust.
Debugging helpers
Playwright offers several flags to make debugging easier:
--headedruns the browser with a UI so you can watch the steps.--slowmo 500inserts a 500 ms delay between actions, letting you follow the flow.--debugopens an interactive session where you can step through the script.--trace onrecords a comprehensive trace of every operation; after a failure you can view the trace withnpx playwright show-reportand explore DOM snapshots, network activity, and selector resolution. See the Trace Viewer documentation.
These facilities simplify locating flaky behavior and fixing it quickly.
Selectors
Playwright supports many strategies for locating elements, allowing you to choose the most semantic way for your application.
getByRole()targets elements based on ARIA roles, which aligns with how assistive technologies interpret the page.getByLabel()finds form controls by their associated<label>text.getByPlaceholder()matches inputs that display a placeholder attribute.getByText()searches for visible text anywhere on the page.getByAltText()locates images via theiraltattribute, useful for accessibility verification.getByTitle()selects elements that have atitleattribute.getByTestId()works with customdata-test-idattributes that developers add solely for testing purposes.
For an exhaustive catalog, consult the official Playwright Locator documentation.
Assertions
Playwright’s expect library provides a rich set of matchers. Below are the most commonly used ones:
toBeTruthy()/toBeFalsy()– verify that a value is truthy or falsy.toBe(value)– strict equality check.toBeDefined()– ensures a variable is notundefined.toBeVisible()/not.toBeVisible()– checks visibility.toHaveText(text)– asserts that an element’s text content matches the expected string.toBeDisabled()– confirms a control is disabled.toHaveURL(url)– validates the current page URL.toHaveAttribute(name, value)– compares an element’s attribute against an expected value.
The .not modifier lets you assert the opposite of any matcher, for example await expect(element).not.toHaveText("Old text");.
For a complete list, refer to the Playwright Test Assertions guide.
Visual testing
Playwright can capture screenshots and compare them against a baseline image, turning visual regression into an automated assertion. The matcher toHaveScreenshot() stores a reference snapshot on the first run; subsequent runs compare the new capture to that baseline and fail if the pixel difference exceeds a tolerance. You can relax the strictness with the maxDiffPixels option.
test("homepage visual check", async ({ page }) => {
await page.goto("[https://playwright.dev"](https://playwright.dev"));
await expect(page).toHaveScreenshot({ maxDiffPixels: 100 });
});
When the test fails, Playwright generates a diff image highlighting the changed regions, making it easy to spot unintended UI changes. See the Screenshots documentation.
Final thoughts
Playwright feels remarkably approachable while offering a breadth of capabilities out of the box. Developers familiar with other UI testing frameworks will find the concepts comfortable, yet the reliability of real-browser execution sets it apart. Automated tests – whether functional, API-level, or visual – provide scalable confidence that core user flows remain solid. For an e-commerce application, a handful of Playwright scenarios covering login, product selection, cart manipulation, and checkout can safeguard the most critical path.
Give Playwright a try; its ease of use, powerful debugging, and cross-browser support can boost productivity and catch regressions early in the development cycle.