Playwright best practices

Playwright best practices.

#Playwright — Best Practices for Writing Tests

Based on Checkly’s “Best Practices for Writing Tests in Playwright” (Updated: Aug 14, 2025). (Checkly)

#Keep Tests Short

#Checklist (short, simple, readable)

  • One primary user flow
  • ≤ ~10 actions & a few asserts
  • No verbose helpers inline
  • Prefer small, named utils
  • Fail fast; avoid noise
  • Update cost stays low (Checkly)

#Example — Short Login Test

// tests/login.spec.ts
import { test, expect } from '@playwright/test';

test('user logs in', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('u@example.com');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL(/\/dashboard$/); // clear, quick
});

#Keep Helpers Out of the Way

// prefer: small, named utils
export async function signIn(page, email, pass) {
  await page.getByLabel('Email').fill(email);
  await page.getByLabel('Password').fill(pass);
  await page.getByRole('button', { name: 'Sign in' }).click();
}

#Keep Tests Focused

#Anti-pattern — Combined Features (hard to read)

// ❌ mixes checkout + coupons; failures unclear
test('checkout + coupon', async ({ page }) => {
  await addItem(page);
  await applyCoupon(page, 'SAVE10'); // feature A
  await checkout(page); // feature B
  await expect(page.getByText('Thanks')).toBeVisible();
});

#Refactor — One Feature per Test (clear intent)

// ✅ coupon feature
test('apply coupon at cart', async ({ page }) => {
  await addItem(page);
  await applyCoupon(page, 'SAVE10');
  await expect(page.getByText('10% off')).toBeVisible();
});

// ✅ checkout flow
test('complete checkout', async ({ page }) => {
  await addItem(page);
  await checkout(page);
  await expect(page.getByText('Thanks')).toBeVisible();
});

#Rule of Thumb

  • Each test answers one question
  • Assertions stick to that feature
  • Split when asserts span features (Checkly)

#Keep Tests Independent

#Anti-pattern — Chained Tests

// ❌ test2 depends on test1 creating a user
test('create user', async () => {
  /* ... */
});
test('login as created user', async () => {
  /* ... */
});
test('checkout as that user', async () => {
  /* ... */
});

#Refactor — Self-Contained with Setup

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

test.beforeEach(async ({}, testInfo) => {
  // create fresh user for each test
  // fast, reliable via API when possible
  const req = await request.newContext();
  await req.post('/api/users', { data: { email: unique(), pass: 'x' } });
});

test('login works', async ({ page }) => {
  await page.goto('/login');
  await signIn(page, currentEmail(), 'x');
  await expect(page).toHaveURL(/dashboard$/);
});

test('checkout works', async ({ page }) => {
  await page.goto('/login');
  await signIn(page, currentEmail(), 'x');
  await checkout(page);
  await expect(page.getByText('Thanks')).toBeVisible();
});

#Why Independence?

  • Run in any order / parallel
  • Easier maintenance & triage
  • No hidden coupling across files (Checkly)

#Readable Tests

#Naming & Structure Tips

// clear titles communicate status fast
test.describe('Cart — coupons', () => {
  test('shows applied discount', async ({ page }) => {
    /* ... */
  });
});

// prefer clear locators (user-visible)
page.getByRole('button', { name: 'Pay now' });
page.getByLabel('Email').fill('u@example.com');

#Small, Reusable Setup (no duplication)

// factor shared setup without coupling tests
async function seedUser(req, data) {
  return req.post('/api/users', { data });
}
// call from beforeEach or fixtures (per test)

Readability = faster updates & quicker interpretation. (Checkly)

#Diagram

#Dependent vs. Independent Tests

flowchart TB
  subgraph Coupled (bad)
    A[test1: create user] --> B[test2: login as user]
    B --> C[test3: checkout as user]
  end

  subgraph Independent (good)
    A2[test1: create user]
    B2[test2: setup: create → login]
    C2[test3: setup: create → login → checkout]
  end

Independent tests can run in parallel and in any order. (Checkly)

#Takeaways

  • Keep tests short, focused, independent
  • Prefer API setup/teardown when possible
  • Optimize for human readability & quick triage (Checkly)

#Any Suggestions?