E2E test rất dễ vỡ khi UI đổi. Vài chiến lược giữ cho test bền:
Page Object Model — gom selector và hành động vào một chỗ, UI đổi thì chỉ sửa một file:
class LoginPage {
async login(email: string, pw: string) {
await this.page.getByLabel('Email').fill(email)
await this.page.getByLabel('Password').fill(pw)
await this.page.getByRole('button', { name: 'Sign in' }).click()
}
}Locator theo ngữ nghĩa — page.getByRole('button', { name: 'Submit' }) và page.getByLabel('Email') bền hơn page.locator('.btn-submit') vì không dính vào tên class CSS. Với phần tử quan trọng thì đặt data-testid ngay trong code production, coi như contract rõ ràng giữa dev và test.
Bỏ wait cứng — thay page.waitForTimeout(2000) bằng expect(locator).toBeVisible() hoặc page.waitForSelector('[data-testid=result]') để Playwright tự retry.
Fixture nền — test.extend({ authenticatedPage }) đăng nhập sẵn trước mỗi test, khỏi lặp lại logic login.
Trace khi fail — đặt trace: 'on-first-retry' trong playwright.config.ts, chỉ ghi trace lúc hỏng; mở trace.zip bằng Playwright Trace Viewer là debug được lỗi CI mà không cần dựng lại ở máy.
Lưu ý: nhồi quá nhiều assertion vào một test thì lúc fail không biết đang ở bước nào — nhóm assertion theo hành trình người dùng.