NỘI DUNG BÀI HỌC
🏗️ Phần 1: Nền tảng - Cấu trúc một File Test Chuyên nghiệp
🚀 Phần 2: Tăng tốc - Sử dụng Playwright Codegen
🎬 Phần 1: Codegen - trợ lý sinh code tự động
Codegen (Code Generator) là công cụ quan sát mọi thao tác bạn làm trên trình duyệt (click, gõ phím, chọn dropdown) rồi tự động sinh code Playwright tương ứng. Nó biến tương tác thủ công thành kịch bản test hoàn chỉnh, rất hợp để tạo bản nháp nhanh.
🔹 1. Khởi động và ghi kịch bản
Chạy lệnh sau từ thư mục dự án. Hai cửa sổ sẽ bật lên: trình duyệt Chromium để thao tác, và Playwright Inspector hiển thị code sinh ra theo thời gian thực.
npx playwright codegen https://hrm.anhtester.com/
Bạn thao tác đăng nhập, mỗi hành động sinh ra một dòng code. Đặc biệt nên dùng nút Assert Visibility (biểu tượng con mắt) để sinh thêm dòng khẳng định.
🔹 2. Code sinh ra và cách chạy lại
import { test, expect } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('https://hrm.anhtester.com/');
await page.locator('#iusername').click();
await page.locator('#iusername').fill('admin_example'); // Điền username
await page.locator('#ipassword').click();
await page.locator('#ipassword').fill('123456'); // Điền mật khẩu
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.locator('.page-header h4')).toBeVisible(); // Assertion do "con mắt" sinh ra
});
npx playwright test tests/hrm-login.spec.ts
💡 Mẹo dùng đúng: Coi Codegen là trợ thủ, không phải người thay thế. Quy trình chuẩn là Record (ghi luồng cơ bản) → Refactor (tái cấu trúc theo Page Object Model, đặt tên biến rõ ràng) → Enhance (thêm vòng lặp, điều kiện, dữ liệu ngoài). Code sinh ra thường tuyến tính, lặp locator và dễ vỡ nên đừng giữ nguyên.
🗂️ Phần 2: Tổ chức file test hiệu quả
Cách viết một danh sách phẳng các test() chỉ hợp với file rất nhỏ (dưới 5 test). Khi test tăng lên, nó bị lặp code và thiếu nhóm gộp logic. Cách chuyên nghiệp là dùng test.describe() để gom nhóm.
🔹 1. Nhóm gộp với test.describe() và beforeEach()
import { test, expect } from '@playwright/test';
test.describe('Trang chủ Playwright.dev', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://playwright.dev/'); // Chạy trước mỗi test, tránh lặp code (DRY)
});
test('Nên có tiêu đề đúng', async ({ page }) => {
await expect(page).toHaveTitle(/Playwright/);
});
test.describe('Chức năng Tìm kiếm', () => { // Nhóm lồng nhau
test('Click nút Search sẽ mở ô nhập liệu', async ({ page }) => {
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByPlaceholder('Search docs')).toBeVisible();
});
});
});
File test lúc này giống một cuốn sách có chương và mục con: báo cáo hiển thị rõ cấu trúc, dùng được hooks (beforeEach, afterEach) để tránh lặp. Nên dùng làm mặc định khi có từ 2 test liên quan trở lên.
🔹 2. Tách nhiều file và quy ước *.spec.ts
Khi một file quá lớn (300-400 dòng), hãy tách theo quy tắc "một file cho một trang/tính năng": login.spec.ts, dashboard.spec.ts, employees.spec.ts. Cách này giúp quản lý dễ và tận dụng chạy song song trên nhiều worker.
Playwright mặc định tìm file kết thúc bằng *.spec.ts hoặc *.test.ts. Từ "spec" là viết tắt của Specification - xem file test như một bản đặc tả có thể thực thi (tư duy từ BDD). Bạn có thể đổi mẫu này qua testMatch trong config, nhưng nên giữ mặc định.
import { defineConfig } from '@playwright/test';
export default defineConfig({
testMatch: '**/*.kiemthu.ts', // Đổi quy ước tên file test nếu thật sự cần
});
🪜 Phần 3: Chia bước rõ ràng với test.step()
test.step() chia một test dài thành các bước logic có tên. Nếu không dùng, khi test fail bạn chỉ biết "có lỗi đâu đó". Khi dùng, báo cáo chỉ chính xác bước nào hỏng, gỡ lỗi nhanh hơn nhiều.
import { test, expect } from '@playwright/test';
test('Đăng nhập và kiểm tra widget', async ({ page, browserName }) => {
await test.step('Bước 1: Điều hướng và đăng nhập', async () => {
await page.goto('https://hrm.anhtester.com/');
await page.locator('#iusername').fill('admin_example');
await page.locator('#ipassword').fill('password_example');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.locator('.page-header h4')).toHaveText('Welcome back, Admin!');
});
await test.step('Bước 2: Kiểm tra widget trên Dashboard', async () => {
await expect(page.getByText('Employees')).toBeVisible();
await expect(page.getByText('Projects')).toBeVisible();
});
await test.step('Bước 3: Widget đặc biệt (chỉ Chrome)', async (step) => {
step.skip(browserName !== 'chromium', 'Widget này chỉ thiết kế cho Chrome.');
await expect(page.locator('#chrome-special-widget')).toBeVisible();
});
});
Trên Chrome cả 3 bước chạy; trên Firefox/WebKit thì Bước 3 bị đánh dấu skipped nhưng test vẫn PASS.
🏷️ Phần 4: Annotations - điều khiển cách chạy test
Annotations là các "nhãn" gắn vào test hoặc suite để ra lệnh cho Test Runner, cung cấp metadata và thay đổi cách thực thi/báo cáo.
🔹 1. test.skip - bỏ qua, không chạy
// Bỏ qua vô điều kiện (tính năng chưa xong)
test.skip('Upload file nâng cao chưa hoàn thiện', async ({ page }) => {});
// Bỏ qua toàn bộ nhóm
test.describe.skip('Nhóm test cho Admin V2', () => {});
// Bỏ qua theo điều kiện
test('Giao diện chỉ dành cho WebKit', async ({ page, browserName }) => {
test.skip(browserName !== 'webkit', 'Chỉ tối ưu cho WebKit');
});
🔹 2. test.fail - dự kiến sẽ thất bại
Test vẫn chạy nhưng được kỳ vọng fail. Rất hữu ích để quản lý bug: khi bug được sửa, test bất ngờ pass và Playwright báo "unexpected pass" để nhắc bạn gỡ nhãn.
test.fail('Đang thất bại do bug #123 trong logic tính toán', async ({ page }) => {
await page.locator('#calculate').click();
await expect(page.locator('#result')).toHaveText('100');
});
🔹 3. fixme, slow và only
// Giống skip nhưng mạnh hơn, đánh dấu test hỏng cần sửa
test.fixme('Cần cập nhật sau khi refactor API', async ({ page }) => {});
// Tăng timeout lên gấp 3 lần cho test chạy lâu
test.slow('Xuất báo cáo tài chính cả năm', async ({ page }) => {});
// Chỉ chạy test này, bỏ qua tất cả còn lại - công cụ debug
test.only('Tập trung debug test này', async ({ page }) => {});
⚠️ Lưu ý: Tuyệt đối không commit
test.only()vào nhánh chính. Trên CI/CD nó sẽ khiến mọi test khác bị bỏ qua mà không ai để ý.
🔹 Ghi nhớ nhanh cho Tester
- Codegen chỉ để tạo nháp nhanh, luôn refactor lại theo POM trước khi dùng thật.
- Dùng
test.describe()+beforeEach()làm mặc định để tổ chức test và tránh lặp code. - Tách file theo trang/tính năng khi file vượt 300-400 dòng để chạy song song tốt hơn.
test.step()giúp báo cáo chỉ đúng bước hỏng, gỡ lỗi nhanh hơn.skipbỏ qua,failkỳ vọng lỗi,fixmeđánh dấu cần sửa,slowtăng timeout x3,onlychỉ chạy một test.- Không bao giờ commit
test.only()lên nhánh chính.
✅ Kết luận: Bài 9 giúp bạn chuyển từ viết test rời rạc sang tổ chức bộ test chuyên nghiệp. Kết hợp Codegen để tạo nháp, describe/step để cấu trúc rõ ràng, và annotations để điều khiển cách chạy, bạn sẽ có một bộ test dễ đọc, dễ bảo trì và sẵn sàng cho CI/CD.
