UI / E2E · Junior & Senior

Playwright

Microsoft’s modern browser automation library that drives Chromium, Firefox, and WebKit with a single API — the current default choice for E2E testing on new NZ projects.

Junior Senior ISTQB CTAL-TAE ~12 min read

1 The Hook — The Auckland SaaS "Works on my machine" Incident

A fast-growing Auckland SaaS company was using an older automation framework for their daily deployments. Their tests passed locally 100% of the time, but failed 40% of the time in the CI pipeline. Because they couldn't trust the results, the team started ignoring "red" tests and shipping anyway.

One Friday, a race condition in the login flow (where the button appeared but wasn't yet clickable) made it to production. The old tests missed it because they had hardcoded "sleeps" that were just long enough on a laptop, but too short on the busy production server. Playwright's auto-waiting would have caught this instantly.

2 The Rule — Test in Isolation, Wait for Reality

Every test should run in a clean, isolated browser context. Don't tell the browser to "sleep"; tell it what state to wait for.

Isolation prevents one test from leaking state into the next (like staying logged in). Auto-waiting ensures your tests only move as fast as the application actually can, eliminating 90% of flakiness.

3 The Analogy — The Self-Driving Car

Analogy

Self-Driving vs. Remote Control.

Selenium is like a remote-control car: you have to tell it exactly when to turn, when to brake, and how long to wait at a light. If the light takes 0.1s longer than you expected, you crash. Playwright is a self-driving car: you say "Go to the dairy," and it handles the stops, starts, and obstacles automatically until it arrives safely at the destination.

Senior engineer insight

The moment that changed how I approach Playwright was realising that getByRole isn't just a "nicer selector" — it's a contract that your UI is actually accessible. On a Wellington government digital services project, switching from CSS selectors to role-based locators forced developers to fix missing ARIA labels that had been invisible to manual testers for months. The tests became a passive accessibility audit running on every push.

Most common mistake: treating Playwright as "Selenium with a better API" and porting brittle CSS selectors across rather than rethinking selector strategy from scratch.

4 Watch Me Do It — NZ E-commerce Login

Scenario: We want to test the login flow for a typical NZ storefront. We need to ensure the user is redirected to the dashboard after a successful login.

import { test, expect } from '@playwright/test';

test('successful login redirects to dashboard', async ({ page }) => {
  // 1. Navigate to login
  await page.goto('/login');

  // 2. Fill credentials (auto-waits for these to be visible)
  await page.getByLabel('Email address').fill('tester@resync.nz');
  await page.getByLabel('Password').fill('SecurePass123!');

  // 3. Click login
  await page.getByRole('button', { name: 'Log in' }).click();

  // 4. Assert URL (auto-waits for the redirect to complete)
  await expect(page).toHaveURL(/.*dashboard/);
  
  // 5. Assert welcome message
  await expect(page.getByText('Welcome back, Tester')).toBeVisible();
});

Notice there are no wait(2000) calls. Playwright handles the network delay and the DOM rendering for you.

From the field

An Auckland fintech team migrated from Cypress to Playwright mid-project after hitting Cypress's same-origin restriction — their checkout flow redirected through a bank's hosted payment page on a separate domain, which Cypress simply couldn't follow. They assumed Playwright would be a straight swap, stood up the tests in a week, and declared victory. Three weeks later the CI suite was 40% flaky. The culprit: their page.fill() calls on the bank's card-number input were firing before a JavaScript formatter had attached, so some runs would get "4111" and others "4 1 1 1". The fix was a single page.waitForFunction call confirming the formatter was active, but finding it cost a week of debugging across two timezones. Lesson: cross-origin flows and third-party widgets hide timing assumptions that your own app's DOM doesn't expose.

5 Decision Tool — Why Playwright?

✅ Choose Playwright for...

  • New projects (Greenfield)
  • Multi-browser requirements (Safari/WebKit)
  • Fast, parallel CI pipelines
  • Mobile web emulation

❌ Stick to Selenium/Others for...

  • Legacy Java/C# suites with 1000s of tests
  • Teams with zero JS/TS knowledge
  • Testing very old browsers (IE11)
  • Native mobile apps (use Appium)

6 Common Mistakes

🚫 Using waitForTimeout()

Don't: await page.waitForTimeout(5000);
Do: await expect(page.getByText('Loaded')).toBeVisible();
Hard sleeps make tests slow and flakier. Always wait for a specific state.

🚫 Brittle CSS Selectors

Don't: page.locator('div > div > span').click();
Do: page.getByRole('button', { name: 'Submit' }).click();
Use user-facing attributes (Roles, Labels, Text) or data-testid. They don't break when CSS changes.

7 Now You Try — Setup

🚀 Getting Started

Ready to try it on your own machine? Run this command in your terminal:

npm init playwright@latest

Follow the prompts to choose TypeScript and install the browsers. Once done, run your first test with:

npx playwright test --ui

Why teams fail here

  • Parallelism without isolation: Running tests in parallel while sharing a single user account or a seeded database — writes from one worker corrupt the state another worker expects, producing intermittent failures that are impossible to reproduce locally.
  • Skipping the Trace Viewer on failure: Teams reproduce the failure locally (slowly, painfully) instead of enabling trace: 'on-first-retry' and reading the exact network response, DOM snapshot, and action timeline that caused the CI failure.
  • Testing implementation, not behaviour: Assertions like expect(component.state.isLoading).toBe(false) instead of expect(page.getByText('Your results')).toBeVisible() — the first breaks on every refactor, the second is what the user actually experiences.
  • No baseURL or environment config: Hardcoding https://staging.example.co.nz into test files instead of using Playwright's baseURL config and environment variables, making the suite impossible to run against local dev or a preview deployment without find-and-replace surgery.

8 Self-Check

Q1. Does Playwright share login state between tests by default?

No. Each test runs in its own BrowserContext (like an incognito window) by default. This ensures total isolation and reproducibility.

Q2. Can I test Safari on Windows with Playwright?

Yes. Playwright uses the WebKit engine, which is what Safari uses. You can run WebKit tests on Windows, Linux, and macOS without needing a Mac.

Key takeaway

Playwright doesn't make bad tests fast — it makes well-designed tests reliable; if your suite is still flaky after switching, the problem is selector strategy, not the tool.

9 Interview Prep

"How does Playwright solve the 'flaky test' problem?"

Answer: "Primarily through auto-waiting. Unlike Selenium, which fails if an element isn't ready the millisecond the command is sent, Playwright performs actionability checks (visibility, stability, enablement) automatically. It also provides a Trace Viewer that allows us to inspect the exact network and DOM state of a failure after the run."