npm init playwright@latest | 프로젝트 초기화 |
npx playwright test | 모든 테스트 실행 |
npx playwright test --headed | 브라우저 표시하며 실행 |
npx playwright test --debug | 디버그 모드 |
npx playwright test --ui | UI 모드 |
npx playwright test tests/login.spec.ts | 특정 파일 실행 |
npx playwright test -g "login" | 제목으로 실행 |
npx playwright test --project=chromium | 특정 브라우저 실행 |
npx playwright codegen | 코드 생성 |
npx playwright codegen https://example.com | 액션 녹화 |
npx playwright show-report | 리포트 표시 |
npx playwright install | 브라우저 설치 |
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
}); test.describe('login', () => {
test('valid login', async ({ page }) => {
// test code
});
test('invalid login', async ({ page }) => {
// test code
});
}); test.skip('skipped test', async ({ page }) => {});
test.only('only this runs', async ({ page }) => {});
test.fixme('needs fixing', async ({ page }) => {}); page.getByRole('button', { name: 'Submit' })
page.getByRole('link', { name: /learn more/i })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('checkbox', { name: 'Agree' }) page.getByText('Hello World')
page.getByText(/hello/i) // regex page.getByLabel('Email')
page.getByLabel('Password') page.getByPlaceholder('Enter email') page.getByTestId('submit-button') page.locator('.btn-primary')
page.locator('#login-form')
page.locator('[data-id="123"]') page.locator('xpath=//button[text()="Submit"]') page.locator('.list').locator('li').first()
page.locator('.card').filter({ hasText: 'Title' }) page.locator('li').first()
page.locator('li').last()
page.locator('li').nth(2) // 0-indexed await page.click('button');
await page.getByRole('button').click();
await page.click('button', { button: 'right' });
await page.dblclick('button'); await page.fill('input[name="email"]', 'test@example.com');
await page.getByLabel('Email').fill('test@example.com'); await page.type('input', 'Hello', { delay: 100 }); await page.getByLabel('Email').clear(); await page.selectOption('select', 'value');
await page.selectOption('select', { label: 'Option 1' }); await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');
await page.setChecked('input', true); await page.hover('.menu-item'); await page.dragAndDrop('#source', '#target'); await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
await page.setInputFiles('input', ['file1.pdf', 'file2.pdf']); await page.keyboard.press('Enter');
await page.keyboard.press('Control+A');
await page.keyboard.type('Hello'); await expect(page).toHaveTitle(/Example/);
await expect(page).toHaveURL(/dashboard/);
await expect(page).toHaveURL('https://example.com'); await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).not.toBeVisible(); await expect(locator).toHaveText('Hello');
await expect(locator).toContainText('World');
await expect(locator).toHaveText(/hello/i); await expect(locator).toHaveAttribute('href', '/home');
await expect(locator).toHaveClass(/active/);
await expect(locator).toHaveId('my-id'); await expect(locator).toHaveValue('test@example.com');
await expect(locator).toBeEmpty(); await expect(locator).toBeEnabled();
await expect(locator).toBeDisabled();
await expect(locator).toBeChecked();
await expect(locator).toBeFocused(); await expect(locator).toHaveCount(5); await expect(page).toHaveScreenshot();
await expect(locator).toHaveScreenshot('button.png'); await page.waitForSelector('.loaded');
await page.waitForSelector('.loaded', { state: 'visible' }); await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded'); const response = await page.waitForResponse(
resp => resp.url().includes('/api/data')
); const request = await page.waitForRequest('**/api/data'); await page.waitForFunction(() => {
return document.querySelector('.loaded') !== null;
}); await page.waitForTimeout(1000); // Avoid if possible test.beforeEach(async ({ page }) => {
await page.goto('https://example.com');
});
test.afterEach(async ({ page }) => {
// Cleanup
});
test.beforeAll(async () => {
// Setup once
});
test.afterAll(async () => {
// Teardown
}); import { test as base } from '@playwright/test';
const test = base.extend({
loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('button[type="submit"]');
await use(page);
},
});
test('logged in test', async ({ loggedInPage }) => {
// Already logged in
}); await page.route('**/api/users', route => {
route.fulfill({
status: 200,
body: JSON.stringify([{ id: 1, name: 'John' }]),
});
}); await page.route('**/api/data', async route => {
const response = await route.fetch();
const json = await response.json();
json.modified = true;
route.fulfill({ body: JSON.stringify(json) });
}); await page.route('**/*.png', route => route.abort()); await page.route('**/api/*', route => {
route.continue({
headers: {
...route.request().headers(),
'Authorization': 'Bearer token',
},
});
}); import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 13'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});