- Tác giả
- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Tổng hợp đầy đủ các Assertions trong Playwright - Hướng dẫn chi tiết từ cơ bản đến nâng cao
Giới thiệu
Trong bài viết trước về Playwright Assertions và Wait, chúng ta đã tìm hiểu về các khái niệm cơ bản và cách sử dụng assertions trong Playwright. Bài viết này sẽ mở rộng và đi sâu hơn vào tất cả các loại assertions có sẵn trong Playwright, cùng với các ví dụ thực tế và best practices.
Tại sao cần hiểu rõ về Assertions?
Assertions là một phần không thể thiếu trong test automation, giúp chúng ta:
- Verify rằng ứng dụng hoạt động đúng như mong đợi
- Phát hiện lỗi và regression sớm
- Tạo ra test cases chắc chắn và đáng tin cậy
- Giảm thiểu false positives trong test results
Các loại Assertions trong Playwright
Playwright cung cấp nhiều loại assertions khác nhau để phục vụ các mục đích testing khác nhau:
- Page Assertions: Kiểm tra các thuộc tính của trang web như title, URL
- Element Assertions: Verify trạng thái và thuộc tính của các elements
- Count Assertions: Kiểm tra số lượng elements
- Screenshot Assertions: So sánh visual để phát hiện regression
- Custom Assertions: Tạo assertions riêng cho business logic cụ thể
- Soft Assertions: Cho phép test tiếp tục dù có assertion fail
Trong bài viết này, chúng ta sẽ đi qua từng loại assertion với các ví dụ cụ thể và best practices.
Page Assertions
Page assertions dùng để verify các thuộc tính của trang web:
1. Title Assertions
test('Page title assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app');
// Kiểm tra title chính xác
await expect(page).toHaveTitle('Home | IT Demo Dummy');
// Kiểm tra title chứa substring
await expect(page).toHaveTitle(/Demo Dummy/);
// Kiểm tra title không chứa text nào đó
await expect(page).not.toHaveTitle(/Error/);
});
2. URL Assertions
test('Page URL assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app');
// Kiểm tra URL chính xác
await expect(page).toHaveURL('https://dummy-demo-njndx.web.app/');
// Kiểm tra URL với regex
await expect(page).toHaveURL(/dummy-demo-njndx\.web\.app/);
// Navigate và kiểm tra URL thay đổi
await page.getByTestId('feature-link-todo_list').click();
await expect(page).toHaveURL(/todo-list/);
// Kiểm tra URL không chứa parameter nào đó
await expect(page).not.toHaveURL(/debug=true/);
});
3. Network và Response Assertions
test('Network response assertions', async ({ page }) => {
// Listen for API responses
const responsePromise = page.waitForResponse('**/api/todos');
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
const response = await responsePromise;
// Kiểm tra response status
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy();
// Kiểm tra response headers
expect(response.headers()['content-type']).toContain('application/json');
// Kiểm tra response body
const responseBody = await response.json();
expect(responseBody).toHaveProperty('todos');
expect(responseBody.todos).toBeInstanceOf(Array);
});
Element Assertions
Element assertions là nhóm assertions được sử dụng nhiều nhất để verify trạng thái của các elements:
1. Visibility Assertions
test('Element visibility assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra element có visible không
await expect(page.getByRole('heading', { name: 'Todo List' })).toBeVisible();
// Kiểm tra element bị ẩn
await expect(page.getByTestId('loading-spinner')).toBeHidden();
// Kiểm tra element attached to DOM
await expect(page.getByTestId('todo-input')).toBeAttached();
// Kiểm tra element detached from DOM
await expect(page.getByTestId('deleted-todo')).toBeDetached();
});
2. Content Assertions
test('Element content assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra text content chính xác
await expect(page.getByRole('heading')).toHaveText('Todo List');
// Kiểm tra text content chứa substring
await expect(page.getByTestId('welcome-message')).toContainText('Welcome');
// Kiểm tra text content với regex
await expect(page.getByTestId('todo-count')).toHaveText(/\d+ items/);
// Kiểm tra text content với array (multiple elements)
await expect(page.locator('.todo-item .title')).toHaveText([
'Learn Playwright',
'Write tests',
'Deploy application'
]);
// Kiểm tra inner HTML
await expect(page.getByTestId('formatted-text')).toContainText('Bold text');
});
3. Form Element Assertions
test('Form element assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/contact');
// Input value assertions
await page.getByLabel('Email').fill('test@example.com');
await expect(page.getByLabel('Email')).toHaveValue('test@example.com');
// Checkbox assertions
await page.getByLabel('Subscribe to newsletter').check();
await expect(page.getByLabel('Subscribe to newsletter')).toBeChecked();
// Radio button assertions
await page.getByLabel('Male').check();
await expect(page.getByLabel('Male')).toBeChecked();
await expect(page.getByLabel('Female')).not.toBeChecked();
// Select dropdown assertions
await page.getByLabel('Country').selectOption('Vietnam');
await expect(page.getByLabel('Country')).toHaveValue('Vietnam');
// File input assertions
await page.getByLabel('Upload file').setInputFiles('test-file.pdf');
const fileInput = page.getByLabel('Upload file');
expect(await fileInput.inputValue()).toContain('test-file.pdf');
});
4. State Assertions
test('Element state assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra element enabled/disabled
await expect(page.getByTestId('add-button')).toBeEnabled();
await expect(page.getByTestId('disabled-button')).toBeDisabled();
// Kiểm tra element editable
await expect(page.getByTestId('todo-input')).toBeEditable();
await expect(page.getByTestId('readonly-text')).not.toBeEditable();
// Kiểm tra element focused
await page.getByTestId('todo-input').focus();
await expect(page.getByTestId('todo-input')).toBeFocused();
// Kiểm tra element empty
await expect(page.getByTestId('empty-list')).toBeEmpty();
await expect(page.getByTestId('todo-list')).not.toBeEmpty();
});
5. Attribute Assertions
test('Element attribute assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra attribute có tồn tại
await expect(page.getByTestId('todo-item')).toHaveAttribute('data-id');
// Kiểm tra attribute value chính xác
await expect(page.getByTestId('todo-item')).toHaveAttribute('data-id', '123');
// Kiểm tra attribute value với regex
await expect(page.getByTestId('todo-item')).toHaveAttribute('class', /todo-item/);
// Kiểm tra CSS class
await expect(page.getByTestId('completed-todo')).toHaveClass('completed');
await expect(page.getByTestId('important-todo')).toHaveClass(['todo-item', 'important']);
// Kiểm tra CSS properties
await expect(page.getByTestId('red-text')).toHaveCSS('color', 'rgb(255, 0, 0)');
await expect(page.getByTestId('large-text')).toHaveCSS('font-size', '24px');
});
Count Assertions
Kiểm tra số lượng elements - rất hữu ích cho dynamic content:
test('Count assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra số lượng chính xác
await expect(page.locator('.todo-item')).toHaveCount(5);
// Kiểm tra ít nhất
await expect(page.locator('.todo-item')).toHaveCount({ min: 3 });
// Kiểm tra tối đa
await expect(page.locator('.todo-item')).toHaveCount({ max: 10 });
// Add new todo và verify count tăng
await page.getByTestId('todo-input').fill('New todo');
await page.getByTestId('add-button').click();
await expect(page.locator('.todo-item')).toHaveCount(6);
// Delete todo và verify count giảm
await page.locator('.todo-item').first().getByRole('button', { name: 'Delete' }).click();
await expect(page.locator('.todo-item')).toHaveCount(5);
});
Screenshot Assertions
So sánh visual để detect regression:
test('Screenshot assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Screenshot toàn trang
await expect(page).toHaveScreenshot('todo-page.png');
// Screenshot một element cụ thể
await expect(page.getByTestId('todo-list')).toHaveScreenshot('todo-list.png');
// Screenshot với options
await expect(page).toHaveScreenshot('todo-page-mobile.png', {
fullPage: true,
threshold: 0.2, // 20% difference tolerance
mask: [page.getByTestId('dynamic-timestamp')] // Mask dynamic content
});
// Screenshot comparison với custom name
await expect(page.locator('.todo-item').first()).toHaveScreenshot({
name: 'first-todo-item.png',
threshold: 0.1
});
});
Custom Assertions và Matchers
Tạo custom assertions cho business logic cụ thể:
// Custom assertion function
async function expectTodoToBeCompleted(page: Page, todoText: string) {
const todoItem = page.locator('.todo-item').filter({ hasText: todoText });
await expect(todoItem).toHaveClass(/completed/);
await expect(todoItem.getByRole('checkbox')).toBeChecked();
}
// Custom matcher
expect.extend({
async toHaveValidEmail(locator: Locator) {
const textContent = await locator.textContent();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const pass = emailRegex.test(textContent || '');
return {
message: () => `Expected ${textContent} to be a valid email`,
pass
};
}
});
test('Custom assertions example', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Sử dụng custom assertion function
await expectTodoToBeCompleted(page, 'Learn Playwright');
// Sử dụng custom matcher
await expect(page.getByTestId('user-email')).toHaveValidEmail();
});
Soft Assertions
Cho phép test tiếp tục chạy dù assertion fail:
test('Soft assertions example', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Soft assertions - test sẽ không dừng ngay khi fail
await expect.soft(page.getByRole('heading')).toHaveText('Todo List');
await expect.soft(page.locator('.todo-item')).toHaveCount(5);
await expect.soft(page.getByTestId('add-button')).toBeEnabled();
// Test tiếp tục chạy dù các soft assertion ở trên có thể fail
await page.getByTestId('todo-input').fill('New todo');
await page.getByTestId('add-button').click();
// Hard assertion - test sẽ dừng nếu fail
await expect(page.locator('.todo-item')).toHaveCount(6);
});
Assertion Timeout và Configuration
1. Custom Timeout cho Assertions
test('Custom assertion timeout', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/slow-loading-page');
// Tăng timeout cho assertion cụ thể
await expect(page.getByTestId('slow-content')).toBeVisible({ timeout: 10000 });
// Giảm timeout cho assertion nhanh
await expect(page.getByTestId('instant-content')).toBeVisible({ timeout: 1000 });
});
2. Global Assertion Configuration
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
// Global assertion timeout
expect: {
timeout: 10000, // 10 seconds
toHaveScreenshot: { threshold: 0.2 },
toMatchSnapshot: { threshold: 0.3 }
},
use: {
// Screenshot comparison settings
screenshot: 'only-on-failure'
}
});
Debugging Assertions
1. Assertion Error Messages
test('Understanding assertion errors', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
try {
// Assertion có thể fail
await expect(page.getByRole('heading')).toHaveText('Wrong Title');
} catch (error) {
console.log('Assertion Error:', error.message);
// Error message sẽ rất chi tiết:
// Expected string: "Wrong Title"
// Received string: "Todo List"
}
});
2. Debug Mode với Assertions
test('Debug assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Log element content trước khi assert
const headingText = await page.getByRole('heading').textContent();
console.log('Heading text:', headingText);
// Highlight element trước khi assert
await page.getByRole('heading').highlight();
// Take screenshot trước assertion quan trọng
await page.screenshot({ path: 'before-assertion.png' });
await expect(page.getByRole('heading')).toHaveText('Todo List');
});
Advanced Assertion Patterns
1. Conditional Assertions
test('Conditional assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Kiểm tra điều kiện trước khi assert
const todoCount = await page.locator('.todo-item').count();
if (todoCount > 0) {
await expect(page.getByTestId('clear-all-button')).toBeVisible();
await expect(page.getByTestId('todo-counter')).toContainText(`${todoCount} items`);
} else {
await expect(page.getByTestId('empty-message')).toBeVisible();
await expect(page.getByTestId('clear-all-button')).toBeHidden();
}
});
2. Polling Assertions
test('Polling assertions for dynamic content', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/live-updates');
// Wait for dynamic content to update
await expect(async () => {
const liveCount = await page.getByTestId('live-counter').textContent();
expect(parseInt(liveCount || '0')).toBeGreaterThan(0);
}).toPass();
// Polling with custom timeout and interval
await expect(async () => {
const status = await page.getByTestId('connection-status').textContent();
expect(status).toBe('Connected');
}).toPass({
timeout: 15000,
intervals: [500, 1000, 2000] // Custom retry intervals
});
});
3. Multiple Element Assertions
test('Multiple element assertions', async ({ page }) => {
await page.goto('https://dummy-demo-njndx.web.app/todo-list');
// Assert tất cả todo items có structure đúng
const todoItems = page.locator('.todo-item');