2HAASStart a project
All posts

Tool Reviews

Playwright: Modern E2E Testing for Web Apps

Learn how Playwright provides fast, reliable, and cross browser end to end testing for your web applications, improving development quality.

The 2HAAS team4 min read

End-to-end (E2E) testing is crucial for ensuring the quality of web applications, simulating real user interactions to catch critical bugs before deployment. However, many developers struggle with existing E2E testing solutions. Common problems include flaky tests that fail intermittently, slow execution times, and complex setups for cross-browser compatibility, leading to unreliable test suites and hindering agile development.

What is Playwright?

Playwright is an open-source framework developed by Microsoft for reliable end-to-end testing of modern web apps. It automates Chromium, Firefox, and WebKit with a single API, allowing developers to write tests that run consistently across all major browsers. Unlike some older frameworks, Playwright operates directly with the browser's native capabilities, providing faster execution and a highly stable testing environment.

Key features of Playwright include its powerful auto-waiting mechanism, which automatically waits for elements to be actionable before performing operations, significantly reducing flakiness. It also supports parallel test execution out of the box, speeding up test suites, and offers a comprehensive set of tools like a code generator (Codegen) and a visual trace viewer for debugging tests.

Here is a quick comparison with other popular E2E testing tools:

Feature Playwright Cypress Selenium (WebDriver)
Architecture Direct browser interaction Proxy-based, in-browser execution WebDriver protocol, external drivers
Browser Support Chromium, Firefox, WebKit, mobile emulation Chromium-based, Firefox (experimental), Edge All major browsers and operating systems
Parallelization Built-in (shard tests across workers) Limited, requires external tools/plugins Supports parallel execution via Grid
Auto-waiting Yes (robust and intelligent) Yes (automatically waits for elements) Requires explicit waits or custom implementations
API Modern, async/await focused, unified JavaScript-centric, chained commands Language bindings (Java, Python, JS, C# etc.)
Network Control Powerful interception and modification Robust network stubbing, route control Limited, relies on proxy setup or browser extensions

Getting Started with Playwright

Starting with Playwright is straightforward. You can initialize a new project and install the necessary browser binaries with a single command.

  1. Initialize a new Playwright project:

    npm init playwright@latest
    # or yarn create playwright
    # or pnpm create playwright
    

    This command will guide you through setting up your first tests, choosing your language (TypeScript or JavaScript), and installing browser binaries.

  2. Run your first test (example created by init):

    // tests/example.spec.ts
    import { test, expect } from '@playwright/test';
    
    test('has title', async ({ page }) => {
      await page.goto('https://playwright.dev/');
      // Expect a title "to contain" a substring.
      await expect(page).toHaveTitle(/Playwright/);
    });
    
    test('get started link', async ({ page }) => {
      await page.goto('https://playwright.dev/');
      // Click the get started link.
      await page.getByRole('link', { name: 'Get started' }).click();
      // Expects page to have a heading with the name of Installation.
      await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
    });
    
  3. Execute your tests:

    npx playwright test
    

    Playwright will run the tests headlessly by default, meaning no browser window will open. You can add --headed to see the browser UI during execution.

Advanced Features for Robust Testing

Playwright offers a rich API for complex testing scenarios:

  • Locators: Playwright encourages robust locators based on roles, text, and test IDs, making tests less brittle to UI changes. For instance, page.getByRole('button', { name: 'Submit' }) is more reliable than a CSS selector.
  • User Interactions: Simulate complex user flows including clicks, typing, hovering, dragging, and keyboard shortcuts.
    await page.getByRole('textbox', { name: 'Username' }).fill('myuser');
    await page.getByLabel('Password').type('mypassword');
    await page.getByRole('button', { name: 'Log in' }).click();
    
  • Network Interception: Control network requests, mock API responses, and test error conditions without modifying your backend.
    await page.route('**/api/users', async route => {
      const json = [{ id: 1, name: 'Alice' }];
      await route.fulfill({ json });
    });
    await page.goto('/users'); // Will load mocked user data
    
  • Authentication: Easily handle login flows and persist authentication states across tests to avoid repeated logins.

Boosting Efficiency with Playwright

Playwright provides tools to streamline your testing workflow:

  • Parallel Execution: By default, Playwright runs tests in parallel across worker processes, drastically reducing test suite execution time. You can configure the number of workers in playwright.config.ts.
  • CI/CD Integration: Playwright integrates seamlessly with popular CI/CD pipelines like GitHub Actions, GitLab CI, and Jenkins, allowing you to run tests automatically on every code push.
  • Trace Viewer: A powerful GUI tool that captures a full trace of your tests, including screenshots, DOM snapshots, and network logs, making debugging effortless. Run npx playwright show-report to view test reports and launch the trace viewer.

Common Pitfalls and Best Practices

To maximize Playwright's benefits, consider these best practices:

  • Avoid page.waitForTimeout(): This hardcoded wait introduces flakiness and slows down tests. Rely on Playwright's auto-waiting or explicit expect assertions for conditions.
  • Use Robust Locators: Prefer getByRole, getByText, getByLabel, or getByTestId over brittle CSS/XPath selectors that break with minor UI changes.
  • Modularize Tests: Organize your test code using Page Object Model (POM) or a similar pattern to improve readability and maintainability, especially for large applications.
  • Test Environment Configuration: Ensure your tests run in an isolated and consistent environment to prevent dependencies or state leaks between tests.

Playwright offers a modern, reliable, and efficient solution for end-to-end testing, empowering development teams to deliver high-quality web applications with confidence. Its cross-browser support, powerful API, and developer-friendly tools make it an excellent choice for any serious web project.

Keep reading

  1. Code Snippets4 min read

    Simplify Popups with HTML Popover API

    Learn how to effortlessly create accessible popups, tooltips, and modals using the native HTML Popover API, reducing custom JavaScript and improving user experience.

  2. Tech News4 min read

    Bun 1.1 Release: Faster JavaScript Development

    Learn how Bun 1.1 enhances JavaScript development with improved speed, Node.js compatibility, and a streamlined toolchain.

  3. Code Snippets4 min read

    Simplify Promise Handling with Promise.withResolvers

    Learn how the new Promise.withResolvers static method streamlines common asynchronous patterns in JavaScript, reducing boilerplate and improving code clarity.

Tell us what’s in the way.

A few lines is enough. You get a candid yes, a candid no, or a sharper question.

  • Working software in week four
  • A reply in one business day
  • Handover is the deliverable

Prefer email? [email protected]

The problem, who it is for, and what is in the way right now.

One business day to a real reply. No sales sequence, no call required.