Tổng quan dự án
Project nằm tại d:\Code\automation. Đây là bản demo để minh hoạ cách một bộ automation test thực tế thường được tổ chức — không phải một vài file test rời rạc, mà có cấu trúc, có dữ liệu test tách riêng, chạy được cả trên máy cá nhân lẫn trên CI, và xuất ra báo cáo xem lại được.
Project test hai thứ độc lập nhau trong cùng một bộ khung:
- Web UI — trang demo bán hàng công khai saucedemo.com, mô phỏng luồng đăng nhập → thêm giỏ hàng → thanh toán.
- API — dịch vụ REST công khai dummyjson.com, mô phỏng luồng quản lý người dùng và đăng nhập bằng token.
Cả hai đều là hệ thống demo công khai, không cần server hay tài khoản riêng — mục đích để bất kỳ ai clone project về cũng chạy được ngay, tập trung học cách automation hoạt động thay vì loay hoay dựng môi trường.
Vì sao chọn Playwright
Ba công cụ phổ biến nhất mà Tester hay gặp là Selenium, Cypress và Playwright. Bảng dưới so sánh nhanh những điểm ảnh hưởng trực tiếp đến người mới học:
| Tiêu chí | Selenium | Cypress | Playwright |
|---|---|---|---|
| Test API trong cùng framework | Không — cần thư viện khác | Hạn chế | Có sẵn (request fixture) |
| Tự chờ phần tử sẵn sàng | Phải tự cấu hình wait | Có | Có, mặc định |
| Chạy đa trình duyệt | Có | Giới hạn hơn | Chromium, Firefox, WebKit |
| Báo cáo HTML tích hợp sẵn | Không | Có (bản trả phí đầy đủ hơn) | Có, miễn phí, có trace/video |
Vì project cần test cả UI lẫn API và muốn có report ngay mà không cài thêm gì, Playwright là lựa chọn tốn ít công cụ phụ trợ nhất.
Kiến trúc thư mục
Toàn bộ code test nằm trong tests/, tách rõ ba việc: trang UI, kịch bản test, và dữ liệu test.
Sơ đồ dữ liệu chảy khi chạy một test, cho cả hai nhánh:
Khái niệm cần biết trước khi đọc code
Năm khái niệm này xuất hiện lặp lại trong toàn bộ project. Hiểu chúng trước sẽ giúp đọc code nhanh hơn nhiều.
Locator
Cách Playwright "trỏ" tới một phần tử trên trang, ví dụ page.locator('#user-name'). Locator không tìm phần tử ngay lúc khai báo — nó chỉ thật sự tìm và tự động chờ phần tử sẵn sàng (hiện ra, không bị che, có thể click) vào lúc thao tác được gọi. Nhờ vậy code test gần như không bao giờ cần viết sleep().
Assertion (expect)
Câu khẳng định để kiểm tra kết quả có đúng như mong đợi không, ví dụ await expect(page).toHaveURL(...). Cũng tự động thử lại đến khi đúng hoặc hết thời gian chờ (mặc định 5 giây, cấu hình trong playwright.config.ts) rồi mới báo fail.
Page Object Model (POM)
Thay vì viết locator trực tiếp trong file test, project gom chúng vào một class riêng đại diện cho một màn hình — ví dụ LoginPage chứa 3 locator (ô tài khoản, ô mật khẩu, nút đăng nhập) và 1 hành động (login()). Lợi ích: khi giao diện đổi, chỉ cần sửa một chỗ trong Page Object thay vì lục tìm trong hàng chục file test.
Project (trong playwright.config.ts)
Một nhóm test chạy chung một cấu hình (trình duyệt, baseURL, headless hay không...). Config khai báo 3 project:
| Project | Chạy gì | Có trong npm test? |
|---|---|---|
ui-chromium | 7 test UI, headless, dùng hằng ngày | Có |
api | 10 test API, không cần trình duyệt | Có |
ui-slow | 7 test UI y hệt ui-chromium, nhưng headed + chậm 800ms/thao tác để quan sát | Không — chỉ chạy qua npm run test:slow |
Mặc định, Playwright chạy tất cả project khai báo trong config nếu không lọc bằng --project. Khi ui-slow được thêm vào, script "test": "playwright test" ban đầu vô tình chạy luôn cả 24 test — kể cả bật trình duyệt chậm mà không ai yêu cầu. Đã sửa lại thành "playwright test --project=ui-chromium --project=api" để npm test chỉ chạy đúng 2 project mặc định. Nhớ kiểm tra lại điều này mỗi khi thêm project mới.
Test data / fixture
Dữ liệu dùng lại ở nhiều test — tài khoản đăng nhập, thông tin thanh toán — được gom vào tests/fixtures/test-data.ts một lần, thay vì gõ tay rải rác trong từng file.
Bước 1 — Cài đặt môi trường
Bốn lệnh đã được chạy để dựng project từ số 0. Làm theo đúng thứ tự này khi setup trên một máy mới.
-
1
Kiểm tra Node.js
Playwright cần Node.js 18 trở lên. Kiểm tra bằng:
$ node -v
$ npm -v -
2
Khởi tạo project Node
Tạo
package.json— nơi khai báo tên project, script, và các thư viện sẽ cài.$ npm init -y -
3
Cài Playwright Test và TypeScript
Cài như dev dependency (
-D) vì đây là công cụ phục vụ việc test, không phải thứ ứng dụng thật cần lúc chạy.$ npm install -D @playwright/test typescript @types/node -
4
Tải trình duyệt Playwright điều khiển
Khác với
npm install— lệnh này tải một bản Chromium thật (~190MB) về máy, cộng thư viện hệ thống cần thiết (--with-deps). Không có bước này, test UI sẽ báo lỗi ngay khi mở trình duyệt.$ npx playwright install --with-deps chromium
Test API không cần trình duyệt, nhưng bước 4 vẫn cần chạy một lần vì project chung một lệnh cài đặt cho cả hai loại test.
Bước 2 — Chạy test lần đầu
Chạy 17 test mặc định (project ui-chromium + api) bằng một lệnh duy nhất:
Mặc định test UI chạy headless — trình duyệt chạy ngầm, không hiện cửa sổ, để nhanh nhất có thể. Kết quả rút gọn từ lần chạy thật:
Running 17 tests using 6 workers
ok 1 [ui-chromium] › login.spec.ts:7:7 › Login › logs in successfully with valid credentials (1.1s)
ok 8 [api] › auth.spec.ts:4:7 › Auth API › POST /auth/login succeeds with valid credentials (467ms)
...
17 passed (3.9s)
Vài biến thể hay dùng khi làm việc hằng ngày:
| Lệnh | Khi nào dùng |
|---|---|
npm run test:ui | Chỉ muốn chạy 7 test UI, bỏ qua API |
npm run test:api | Chỉ muốn chạy 10 test API, nhanh hơn vì không mở trình duyệt |
npm run test:headed | Muốn nhìn thấy trình duyệt chạy thật, tốc độ bình thường |
npm run test:slow | Muốn quan sát từng bước — trình duyệt hiện lên, mỗi thao tác chờ 800ms, chạy tuần tự 1 test/lần (--workers=1) |
npm run test:debug | Mở Playwright Inspector, chạy từng bước một để soi lỗi |
Muốn chậm hơn hay nhanh hơn nữa với test:slow, chỉnh số slowMo: 800 (đơn vị mili-giây) trong project ui-slow ở playwright.config.ts.
Bước 3 — Đọc báo cáo HTML
Sau mỗi lần chạy, Playwright ghi report vào thư mục playwright-report/. Mở bằng:
Report mở trên trình duyệt, liệt kê toàn bộ test theo từng project, kèm thời gian chạy. Riêng với test fail, report còn kèm thêm ba thứ hữu ích để tìm nguyên nhân mà không cần chạy lại:
- Screenshot tại đúng thời điểm fail (
screenshot: 'only-on-failure') - Video toàn bộ quá trình chạy test đó (
video: 'retain-on-failure') - Trace — dòng thời gian từng bước, từng network request, có thể tua qua lại như xem lại màn hình (
trace: 'on-first-retry')
Ba dòng cấu hình này nằm trong playwright.config.ts, mục use.
Đọc hiểu một test UI
Lấy ví dụ test đầu tiên trong tests/ui/login.spec.ts:
test('logs in successfully with valid credentials', async ({ page }) => {
const loginPage = new LoginPage(page);
const inventoryPage = new InventoryPage(page);
await loginPage.goto();
await loginPage.login(users.standard.username, users.standard.password);
await inventoryPage.expectLoaded();
});
File test không hề chứa một dòng locator nào (không có #user-name, không có #login-button). Toàn bộ nằm trong tests/ui/pages/LoginPage.ts:
export class LoginPage {
// locator được khai báo 1 lần trong constructor
constructor(page) {
this.usernameInput = page.locator('#user-name');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#login-button');
}
async login(username, password) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
Đọc theo đúng thứ tự: test mô tả kịch bản người dùng ("đăng nhập rồi vào được trang sản phẩm") — Page Object mô tả màn hình đó có gì và làm được gì. Nếu SauceDemo đổi id nút đăng nhập ngày mai, chỉ sửa một dòng trong LoginPage.ts, không đụng vào 4 test đang dùng nó.
Đọc hiểu test API
Test API khác hẳn test UI: không mở trình duyệt, không tìm phần tử — chỉ gọi thẳng HTTP request rồi kiểm tra response trả về. Đó là lý do trong report test API chạy dưới nửa giây, còn test UI mất cả giây.
request đến từ đâu
Tham số { request } trong mỗi test là một fixture Playwright tự tạo sẵn (APIRequestContext). Nó biết gọi tới đâu nhờ baseURL khai báo trong project api của playwright.config.ts, nên trong test chỉ cần viết đường dẫn tương đối:
{
name: 'api',
testMatch: 'tests/api/**/*.spec.ts',
use: { baseURL: 'https://dummyjson.com' },
}
6 test CRUD trong users.spec.ts
Đủ 4 thao tác cơ bản của một API quản lý dữ liệu, kèm cả một trường hợp lỗi — không chỉ test đường "happy path":
| Test | Method | Kiểm tra gì |
|---|---|---|
| GET /users?limit=5 | GET | status 200, mảng đúng 5 phần tử, có field email |
| GET /users/2 | GET | status 200, id đúng bằng 2 |
| GET /users/999999 | GET | status 404 — id không tồn tại |
| POST /users/add | POST | status 201 (Created), body đúng dữ liệu vừa gửi |
| PUT /users/2 | PUT | status 200, field đã đổi đúng giá trị mới |
| DELETE /users/2 | DELETE | status 200, body.isDeleted === true |
2 tầng kiểm tra trong mọi test API
Chỉ check status code là chưa đủ — server có thể trả 200 nhưng dữ liệu sai hoặc thiếu field. Đây là lỗi rất hay gặp ở người mới viết API test. Mọi test trong project đều kiểm tra đủ 2 tầng:
expect(response.status()).toBe(200); // tầng 1: hợp đồng HTTP đúng không
const body = await response.json();
expect(body.id).toBe(2); // tầng 2: dữ liệu trả về đúng không
Nối request: đăng nhập lấy token rồi gọi API cần xác thực
Test khó nhất trong tests/api/auth.spec.ts gọi 2 API liên tiếp — pattern rất phổ biến khi test bất kỳ hệ thống nào có xác thực:
test('GET /auth/me returns current user with valid token', async ({ request }) => {
// Bước 1 — đăng nhập để lấy accessToken
const loginResponse = await request.post('/auth/login', {
data: { username: 'emilys', password: 'emilyspass' },
});
const { accessToken } = await loginResponse.json();
// Bước 2 — dùng token đó để gọi API cần xác thực
const meResponse = await request.get('/auth/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
expect(meResponse.status()).toBe(200);
});
Đây là cách automation mô phỏng luồng "đăng nhập rồi thao tác" mà không cần giữ session cookie như trình duyệt — mỗi request tự mang theo bằng chứng đã đăng nhập (token) trong header.
Thực hành — tự viết một test mới
Cách học automation nhanh nhất là tự viết một test còn thiếu. Bài tập: viết test kiểm tra API GET /products/1 của dummyjson.com trả về đúng sản phẩm có id = 1.
-
1
Tạo file mới
Trong
tests/api/, tạo fileproducts.spec.ts— mọi file nằm trongtests/api/tự động chạy trong projectapinhờ dòngtestMatch: 'tests/api/**/*.spec.ts'trong config. -
2
Import và viết khung test
Bắt đầu bằng
import { test, expect } from '@playwright/test', sau đó một khốitest('...', async ({ request }) => { ... })— giống cấu trúc đã thấy ở mục 09. -
3
Gọi API và assert đủ 2 tầng
Gọi
request.get('/products/1'), kiểm tra status code trước, rồi kiểm trabody.idđúng bằng 1. -
4
Chạy riêng file vừa viết
$ npx playwright test productsPlaywright tự khớp tên file theo từ khoá, không cần gõ đường dẫn đầy đủ.
Xem đáp án gợi ý
import { test, expect } from '@playwright/test';
test('GET /products/1 returns the matching product', async ({ request }) => {
const response = await request.get('/products/1');
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.id).toBe(1);
expect(body).toHaveProperty('title');
});
CI/CD — chạy test tự động
File .github/workflows/playwright.yml khai báo một GitHub Actions workflow, kích hoạt mỗi khi có push hoặc pull request vào nhánh main. Các bước chạy tuần tự trên một máy ảo Ubuntu sạch:
- 1
Checkout code
Lấy đúng phiên bản code vừa push về máy ảo.
- 2
Setup Node 22
Cài đúng phiên bản Node đã dùng khi dựng project.
- 3
npm ciCài dependency đúng y hệt phiên bản khoá trong
package-lock.json— khácnpm installở chỗ không tự ý nâng version, đảm bảo CI luôn tái lập được kết quả giống máy dev. - 4
Cài trình duyệt
npx playwright install --with-deps chromium— máy ảo CI là máy sạch, chưa từng có Chromium. - 5
npm testChạy đúng 17 test mặc định — không chạy project
ui-slowvì scripttestđã giới hạn rõ--project. - 6
Upload report
Đính kèm toàn bộ
playwright-report/làm artifact, giữ 14 ngày — chạy cả khi test fail (if: always()) để vẫn xem lại được nguyên nhân.
Vào tab Actions trên GitHub → chọn lần chạy → mục Artifacts ở cuối trang → tải playwright-report về và mở index.html.
Cheatsheet lệnh
| Lệnh | Tác dụng |
|---|---|
npm test | Chạy 17 test mặc định (UI headless + API) |
npm run test:ui | Chỉ chạy 7 test UI (project ui-chromium) |
npm run test:api | Chỉ chạy 10 test API (project api) |
npm run test:headed | Chạy UI test, hiện trình duyệt thật, tốc độ bình thường |
npm run test:slow | Chạy UI test chậm lại (headed + slowMo 800ms) để quan sát |
npm run test:debug | Mở Playwright Inspector, chạy từng bước |
npm run report | Mở báo cáo HTML lần chạy gần nhất |
npx playwright test login | Chạy test có tên file/khớp từ khoá "login" |
npx playwright codegen saucedemo.com | Ghi thao tác chuột thành code locator tự động |
Lỗi thường gặp khi mới bắt đầu
browserType.launch: Executable doesn't exist
Chưa chạy npx playwright install --with-deps chromium, hoặc chạy trên máy mới mà quên bước này. npm install chỉ tải code, không tải trình duyệt.
Test UI timeout ngay ở bước đầu
Thường do mạng chậm khi tải saucedemo.com lần đầu. Chạy lại, hoặc tăng timeout trong playwright.config.ts nếu mạng công ty chậm thường xuyên.
Locator không tìm thấy phần tử (element not found)
Trang demo có thể đổi cấu trúc HTML theo thời gian. Dùng npm run test:debug để dừng lại đúng bước lỗi và soi DOM thật, hoặc npx playwright codegen để lấy locator mới bằng cách click trực tiếp.
npm test tự nhiên chạy nhiều test hơn / bật trình duyệt ngoài ý muốn
Kiểm tra script test trong package.json có đang giới hạn đúng --project hay không. Playwright mặc định chạy toàn bộ project khai báo trong config nếu không lọc — dễ quên nhất là sau khi thêm một project mới.
API demo reqres.in ban đầu được chọn cho phần test API, nhưng giữa chừng họ đổi chính sách — bắt buộc phải có API key đăng ký riêng, request không key trả về 401. Bài học: API miễn phí có thể đổi luật bất cứ lúc nào, nên luôn kiểm tra response thực tế bằng curl trước khi viết assertion, và cân nhắc mock lại API quan trọng thay vì phụ thuộc dịch vụ ngoài. Project đã chuyển sang dummyjson.com vì không yêu cầu đăng ký.
Bước tiếp theo
Khi đã quen với project này, vài hướng mở rộng tự nhiên để học sâu hơn:
- Thêm project
firefox/webkittrongplaywright.config.tsđể test UI trên nhiều trình duyệt cùng lúc. - Dùng
test.eachhoặcforloop để viết data-driven test — 1 kịch bản, nhiều bộ dữ liệu đầu vào. - Thêm test cho viewport mobile bằng
devices['iPhone 13']. - Tích hợp report nâng cao hơn (Allure) nếu cần chia sẻ báo cáo cho nhiều team.
- Cho CI gửi thông báo (Slack/email) khi test fail trên nhánh
main.