1. Giới thiệu
Playwright Fixture là gì?
Playwright Fixture là một cơ chế mạnh mẽ giúp tự động hóa việc setup và teardown các điều kiện cần thiết cho test. Fixtures giúp bạn:
- Tái sử dụng code setup/teardown giữa các test
- Quản lý dependency một cách rõ ràng
- Đảm bảo isolation giữa các test
- Tối ưu performance bằng cách chia sẻ resource khi phù hợp
Tại sao cần sử dụng Fixture?
Trong testing, chúng ta thường phải:
- Setup môi trường test (database, authentication, mock data)
- Cleanup sau khi test chạy xong
- Đảm bảo mỗi test chạy độc lập
Không có fixture, code sẽ bị duplicate và khó maintain:
// Không dùng fixture - Code bị lặp lại
test('test 1', async ({ page }) => {
// Setup
await page.goto('https://example.com');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password');
await page.click('#login');
// Test logic
await expect(page.locator('.dashboard')).toBeVisible();
// Cleanup
await page.click('#logout');
});
test('test 2', async ({ page }) => {
// Lặp lại setup giống hệt
await page.goto('https://example.com');
await page.fill('#username', 'testuser');
await page.fill('#password', 'password');
await page.click('#login');
// Test logic khác
await page.click('.settings');
// Cleanup
await page.click('#logout');
});
So sánh với các Framework khác
Framework | Setup/Teardown | Dependency Injection | Scope Control |
---|---|---|---|
Playwright | Fixtures | ✓ Built-in | ✓ Flexible |
Jest | beforeEach/afterEach | ✗ Manual | ✗ Limited |
Mocha | before/after hooks | ✗ Manual | ✗ Limited |
Cypress | beforeEach/afterEach | ✗ Manual | ✗ Limited |
2. Khái niệm cơ bản về Fixture
Định nghĩa Fixture
Fixture là một function đặc biệt:
- Chạy trước test để setup môi trường
- Cung cấp giá trị cho test sử dụng
- Tự động cleanup sau khi test kết thúc
// Fixture cơ bản
const test = base.extend({
// Fixture name: userData
userData: async ({}, use) => {
// Setup: Tạo test data
const data = {
username: 'testuser',
email: 'test@example.com'
};
// Use: Cung cấp data cho test
await use(data);
// Teardown: Cleanup (tự động chạy sau test)
console.log('Cleaning up user data...');
}
});
Lifecycle của Fixture

Ví dụ chi tiết về lifecycle:
const test = base.extend({
dbConnection: async ({}, use) => {
console.log('1. Setup: Connecting to database...');
const db = await connectToDatabase();
console.log('2. Providing db connection to test');
await use(db);
console.log('3. Teardown: Closing database connection...');
await db.close();
}
});
test('database test', async ({ dbConnection }) => {
console.log('Test running with db connection');
const users = await dbConnection.query('SELECT * FROM users');
expect(users).toHaveLength(5);
});
// Output:
// 1. Setup: Connecting to database...
// 2. Providing db connection to test
// Test running with db connection
// 3. Teardown: Closing database connection...
Phân loại Fixture
Built-in Fixtures
Playwright cung cấp sẵn nhiều fixtures hữu ích:
test('using built-in fixtures', async ({
page, // Browser page
context, // Browser context
browser, // Browser instance
browserName, // 'chromium' | 'firefox' | 'webkit'
request // API request context
}) => {
// Sử dụng trực tiếp
await page.goto('https://example.com');
});
Custom Fixtures
Fixtures do bạn tự định nghĩa:
const test = base.extend({
// Custom fixture
authenticatedPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#username', 'admin');
await page.fill('#password', 'secret');
await page.click('#login-btn');
await use(page);
await page.click('#logout');
}
});
3. Built-in Fixtures có sẵn
Page Fixture
Fixture phổ biến nhất, đại diện cho một browser page:
test('page fixture example', async ({ page }) => {
// Navigation
await page.goto('https://playwright.dev');
// Interactions
await page.click('text=Get started');
await page.fill('#search', 'fixtures');
// Assertions
await expect(page).toHaveTitle(/Playwright/);
await expect(page.locator('.results')).toBeVisible();
// Screenshot
await page.screenshot({ path: 'example.png' });
});
Context Fixture
Browser context cho phép tạo isolated browser session:
test('context fixture example', async ({ context, page }) => {
// Set cookies cho toàn bộ context
await context.addCookies([{
name: 'session',
value: 'abc123',
domain: 'example.com',
path: '/'
}]);
// Set extra HTTP headers
await context.setExtraHTTPHeaders({
'X-API-Key': 'my-api-key'
});
// Tạo multiple pages (nhiều tab) trong cùng context
const page1 = await context.newPage();
const page2 = await context.newPage();
// Pages share cookies và storage
await page1.goto('https://example.com');
await page2.goto('https://example.com'); // Đã có cookie
});
Browser Fixture
Quản lý browser instance:
test('browser fixture example', async ({ browser }) => {
// Tạo multiple contexts
const userContext = await browser.newContext({
userAgent: 'Custom User Agent'
});
const adminContext = await browser.newContext({
httpCredentials: {
username: 'admin',
password: 'password'
}
});
// Mỗi context độc lập
const userPage = await userContext.newPage();
const adminPage = await adminContext.newPage();
// Cleanup
await userContext.close();
await adminContext.close();
});
Request Fixture
Cho API testing:
test('request fixture example', async ({ request }) => {
// GET request
const response = await request.get('https://api.example.com/users');
expect(response.ok()).toBeTruthy();
const users = await response.json();
expect(users).toHaveLength(10);
// POST request
const newUser = await request.post('https://api.example.com/users', {
data: {
name: 'John Doe',
email: 'john@example.com'
}
});
expect(newUser.status()).toBe(201);
});
Scope của Fixtures
// Test scope (default) - Mới cho mỗi test
test.extend({
testData: async ({}, use) => {
const data = generateTestData();
await use(data);
}
});
// Worker scope - Shared trong worker
test.extend({
workerDatabase: [async ({}, use) => {
const db = await setupDatabase();
await use(db);
await db.teardown();
}, { scope: 'worker' }]
});
// Auto fixture - Chạy tự động
test.extend({
autoFixture: [async ({}, use) => {
console.log('This runs automatically');
await use();
}, { auto: true }]
});
4. Tạo Custom Fixture
Cú pháp cơ bản với test.extend()
import { test as base } from '@playwright/test';
// Định nghĩa types cho fixtures
type MyFixtures = {
todoPage: Page;
testData: {
username: string;
password: string;
};
};
// Extend base test với custom fixtures
const test = base.extend<MyFixtures>({
// Simple fixture
testData: async ({}, use) => {
await use({
username: 'testuser',
password: 'Test@123'
});
},
// Fixture with dependencies
todoPage: async ({ page, testData }, use) => {
await page.goto('/login');
await page.fill('#username', testData.username);
await page.fill('#password', testData.password);
await page.click('#login');
await page.goto('/todo');
await use(page);
}
});
Setup và Cleanup Logic
const test = base.extend({
database: async ({}, use) => {
// Setup
console.log('Setting up database...');
const db = new TestDatabase();
await db.connect();
await db.seed(); // Insert test data
// Provide to test
await use(db);
// Cleanup - ALWAYS runs, even if test fails
console.log('Cleaning up database...');
await db.clean();
await db.disconnect();
}
});
test('database operations', async ({ database }) => {
const user = await database.createUser({
name: 'John',
email: 'john@example.com'
});
expect(user.id).toBeDefined();
// Cleanup tự động chạy sau test
});
Dependency Injection giữa các Fixtures
const test = base.extend({
// Base fixture
apiClient: async ({}, use) => {
const client = new APIClient({
baseURL: 'https://api.example.com'
});
await use(client);
},
// Fixture phụ thuộc vào apiClient
authenticatedClient: async ({ apiClient }, use) => {
const token = await apiClient.login('admin', 'password');
apiClient.setAuthToken(token);
await use(apiClient);
},
// Fixture phụ thuộc vào nhiều fixtures
testEnvironment: async ({ page, authenticatedClient }, use) => {
// Setup UI với authenticated state
await page.goto('/');
await page.evaluate((token) => {
localStorage.setItem('authToken', token);
}, authenticatedClient.token);
await use({
page,
api: authenticatedClient
});
}
});
Fixture với Parameters
// Option 1: Factory function
const test = base.extend({
createUser: async ({}, use) => {
const users = [];
// Factory function
await use(async (userData) => {
const user = await api.createUser(userData);
users.push(user);
return user;
});
// Cleanup all created users
for (const user of users) {
await api.deleteUser(user.id);
}
}
});
test('create multiple users', async ({ createUser }) => {
const user1 = await createUser({ name: 'User 1' });
const user2 = await createUser({ name: 'User 2' });
expect(user1.id).not.toBe(user2.id);
});
// Option 2: Configuration fixture
const test = base.extend({
serverURL: ['https://staging.example.com', { option: true }],
apiClient: async ({ serverURL }, use) => {
const client = new APIClient({ baseURL: serverURL });
await use(client);
}
});
// Override in config or test
test.use({ serverURL: 'https://production.example.com' });
Trả lời