Playwright Scripts
Basic Navigation & Interaction
import { test, expect } from '@playwright/test';
test('login flow', async ({ page }) => {
// Navigation
await page.goto('https://example.com/login');
await page.goto('https://example.com', { waitUntil: 'networkidle' });
// Filling forms
await page.fill('#email', 'user@example.com');
await page.fill('[name="password"]', 'secret123');
await page.selectOption('select#role', 'admin');
await page.check('#rememberMe');
await page.uncheck('#newsletter');
// Clicking
await page.click('button[type="submit"]');
await page.dblclick('.item');
await page.rightClick('.context-menu-trigger');
await page.click('text=Sign In');
// Keyboard
await page.keyboard.press('Enter');
await page.keyboard.type('Hello World', { delay: 50 });
await page.keyboard.press('Control+A');
// Hover and focus
await page.hover('.tooltip-trigger');
await page.focus('input[name="search"]');
});
Selectors
// CSS selectors
page.locator('#submit-btn');
page.locator('.btn.btn-primary');
page.locator('input[type="email"]');
// Text content
page.locator('text=Submit');
page.locator('button:has-text("Sign In")');
page.getByText('Welcome back');
// ARIA roles (recommended)
page.getByRole('button', { name: 'Submit' });
page.getByRole('textbox', { name: 'Email' });
page.getByRole('heading', { level: 1 });
page.getByLabel('Password');
page.getByPlaceholder('Enter your email');
page.getByTestId('submit-button'); // data-testid attribute
// XPath
page.locator('xpath=//button[@type="submit"]');
// Chaining / filtering
page.locator('.card').filter({ hasText: 'Alice' });
page.locator('li').nth(2);
page.locator('.row').first();
page.locator('.row').last();
Waiting Strategies
// Auto-waiting โ Playwright waits automatically for most actions
await page.click('#submit'); // waits for element to be clickable
// Wait for specific state
await page.waitForSelector('.modal', { state: 'visible' });
await page.waitForSelector('#loading', { state: 'hidden' });
await page.waitForSelector('.result', { timeout: 10000 });
// Wait for URL
await page.waitForURL('**/dashboard');
await page.waitForURL(/dashboard/, { waitUntil: 'networkidle' });
// Wait for network
await page.waitForResponse('**/api/users');
await page.waitForRequest(req => req.url().includes('/search'));
// Wait for load state
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
// Wait for function
await page.waitForFunction(() => window.appReady === true);
// Expect auto-waits
await expect(page.locator('.success')).toBeVisible({ timeout: 5000 });
await expect(page.locator('#count')).toHaveText('3');
Screenshots & Video Recording
// Manual screenshot
await page.screenshot({ path: 'screenshot.png' });
await page.screenshot({ path: 'full.png', fullPage: true });
await page.locator('.chart').screenshot({ path: 'chart.png' });
// playwright.config.ts โ automatic on failure
export default {
use: {
screenshot: 'only-on-failure', // or 'on', 'off'
video: 'retain-on-failure', // or 'on', 'off', 'on-first-retry'
trace: 'on-first-retry', // .zip with timeline, DOM, network
},
};
// View trace
// npx playwright show-trace trace.zip
// Per-test override
test('with recording', async ({ page }, testInfo) => {
await page.goto('/');
// Attach to test report
await testInfo.attach('screenshot', {
body: await page.screenshot(),
contentType: 'image/png',
});
});
Test Fixtures
// fixtures.ts โ extend base test
import { test as base, expect } from '@playwright/test';
type Fixtures = {
loggedInPage: Page;
adminUser: { email: string; token: string };
};
export const test = base.extend<Fixtures>({
adminUser: async ({}, use) => {
const user = await createTestUser({ role: 'admin' });
await use(user);
await deleteTestUser(user.id);
},
loggedInPage: async ({ page, adminUser }, use) => {
await page.goto('/login');
await page.fill('#email', adminUser.email);
await page.fill('#password', 'testpass');
await page.click('[type="submit"]');
await page.waitForURL('**/dashboard');
await use(page);
},
});
export { expect };
// usage in tests:
import { test, expect } from './fixtures';
test('admin can see users', async ({ loggedInPage }) => {
await loggedInPage.goto('/admin/users');
await expect(loggedInPage.getByRole('table')).toBeVisible();
});
playwright.config.ts Overview
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html'], ['junit', { outputFile: 'results.xml' }]],
use: {
baseURL: 'http://localhost:3000',
headless: true,
viewport: { width: 1280, height: 720 },
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 13'] } },
],
});