E2E tests automate a real browser. Playwright tests your whole stack — frontend, backend, and database — as a real user would. Today you will write page object model tests and integrate them with CI.
npm init playwright@latest
# or add to existing project:
npm install --save-dev @playwright/test
npx playwright install// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login flow', () => {
test('successful login redirects to dashboard', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('secret123');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('shows error for wrong password', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('[email protected]');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
});
});// tests/pages/LoginPage.ts — Page Object Model
export class LoginPage {
constructor(private page: Page) {}
async goto() { await this.page.goto('/login'); }
async login(email: string, password: string) {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Log in' }).click();
}
}
// Use in tests:
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('[email protected]', 'secret123');