Find out how easy it is to capture and share pixel-perfect screenshots at scale using Allscreenshots. Sign up for a free account and start integrating your first screenshot API call today.
AC
Alex Chen
Full-stack developer and technical writer who loves browser automation and making complex tools accessible.
Playwright is what happens when the team behind Puppeteer rebuilds browser automation from scratch with testing as the primary goal. Developed at Microsoft by many of the same engineers who created Puppeteer at Google, it addresses the pain points that make browser testing frustrating: flaky tests, cross-browser inconsistencies, and complicated setup.
Why Playwright Exists
Puppeteer solved Chrome automation. Selenium solved cross-browser support. Neither solved the fundamental problem: browser tests are often unreliable. Elements load at unpredictable times. Animations interfere with clicks. Network requests race against assertions.
Playwright tackles this with auto-waiting built into every action. When you click a button, Playwright automatically waits for it to be visible, enabled, and stable before clicking. When you assert text content, it retries until the assertion passes or times out. This eliminates most timing-related flakiness without explicit waits scattered through your code.
Core advantages:
Auto-waiting: Every action waits for actionability automatically
Cross-browser: Chromium, Firefox, and WebKit (Safari's engine) from one API
Test isolation: Each test gets a fresh browser context by default
Powerful tooling: Trace viewer, codegen, UI mode for debugging
Pro tip: Use npx playwright codegen to record interactions and generate test code. It's the fastest way to understand Playwright's API and bootstrap new tests.
Getting Started
Install Playwright with its test runner:
npm init playwright@latest
This creates a project structure with example tests and configuration. Run the example tests:
That's it. No browser setup, no driver downloads, no explicit waits. Playwright handles all of it.
The Auto-Waiting Difference
Compare explicit waiting in Selenium versus Playwright's approach:
// Selenium style: manual waits everywhereawait driver.get('https://example.com');await driver.wait(until.elementLocated(By.css('.loaded')),10000);await driver.wait(until.elementIsVisible(element),10000);await element.click();// Playwright style: just describe what you wantawait page.goto('https://example.com');await page.click('.loaded');// Auto-waits for visible, enabled, stable
Playwright waits for these conditions before acting:
Action
Auto-Wait Conditions
click()
Visible, stable, enabled, not obscured
fill()
Visible, enabled, editable
check()
Visible, enabled, unchecked
selectOption()
Visible, enabled
If an element doesn't meet conditions within the timeout (30 seconds by default), the test fails with a clear error explaining what went wrong.
Locators: The Modern Way
Playwright encourages "user-facing" locators that find elements the way users perceive them:
// By role (recommended)await page.getByRole('button',{name:'Submit'}).click();await page.getByRole('textbox',{name:'Email'}).fill('[email protected]');// By text contentawait page.getByText('Welcome back').isVisible();// By labelawait page.getByLabel('Password').fill('secret123');// By placeholderawait page.getByPlaceholder('Search...').fill('query');// By test ID (when you control the HTML)await page.getByTestId('checkout-button').click();// CSS and XPath still work when neededawait page.locator('.legacy-class').click();await page.locator('xpath=//div[@id="root"]').isVisible();
Role-based locators are resilient because they match how assistive technologies see your page. If a button looks like a button to screen readers, getByRole('button') will find it regardless of the underlying HTML implementation.
Avoid chaining multiple CSS classes or using auto-generated class names as locators. These break whenever styling changes. Prefer getByRole, getByTestId, or getByLabel for stable tests.
Assertions That Retry
Playwright's assertions automatically retry until they pass:
// Retries until the element contains "Success" or timeoutawaitexpect(page.getByTestId('status')).toHaveText('Success');// Retries until element is visibleawaitexpect(page.getByRole('dialog')).toBeVisible();// Retries until URL matchesawaitexpect(page).toHaveURL(/dashboard/);// Retries until element count matchesawaitexpect(page.getByRole('listitem')).toHaveCount(5);
This retry behavior is what makes Playwright tests reliable. Instead of failing instantly when an assertion doesn't match, it keeps checking until the condition is true or the timeout expires. Perfect for SPAs where content appears after API calls complete.
Playwright's WebKit support means you can test Safari rendering without a Mac. It's not identical to real Safari, but it catches most cross-browser issues.
Testing Forms and User Flows
Here's a realistic login test:
const{ test, expect }=require('@playwright/test');test('user can log in and see dashboard',async({ page })=>{await page.goto('https://app.example.com/login');// Fill the login formawait page.getByLabel('Email').fill('[email protected]');await page.getByLabel('Password').fill('securepassword');await page.getByRole('button',{name:'Sign In'}).click();// Verify redirect to dashboardawaitexpect(page).toHaveURL(/dashboard/);awaitexpect(page.getByRole('heading',{name:'Welcome'})).toBeVisible();});test('shows error for invalid credentials',async({ page })=>{await page.goto('https://app.example.com/login');await page.getByLabel('Email').fill('[email protected]');await page.getByLabel('Password').fill('wrongpassword');await page.getByRole('button',{name:'Sign In'}).click();awaitexpect(page.getByText('Invalid email or password')).toBeVisible();awaitexpect(page).toHaveURL(/login/);// Should stay on login page});
Network Mocking
Intercept and mock API responses for isolated, fast tests:
test('displays user data from API',async({ page })=>{// Mock the API responseawait page.route('**/api/user',route=>{ route.fulfill({status:200,contentType:'application/json',body:JSON.stringify({id:1,name:'Test User',role:'admin'}),});});await page.goto('https://app.example.com/profile');awaitexpect(page.getByText('Test User')).toBeVisible();awaitexpect(page.getByText('admin')).toBeVisible();});test('handles API errors gracefully',async({ page })=>{await page.route('**/api/user',route=>{ route.fulfill({status:500});});await page.goto('https://app.example.com/profile');awaitexpect(page.getByText('Failed to load profile')).toBeVisible();});
Debugging Failed Tests
Playwright's debugging tools are exceptional:
# Run with headed browser and slow motionnpx playwright test--headed--slowmo=500# Debug mode: step through test with DevToolsnpx playwright test--debug# UI mode: visual test runner with time-travelnpx playwright test--ui
When tests fail in CI, Playwright can capture traces:
// playwright.config.jsmodule.exports={use:{trace:'on-first-retry',// Capture trace on failurescreenshot:'only-on-failure',video:'retain-on-failure',},};
View traces with:
npx playwright show-trace trace.zip
The trace viewer shows every action, network request, and DOM snapshot. You can step through the test and see exactly what the page looked like at each moment.
Playwright hits a sweet spot: it offers cross-browser support like Selenium does, it offers a developer experience approaching Cypress, and reliability that exceeds both.
Screenshot capture without the infrastructure
Playwright excels at testing, but if your primary need is capturing screenshots rather than running test assertions, managing browser infrastructure adds unnecessary complexity. Each CI run needs browsers installed, and headless Chrome can be resource-intensive.
For screenshot-focused workflows like visual regression baselines, documentation, or link previews, AllScreenshots provides a simpler path:
// Instead of managing Playwright for screenshotsconst response =awaitfetch('https://api.allscreenshots.com/v1/screenshots',{method:'POST',headers:{'X-API-Key': process.env.SCREENSHOT_API_KEY,'Content-Type':'application/json'},body:JSON.stringify({url:'https://example.com',fullPage:true,blockAds:true})});
The API handles browser rendering, ad blocking, cookie banner removal, and device emulation. No npx playwright install in your CI pipeline, no browser binaries to cache. For teams already using Playwright for testing, AllScreenshots can handle the screenshot-specific parts of your workflow while Playwright focuses on what it does best: reliable cross-browser tests.
Playwright removes most of the friction from browser testing. Auto-waiting eliminates timing flakiness. Cross-browser support means one test suite covers Chromium, Firefox, and WebKit. The tooling, such as codegen, trace viewer and UI mode make debugging fast instead of frustrating.
Start with npm init playwright@latest and write tests using role-based locators. Run across browsers in CI from day one. When tests fail, use the trace viewer to understand exactly what happened.
The goal is tests you can trust. Playwright gets closer to that goal than anything else available today.