Playwright TypeScript cho người mới: từ cài đặt đến Page Object, fixture và test data-driven
Nếu bạn là tester manual muốn chuyển sang automation, hoặc dev muốn viết E2E test cho dự án, Playwright là lựa chọn rất dễ bắt đầu: cài một lệnh, tự chờ phần tử, có trace để debug, chạy được Chromium/Firefox/WebKit. Bài này đi từ con số 0 đến một bộ test có cấu trúc gọn gàng: Page Object, fixture, test data-driven và chạy trên CI.
Toàn bộ code trong bài nằm trong repo miễn phí, clone về là chạy được: https://github.com/qakit-vn/playwright-starter-vn-lite
1. Cài đặt
Cần Node.js 18 trở lên.
mkdir pw-demo && cd pw-demo
npm init playwright@latest
Trình cài đặt sẽ hỏi vài câu: chọn TypeScript, thư mục test tests, có thể bỏ qua GitHub Actions lúc đầu. Sau đó cài trình duyệt (nếu chưa):
npx playwright install chromium
Hoặc dùng luôn repo mẫu:
git clone https://github.com/qakit-vn/playwright-starter-vn-lite.git
cd playwright-starter-vn-lite
npm install
npx playwright install chromium
npm test
Kết quả mong đợi là 4 test pass trong vài giây trên trang demo saucedemo.com.
2. Test đầu tiên
import { test, expect } from '@playwright/test';
test('đăng nhập thành công', async ({ page }) => {
await page.goto('https://www.saucedemo.com');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/inventory/);
});
Ba điều đáng chú ý:
- Không có
sleep.fill,clickvàexpecttự chờ phần tử sẵn sàng (auto-wait). Nếu bạn thấy mình cầnwaitForTimeout, gần như chắc chắn có cách tốt hơn. - Locator theo cách người dùng nhìn thấy:
getByRole,getByPlaceholder,getByLabel,getByText. Ổn định hơn CSS/XPath dài vì ít bị vỡ khi đổi giao diện. expectlà web-first assertion:toHaveURL,toBeVisible,toContainTextsẽ thử lại cho đến khi đúng hoặc hết timeout.
Mẹo: npx playwright codegen https://www.saucedemo.com mở trình duyệt, bạn thao tác đến đâu nó sinh code đến đó. Rất hữu ích để học cách chọn locator.
3. Cấu hình chung: playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
reporter: [['html', { open: 'never' }], ['list']],
use: {
baseURL: process.env.BASE_URL ?? 'https://www.saucedemo.com',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
baseURLgiúp viếtpage.goto('/')thay vì URL đầy đủ, và đổi môi trường bằng biến:BASE_URL=https://staging.example.com npm test.retrieschỉ bật trên CI, để ở máy local bạn thấy ngay test flaky thay vì bị che đi.trace: 'on-first-retry'ghi lại toàn bộ thao tác, DOM, network khi test chạy lại. Mở bằngnpx playwright show-tracehoặc trong HTML report. Đây là công cụ debug mạnh nhất của Playwright.
4. Page Object Model: gom locator vào một chỗ
Khi có 20 test cùng đăng nhập, bạn không muốn sửa 20 chỗ khi nút Login đổi tên. Page Object gom locator và hành động của một trang vào một class:
// pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly username: Locator;
readonly password: Locator;
readonly loginButton: Locator;
readonly error: Locator;
constructor(private readonly page: Page) {
this.username = page.getByPlaceholder('Username');
this.password = page.getByPlaceholder('Password');
this.loginButton = page.getByRole('button', { name: 'Login' });
this.error = page.locator('[data-test="error"]');
}
async goto() { await this.page.goto('/'); }
async login(user: string, pass: string) {
await this.username.fill(user);
await this.password.fill(pass);
await this.loginButton.click();
}
async expectError(text: string) { await expect(this.error).toContainText(text); }
}
Nguyên tắc nhỏ: Page Object chứa cách thao tác, còn test chứa kỳ vọng nghiệp vụ. Đừng nhồi quá nhiều logic vào page object.
5. Fixture: khỏi new LoginPage(page) ở mọi test
Fixture là tính năng hay bị bỏ qua của Playwright. Thay vì khởi tạo page object trong từng test, ta mở rộng test:
// tests/fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { InventoryPage } from '../pages/InventoryPage';
type Pages = { loginPage: LoginPage; inventoryPage: InventoryPage };
export const test = base.extend<Pages>({
loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
inventoryPage: async ({ page }, use) => { await use(new InventoryPage(page)); },
});
export { expect } from '@playwright/test';
Giờ test chỉ cần khai báo thứ mình cần trong tham số, Playwright tự tạo. Sau này bạn có thể thêm fixture như loggedInPage (đã đăng nhập sẵn) hay apiClient theo cùng cách.
6. Test data-driven: một khung, nhiều bộ dữ liệu
Các case đăng nhập sai chỉ khác nhau ở input và thông báo lỗi, nên gom thành mảng dữ liệu:
// tests/ui/login.spec.ts
import { test } from '../fixtures';
const invalidCases = [
{ id: 'TC-LOGIN-02', user: 'locked_out_user', pass: 'secret_sauce', msg: 'locked out' },
{ id: 'TC-LOGIN-03', user: 'standard_user', pass: 'sai_mat_khau', msg: 'do not match' },
{ id: 'TC-LOGIN-04', user: '', pass: '', msg: 'Username is required' },
];
test.describe('Đăng nhập', () => {
test.beforeEach(async ({ loginPage }) => { await loginPage.goto(); });
test('TC-LOGIN-01 đăng nhập thành công', async ({ loginPage, inventoryPage }) => {
await loginPage.login('standard_user', 'secret_sauce');
await inventoryPage.expectLoaded();
});
for (const c of invalidCases) {
test(`${c.id} báo lỗi: ${c.msg}`, async ({ loginPage }) => {
await loginPage.login(c.user, c.pass);
await loginPage.expectError(c.msg);
});
}
});
Đặt mã TC-LOGIN-xx trong tên test để đối chiếu với file test case manual (Excel, TestRail...). Khi test fail, report cho biết ngay case nào trong tài liệu bị ảnh hưởng.
7. Chạy, xem report, debug
npx playwright test # chạy tất cả
npx playwright test login # lọc theo tên file
npx playwright test -g "TC-LOGIN-03" # lọc theo tên test
npx playwright test --headed # xem trình duyệt chạy
npx playwright test --ui # UI mode: chạy từng test, xem từng bước
npx playwright show-report # mở HTML report
Khi test fail, thứ tự debug mình hay làm: đọc thông báo lỗi trong terminal → mở HTML report xem screenshot → mở trace để xem DOM tại thời điểm fail. --ui mode rất hợp khi đang viết test mới.
8. Chạy trên GitHub Actions
Repo có sẵn file mẫu docs/playwright.yml. Chép vào .github/workflows/playwright.yml trong repo của bạn:
name: Playwright
on: [push, pull_request, workflow_dispatch]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install
- run: npx playwright install --with-deps chromium
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with: { name: playwright-report, path: playwright-report, retention-days: 7 }
if: always() để report vẫn được upload khi test fail, đúng lúc bạn cần nó nhất.
9. Những lỗi người mới hay gặp
- Dùng
waitForTimeout(3000): làm test chậm và vẫn flaky. Hãy chờ điều kiện cụ thể bằngexpect(...).toBeVisible(). - Locator quá dài kiểu
div > div:nth-child(3) > span: đổi sanggetByRole/getByTestId, và đề nghị dev thêmdata-testidnếu cần. - Test phụ thuộc nhau (test B cần test A chạy trước): mỗi test phải tự chuẩn bị dữ liệu, vì Playwright chạy song song.
- Quên
await: TypeScript + ESLint rule@typescript-eslint/no-floating-promisessẽ bắt giúp bạn. - Hard-code URL, tài khoản: đưa vào
baseURLvà biến môi trường ngay từ đầu.
10. Bước tiếp theo
Khi đã quen với phần trên, bạn có thể tự luyện thêm:
- Viết
InventoryPage.addToCart()và test giỏ hàng. - Thêm fixture
loggedInPagedùngstorageStateđể không phải đăng nhập lại ở mỗi test. - Thử test API bằng fixture
requestcó sẵn của Playwright. - Thêm project
firefox,webkittrong config để chạy đa trình duyệt.
Repo lite để bạn fork và luyện: https://github.com/qakit-vn/playwright-starter-vn-lite. Có chỗ nào khó hiểu hoặc chạy lỗi, cứ mở Issue hoặc comment bên dưới.
Công khai: mình (Siêng Studio) là người làm QA Kit tiếng Việt, bản trả phí có thêm test giỏ hàng, test API, checklist 110 mục và file Excel test case. Repo trong bài là miễn phí (MIT) và đủ để học các khái niệm ở trên. Bài gốc: Playwright TypeScript cho người mới.
All rights reserved