- name
- web-testing
- description
- Web testing patterns and best practices using Playwright for automated testing
# Web Testing with Playwright
This skill covers comprehensive web testing patterns using Playwright, including test structure, assertions, mocking, and best practices for reliable automated testing.
## Test Structure
### Basic Test Organization
```javascript
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com/login');
});
test('should login with valid credentials', async ({ page }) => {
const username = 'testuser@example.com';
const password = 'SecurePass123';
await page.fill('input[name="email"]', username);
await page.fill('input[name="password"]', password);
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('.welcome-message')).toBeVisible();
});
test('should show error with invalid credentials', async ({ page }) => {
await page.fill('input[name="email"]', 'wrong@example.com');
await page.fill('input[name="password"]', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
});
});
```
### Test Hooks and Lifecycle
```javascript
import { test } from '@playwright/test';
test.describe('E-commerce Checkout', () => {
test.beforeAll(async ({ browser }) => {
console.log('Setting up test database');
});
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
});
test.afterEach(async ({ page }) => {
await page.click('[data-testid="clear-cart"]');
});
test.afterAll(async () => {
console.log('Cleaning up test database');
});
test('should add item to cart', async ({ page }) => {
await page.click('[data-product-id="123"]');
await page.click('button:has-text("Add to Cart")');
await expect(page.locator('.cart-count')).toHaveText('1');
});
});
```
### Parameterized Tests
```javascript
import { test, expect } from '@playwright/test';
const testData = [
{ input: 'john@example.com', expected: true },
{ input: 'invalid-email', expected: false },
{ input: 'another@test.co.uk', expected: true },
{ input: '@nodomain.com', expected: false }
];
testData.forEach(({ input, expected }) => {
test(`should validate email: ${input}`, async ({ page }) => {
await page.goto('https://example.com/signup');
await page.fill('input[name="email"]', input);
await page.click('button[type="submit"]');
if (expected) {
await expect(page.locator('.error-message')).not.toBeVisible();
} else {
await expect(page.locator('.error-message')).toBeVisible();
}
});
});
```
### Test Fixtures
```javascript
import { test as base, expect } from '@playwright/test';
const test = base.extend({
authenticatedPage: async ({ page }, use) => {
await page.goto('https://example.com/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
await use(page);
await page.click('[data-testid="logout"]');
}
});
test('should access protected resource', async ({ authenticatedPage }) => {
await authenticatedPage.goto('https://example.com/profile');
await expect(authenticatedPage.locator('h1')).toContainText('My Profile');
});
```
## Assertions
### Element Visibility Assertions
```javascript
import { test, expect } from '@playwright/test';
test('element visibility checks', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.locator('.header')).toBeVisible();
await expect(page.locator('.modal')).toBeHidden();
await expect(page.locator('.hidden-div')).toBeAttached();
await expect(page.locator('.removed-element')).not.toBeAttached();
});
```
### Text Content Assertions
```javascript
test('text content checks', async ({ page }) => {
await page.goto('https://example.com');
await expect(page.locator('h1')).toHaveText('Welcome');
await expect(page.locator('.description')).toContainText('product');
await expect(page.locator('.price')).toHaveText(/\$\d+\.\d{2}/);
await expect(page.locator('.item-title')).toHaveText([
'Item 1',
'Item 2',
'Item 3'
]);
});
```
### Attribute and State Assertions
```javascript
test('attribute and state checks', async ({ page }) => {
await page.goto('https://example.com/form');
await expect(page.locator('input[name="email"]')).toHaveAttribute('type', 'email');
await expect(page.locator('.button')).toHaveClass(/primary/);
await expect(page.locator('.list-item')).toHaveCount(5);
await expect(page.locator('input[name="username"]')).toHaveValue('john.doe');
await expect(page.locator('input[type="checkbox"]')).toBeChecked();
await expect(page.locator('input[type="checkbox"]')).not.toBeChecked();
await expect(page.locator('button[type="submit"]')).toBeEnabled();
await expect(page.locator('button.disabled')).toBeDisabled();
await expect(page.locator('input[name="email"]')).toBeEditable();
});
```
### URL and Page Assertions
```javascript
test('URL and page checks', async ({ page }) => {
await page.goto('https://example.com/products');
await expect(page).toHaveURL('https://example.com/products');
await expect(page).toHaveURL(/.*products/);
await expect(page).toHaveTitle('Products - Example Store');
await expect(page).toHaveTitle(/Products/);
});
```
### Custom Assertions
```javascript
test('custom assertions', async ({ page }) => {
await page.goto('https://example.com/cart');
const cartTotal = await page.locator('.cart-total').textContent();
expect(parseFloat(cartTotal.replace('$', ''))).toBeGreaterThan(0);
await expect.soft(page.locator('.item')).toHaveCount(3);
await expect.soft(page.locator('.discount-badge')).toBeVisible();
const element = page.locator('.product');
await expect(element).toBeVisible();
await expect(element).toContainText('Special Offer');
await expect(element).toHaveClass('highlighted');
});
```
## Mocking
### Network Request Interception
```javascript
import { test, expect } from '@playwright/test';
test('mock API response', async ({ page }) => {
await page.route('**/api/products', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Mocked Product 1', price: 19.99 },
{ id: 2, name: 'Mocked Product 2', price: 29.99 }
])
});
});
await page.goto('https://example.com/products');
await expect(page.locator('.product').first()).toContainText('Mocked Product 1');
});
```
### Conditional Mocking
```javascript
test('conditional network mocking', async ({ page }) => {
await page.route('**/api/**', route => {
const url = route.request().url();
if (url.includes('/api/slow-endpoint')) {
route.fulfill({
status: 200,
body: JSON.stringify({ data: 'fast response' })
});
} else if (url.includes('/api/error-prone')) {
route.fulfill({ status: 500 });
} else {
route.continue();
}
});
await page.goto('https://example.com');
});
```
### Request Modification
```javascript
test('modify outgoing requests', async ({ page }) => {
await page.route('**/api/data', route => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer mock-token-123'
};
route.continue({ headers });
});
await page.goto('https://example.com/dashboard');
});
```
### Response Delays
```javascript
test('simulate slow network', async ({ page }) => {
await page.route('**/api/products', async route => {
await new Promise(resolve => setTimeout(resolve, 3000));
await route.fulfill({
status: 200,
body: JSON.stringify({ products: [] })
});
});
await page.goto('https://example.com/products');
await expect(page.locator('.loading-spinner')).toBeVisible();
});
```
## Best Practices
### Reliable Selectors
```javascript
// GOOD: Use test IDs
GitHubで見る