Playwright best practices.
// 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
});
// 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();
}
// ❌ 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();
});
// ✅ 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();
});
// ❌ test2 depends on test1 creating a user
test('create user', async () => {
/* ... */
});
test('login as created user', async () => {
/* ... */
});
test('checkout as that user', async () => {
/* ... */
});
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();
});
// 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');
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)