Back to Blog
Playwright: Beyond E2E Tests, Automating the Browser for Real Work
8 min readJul 15, 20260 views

Playwright: Beyond E2E Tests, Automating the Browser for Real Work

Playwright is often seen as just a testing tool, but its true power lies in its robust API for general browser automation. I'll show you how to leverage Playwright for tasks far beyond QA, from data extraction to complex user workflows.

ToolingAutomationWeb DevelopmentTypeScriptTesting
Share

by Sunil Band

Most developers, when they hear Playwright, think of end-to-end testing. And they're not wrong; it's an excellent, reliable tool for that. But pigeonholing Playwright as just a testing framework misses its broader, more impactful application: general-purpose browser automation. If you've ever found yourself wishing you could programmatically control a browser to interact with a web application for something other than asserting a data-testId exists, then Playwright is your answer.

I've seen countless developers manually copy-pasting data, clicking through tedious dashboards, or even attempting to reverse-engineer complex API calls just to get at information locked behind a web UI. This isn't just inefficient; it's a drain on developer time that could be spent on actual product work. Playwright offers a robust, multi-browser solution to this problem, allowing you to script these interactions with the same reliability you'd expect from your test suite.

Why Playwright for Automation?

Before we dive into code, let's talk about why Playwright stands out for automation tasks. You've got options: Puppeteer, Selenium, even plain old fetch with Cheerio. Each has its place, but Playwright brings a few key advantages to the table that make it my go-to for anything beyond basic HTML parsing.

First, multi-browser support out of the box. Chromium, Firefox, WebKit—all from a single API. This is huge. Web applications can behave differently across browsers, and if your automation needs to be robust, you need to account for that. With Playwright, the mental overhead and code duplication are minimal. Second, its auto-wait mechanism is a game-changer. Playwright automatically waits for elements to be actionable before performing operations like clicks or type events. This dramatically reduces the flakiness often associated with browser automation, where race conditions between your script and the page's render cycle are common. Finally, the codegen feature is a fantastic starting point for complex flows. You interact with the browser, and Playwright generates the script for you. It's not perfect, but it saves a ton of boilerplate.

A Practical Example: Automating a Web Form Submission

Let's say you frequently interact with an internal tool that requires submitting a form with specific data, perhaps for provisioning new users or updating records. Manually doing this for dozens of entries is painful. We can automate this with Playwright.

Imagine a simple form on https://example.com/admin/createUser with fields for username, email, and a submit button. Our goal is to programmatically fill and submit this form.

First, install Playwright. If you're using TypeScript, which I strongly recommend for any automation script, ensure you have @types/node too.

bash
npm init -y
npm install playwright @types/node
npx playwright install

Now, let's write the script. I'll use a local HTML file to simulate example.com for a runnable example. Create index.html:

html
<!DOCTYPE html>
<html>
<head>
    <title>Create User</title>
</head>
<body>
    <h1>Create New User</h1>
    <form id="userForm" action="/submit" method="POST">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required><br><br>

        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required><br><br>

        <button type="submit">Create User</button>
    </form>
    <div id="message" style="margin-top: 20px; color: green;"></div>

    <script>
        document.getElementById('userForm').addEventListener('submit', function(event) {
            event.preventDefault();
            const username = document.getElementById('username').value;
            const email = document.getElementById('email').value;
            
            // Simulate an API call
            setTimeout(() => {
                document.getElementById('message').textContent = `User '${username}' with email '${email}' created successfully!`;
                console.log(`Submitted: Username - ${username}, Email - ${email}`);
            }, 500);
        });
    </script>
</body>
</html>

Now, let's write our Playwright script (automateForm.ts):

typescript
import { chromium, Page } from 'playwright';
import * as path from 'path';

interface UserData {
  username: string;
  email: string;
}

// Our function to interact with the form
async function createUser(page: Page, userData: UserData): Promise<void> {
  console.log(`Attempting to create user: ${userData.username}`);

  // Fill the username field. Playwright auto-waits for the element to be ready.
  await page.fill('#username', userData.username);
  // Fill the email field.
  await page.fill('#email', userData.email);

  // Click the submit button. Again, Playwright handles waiting.
  await page.click('button[type="submit"]');

  // Wait for the success message to appear. This is a common pattern for confirming actions.
  await page.waitForSelector('#message:has-text("created successfully")');
  const successMessage = await page.textContent('#message');
  console.log(`Automation complete: ${successMessage}`);
}

async function runAutomation() {
  // Launch a Chromium browser in headless mode by default. Set headless: false for visual debugging.
  const browser = await chromium.launch({ headless: false }); // Set to true for production automation
  const page = await browser.newPage();

  // Navigate to our local HTML file.
  const filePath = path.resolve(__dirname, 'index.html');
  await page.goto(`file://${filePath}`);

  // Define the users we want to create.
  const usersToCreate: UserData[] = [
    { username: 'sunil.band', email: 'sunil@example.com' },
    { username: 'jane.doe', email: 'jane@example.com' },
    { username: 'john.smith', email: 'john@example.com' }
  ];

  // Loop through each user and call our creation function.
  for (const user of usersToCreate) {
    await createUser(page, user);
    // After each submission, we might want to navigate back or perform other actions.
    // For this example, we'll just reload the page to clear the form for the next user.
    await page.reload(); 
  }

  // Close the browser when done.
  await browser.close();
}

runAutomation().catch(console.error);

To run this, compile the TypeScript and then execute:

bash
tsc automateForm.ts # Compile TypeScript
node automateForm.js # Run the compiled JavaScript

When you run this, you'll see a browser window pop up (because headless: false) and quickly fill out and submit the form for each user. This is a simple example, but it demonstrates the core principles: locating elements, interacting with them, and waiting for state changes. Imagine extending this to scrape data, navigate through paginated tables, or perform complex multi-step workflows across different web applications.

Advanced Automation Techniques

Beyond basic form submission, Playwright offers features that make advanced automation a breeze:

Network Interception

This is powerful. You can intercept network requests and responses, modify them, or even block them. This is incredibly useful for testing error states, mocking API responses during development of your automation, or preventing unnecessary resource loading to speed up your scripts.

typescript
await page.route('**/api/analytics', route => route.abort()); // Block analytics requests

await page.route('**/api/data', async route => {
  const json = { modified: true, data: 'intercepted!' };
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify(json),
  });
});

This snippet shows how to block specific network calls or even return custom responses. This level of control over the browser's network stack is invaluable for robust automation, letting you simulate different server behaviors without touching the actual backend.

Storage State Management

Logging in can be a cumbersome first step for many automation tasks. Playwright allows you to save and reuse the browser's storage state (cookies, local storage, session storage) across multiple runs. This means you can log in once, save the state, and then load that state for subsequent automation sessions, skipping the login process entirely.

typescript
// After successful login
await page.context().storageState({ path: 'auth.json' });

// In a new automation script or run
const browser = await chromium.launch();
const context = await browser.newContext({
  storageState: 'auth.json',
});
const page = await context.newPage();
// Now 'page' is already authenticated

This dramatically speeds up development and execution of your automation scripts, especially when dealing with complex authentication flows or multi-factor authentication (MFA) that you might only want to handle once.

Screenshot and Video Recording

Debugging a flaky automation script can be a nightmare. Playwright can take screenshots at any point, or even record full videos of the browser session. This visual feedback is incredibly helpful for understanding exactly what the browser was doing when an unexpected error occurred.

typescript
await page.screenshot({ path: 'screenshot.png' });

// For video recording, configure context beforehand
const context = await browser.newContext({
  recordVideo: { dir: './videos' },
});
// ... perform actions
await context.close(); // Video is saved on context close

This is particularly useful when you need to share evidence of an issue or when you're troubleshooting automation failures in a CI/CD pipeline where you don't have a visual browser.

Trade-offs and Considerations

While Playwright is powerful, it's not a silver bullet. There are a few considerations to keep in mind.

First, performance for heavy scraping. If your primary goal is to scrape vast amounts of data from static pages, a more lightweight solution like node-fetch combined with Cheerio or a custom parsing library might be faster because it avoids the overhead of launching a full browser instance. Playwright shines when JavaScript execution is required to render content or when user interaction is necessary.

Second, maintenance overhead. Web UIs change. When a selector changes, your script breaks. This is an inherent challenge with any browser automation. Playwright's robust selectors and auto-wait help, but you'll still need to monitor and update your scripts. Treating your automation scripts like any other piece of critical code—with version control and perhaps even tests for your automation—is crucial.

Finally, resource consumption. Running a browser, even headless, consumes CPU and memory. For large-scale parallel automation, you'll need adequate hardware or cloud resources. Be mindful of how many browser instances you launch concurrently and ensure you close them properly when done.

Wrapping up

Playwright is far more than just a testing framework; it's an indispensable tool for anyone looking to programmatically interact with web applications. From automating repetitive administrative tasks to orchestrating complex data extraction workflows, its robust API, multi-browser support, and developer-friendly features make it an incredibly versatile asset in your toolkit.

The next time you find yourself clicking through the same web interface for the tenth time, stop. Think about how Playwright could save you that time. Start by picking one mundane web task you do regularly and try to automate it. Clone a simple Playwright example from their official documentation, or re-run my example with your own local HTML, and experiment with its page object. You'll be surprised how quickly you can turn hours of manual work into a few lines of code. It's a skill that pays dividends.

More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕