Getting Started with Playwright
Playwright is Microsoft’s modern end-to-end testing framework that enables fast, reliable testing across all major browsers. Unlike older tools, Playwright is built for the modern web with features like auto-wait, network interception, and mobile emulation out of the box.
Why Choose Playwright?
Section titled “Why Choose Playwright?”- Fast & Reliable: Built-in auto-wait eliminates flaky tests
- Cross-Browser: Test Chrome, Firefox, Safari with one codebase
- Modern Features: Screenshots, videos, network mocking, mobile testing
- Developer Experience: Excellent debugging tools and VS Code integration
- Language Support: JavaScript, TypeScript, Python, .NET, Java
Prerequisites
Section titled “Prerequisites”Before starting, ensure you have:
- Node.js 16+ installed
- Code editor (VS Code recommended for best experience)
- Basic knowledge of JavaScript/TypeScript
Installation
Section titled “Installation”Create New Project
Section titled “Create New Project”# Create project directorymkdir playwright-tutorialcd playwright-tutorial
# Initialize package.jsonnpm init -y
Install Playwright
Section titled “Install Playwright”# Install Playwright Testnpm install -D @playwright/test
# Download browser binariesnpx playwright install
The browser download is about 200MB and includes Chromium, Firefox, and WebKit.
Initialize Configuration
Section titled “Initialize Configuration”npx playwright init
This creates:
playwright.config.ts
- Test configurationtests/
- Your test files directorytests/example.spec.ts
- Sample test file
Project Structure
Section titled “Project Structure”After initialization, your project looks like:
playwright-tutorial/├── node_modules/├── tests/│ └── example.spec.ts├── playwright.config.ts├── package.json└── package-lock.json
Your First Test
Section titled “Your First Test”Let’s create a meaningful test that verifies a real website:
import { test, expect } from '@playwright/test';
test.describe('Playwright Homepage', () => { test('should load and display main navigation', async ({ page }) => { // Navigate to Playwright homepage await page.goto('https://playwright.dev');
// Verify page loaded correctly await expect(page).toHaveTitle(/Playwright/);
// Check main navigation is visible await expect(page.getByRole('link', { name: 'Docs' })).toBeVisible(); await expect(page.getByRole('link', { name: 'API' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Community' })).toBeVisible(); });
test('should navigate to getting started guide', async ({ page }) => { await page.goto('https://playwright.dev');
// Click "Get started" button await page.getByRole('link', { name: 'Get started' }).first().click();
// Verify we're on the installation page await expect(page).toHaveURL(/.*\/intro/); await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); });
test('should search for specific content', async ({ page }) => { await page.goto('https://playwright.dev');
// Open search await page.getByRole('button', { name: 'Search' }).click();
// Search for "locators" await page.getByPlaceholder('Search docs').fill('locators'); await page.keyboard.press('Enter');
// Verify search results appear await expect(page.getByText('Locators')).toBeVisible(); });});
Running Your Tests
Section titled “Running Your Tests”Basic Test Execution
Section titled “Basic Test Execution”# Run all testsnpx playwright test
# Run in headed mode (see browser)npx playwright test --headed
# Run specific test filenpx playwright test playwright-homepage
# Run single testnpx playwright test -g "should load and display main navigation"
Debug Mode
Section titled “Debug Mode”# Run in debug modenpx playwright test --debug
# Debug specific testnpx playwright test --debug -g "should navigate"
In debug mode, you can:
- Step through test execution
- Inspect page elements
- View console output
- Take screenshots manually
Understanding the Configuration
Section titled “Understanding the Configuration”Here’s a practical playwright.config.ts
setup:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({ // Test directory testDir: './tests',
// Run tests in parallel fullyParallel: true,
// Fail build on CI if you accidentally left test.only in source forbidOnly: !!process.env.CI,
// Retry failed tests retries: process.env.CI ? 2 : 0,
// Limit workers on CI workers: process.env.CI ? 1 : undefined,
// Test timeout timeout: 30000,
// Expect timeout expect: { timeout: 5000 },
// Reporter configuration reporter: [ ['html'], ['json', { outputFile: 'test-results.json' }] ],
// Global test settings use: { // Base URL for page.goto('/') baseURL: 'https://playwright.dev',
// Capture trace on retry trace: 'on-first-retry',
// Screenshot on failure screenshot: 'only-on-failure',
// Record video on failure video: 'retain-on-failure', },
// Configure browsers projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, },
// Mobile testing { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, ],});
Essential Playwright Concepts
Section titled “Essential Playwright Concepts”Locator Strategies
Section titled “Locator Strategies”Playwright uses a hierarchy of locator strategies:
// 1. Role-based (RECOMMENDED)await page.getByRole('button', { name: 'Submit' });await page.getByRole('textbox', { name: 'Email' });await page.getByRole('link', { name: 'Home' });
// 2. Test ID (for unique elements)await page.getByTestId('user-menu');await page.getByTestId('checkout-button');
// 3. Text contentawait page.getByText('Welcome back');await page.getByText('Total: $29.99');
// 4. Label associationawait page.getByLabel('Password');await page.getByLabel('Remember me');
// 5. CSS selectors (when necessary)await page.locator('.navbar-toggle');await page.locator('#main-content');
Auto-waiting
Section titled “Auto-waiting”Playwright automatically waits for elements to be ready:
// These actions auto-wait for element to be:// - Visible// - Stable (not animating)// - Enabled// - Not covered by other elements
await page.getByRole('button', { name: 'Save' }).click();await page.getByLabel('Email').fill('test@example.com');await page.getByRole('textbox').type('Hello world');
Assertions
Section titled “Assertions”Use Playwright expect for reliable assertions:
// Element visibilityawait expect(page.getByText('Success')).toBeVisible();await expect(page.getByRole('button', { name: 'Delete' })).toBeHidden();
// Text contentawait expect(page.getByRole('heading')).toHaveText('Welcome');await expect(page.locator('.status')).toContainText('Active');
// Page-level assertionsawait expect(page).toHaveTitle('Dashboard');await expect(page).toHaveURL(/.*\/dashboard/);
// Form valuesawait expect(page.getByLabel('Email')).toHaveValue('user@example.com');await expect(page.getByRole('checkbox')).toBeChecked();
Best Practices
Section titled “Best Practices”1. Use Page Object Model
Section titled “1. Use Page Object Model”Create reusable page objects for complex pages:
import { Page, Locator } from '@playwright/test';
export class LoginPage { readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly loginButton: Locator; readonly errorMessage: Locator;
constructor(page: Page) { this.page = page; this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.loginButton = page.getByRole('button', { name: 'Login' }); this.errorMessage = page.getByTestId('error-message'); }
async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.loginButton.click(); }
async expectErrorMessage(message: string) { await expect(this.errorMessage).toHaveText(message); }}
2. Use Test Data Management
Section titled “2. Use Test Data Management”export const testUsers = { validUser: { email: 'test@example.com', password: 'SecurePass123' }, invalidUser: { email: 'invalid@example.com', password: 'wrongpass' }};
// In your testimport { testUsers } from '../test-data/users';
test('login with valid credentials', async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('/login'); await loginPage.login(testUsers.validUser.email, testUsers.validUser.password);});
3. Use Fixtures for Setup
Section titled “3. Use Fixtures for Setup”import { test as base } from '@playwright/test';import { LoginPage } from './pages/LoginPage';
export const test = base.extend<{ loginPage: LoginPage }>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await use(loginPage); },});
export { expect } from '@playwright/test';
Viewing Test Results
Section titled “Viewing Test Results”After running tests, view the HTML report:
npx playwright show-report
The report includes:
- Test execution timeline
- Screenshots of failures
- Trace files for debugging
- Network activity logs
Next Steps
Section titled “Next Steps”Now that you have Playwright running, explore these advanced topics:
- API Testing: Combine UI and API tests
- Visual Testing: Screenshot comparisons
- Mobile Testing: Device emulation
- CI/CD Integration: GitHub Actions setup
- Advanced Patterns: Custom fixtures and utilities
Common Issues & Solutions
Section titled “Common Issues & Solutions”Browser Installation Problems
Section titled “Browser Installation Problems”# Reinstall browsersnpx playwright install --force
# Install system dependencies (Linux)npx playwright install-deps
Debugging Flaky Tests
Section titled “Debugging Flaky Tests”// Add explicit waits when neededawait page.waitForLoadState('networkidle');await page.waitForSelector('[data-testid="content"]');
// Use soft assertions for non-critical checksawait expect.soft(page.getByText('Optional text')).toBeVisible();
Resources
Section titled “Resources”You now have a solid foundation with Playwright! The next guide will cover building a complete test framework with page objects, utilities, and CI integration.