Câu hỏi
Auto-wait trong Playwright hoạt động thế nào? Có phải dùng Playwright thì không bao giờ cần viết câu lệnh chờ nữa không?
Câu này rất dễ trả lời một câu rồi tự chôn mình:
Playwright tự wait element nên không cần wait.
Nghe đúng đúng, nhưng interviewer hỏi tiếp “nó wait cái gì?” là bắt đầu tắc. Element có mặt trong DOM chưa có nghĩa là click được. Nó có thể đang chạy animation, bị overlay che, đang disabled, hoặc locator của bạn khớp luôn hai cái nút.
Auto-wait thực sự chờ cái gì?
Khi gọi một action qua Locator, Playwright kiểm tra các điều kiện liên quan đến action đó. Chưa đạt thì nó chờ và thử lại cho đến khi được hoặc hết timeout.
Ví dụ:
await page.getByRole('button', { name: 'Thanh toán' }).click();
Trước khi click, Playwright cần chắc rằng:
- Locator resolve ra đúng một element.
- Element đang visible.
- Element đã stable, nôm na là không còn chạy animation làm nó đổi vị trí.
- Element thực sự nhận được pointer event, không bị một lớp khác chắn lên.
- Element đang enabled.
Nếu nút Thanh toán đang disabled trong lúc trang tính lại tổng tiền, test không cần sleep hai giây rồi mới click. Playwright sẽ chờ đến lúc nút enabled, miễn là nó kịp xảy ra trước timeout.
Nhưng nếu một cái loading overlay bị kẹt luôn trên nút thì click() phải fail. Đấy là failure cần nhìn thấy, không phải chỗ để nhét force: true vào cho test xanh.
Mỗi action chờ một kiểu
click(), fill() và hover() không dùng nguyên một checklist.
| Action | Một số điều kiện chính |
|---|---|
click() | Visible, stable, receives events, enabled |
fill() | Visible, enabled, editable |
hover() | Visible, stable, receives events |
fill() cần input editable nhưng không cần đợi nó stable như click. hover() không quan tâm element enabled vì hover lên một nút disabled vẫn là hành vi hợp lệ.
Vậy auto-wait không phải Playwright “đợi trang load xong”. Nó đợi cho action cụ thể sắp chạy trở nên hợp lệ.
Click được rồi chưa chắc flow đã xong
Đây là đoạn nhiều người lẫn nhất.
await page.getByRole('button', { name: 'Lưu' }).click();
Dòng này kết thúc nghĩa là Playwright đã thực hiện click. Nó không có khả năng đọc requirement để biết sau click thì profile phải lưu xong, toast phải hiện và dữ liệu phải xuống database.
Giả sử click xong frontend gọi API mất ba giây. Test viết thế này:
await page.getByRole('button', { name: 'Lưu' }).click();
expect(await page.getByRole('status').textContent()).toBe('Đã lưu thành công');
textContent() lấy text tại đúng thời điểm nó được gọi. API chưa xong thì mình vừa chụp một giá trị quá sớm rồi assert ngay, rất dễ flaky.
Dùng web-first assertion:
await page.getByRole('button', { name: 'Lưu' }).click();
await expect(page.getByRole('status')).toHaveText('Đã lưu thành công');
toHaveText() sẽ lấy lại element và kiểm tra nhiều lần cho đến khi text đúng hoặc hết assertion timeout.
Nôm na:
- Auto-wait trước action hỏi: bây giờ bấm được chưa?
- Web-first assertion sau action hỏi: kết quả mình cần đã xuất hiện chưa?
Hai cái có cơ chế retry gần giống nhau, nhưng giải quyết hai việc khác nhau.
Khi nào vẫn phải viết wait?
Khi test cần đồng bộ với một tín hiệu mà actionability và assertion trên UI không đại diện được.
Ví dụ mình cần kiểm tra request lưu profile đã trả về thành công:
const responsePromise = page.waitForResponse((response) =>
response.url().endsWith('/api/profile') &&
response.request().method() === 'PUT'
);
await page.getByRole('button', { name: 'Lưu' }).click();
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
await expect(page.getByRole('status')).toHaveText('Đã lưu thành công');
Phải tạo responsePromise trước khi click. Nếu click trước rồi mới bắt đầu chờ, response chạy nhanh có thể về mất rồi, test ngồi đợi một thứ đã xảy ra.
Tương tự, download, popup, request, WebSocket event hay một thay đổi URL đều có API chờ theo đúng tín hiệu. Chờ có chủ đích vẫn cần; thứ nên bỏ là chờ bằng cách đoán thời gian.
Ba cách chữa flaky test rất dễ phản tác dụng
Thêm waitForTimeout()
Test fail vì API chậm nên thêm:
await page.waitForTimeout(3000);
Máy nhanh thì phí ba giây. Máy chậm hơn ba giây thì vẫn fail. Sau đó tăng lên năm giây, CI vẫn fail thì tăng lên mười. Cuối cùng suite chạy như rùa mà flaky vẫn hoàn flaky.
waitForTimeout() hữu dụng khi debug, ví dụ muốn trang đứng lại để nhìn. Đưa nó vào test production để đồng bộ là đang lấy đồng hồ thay requirement.
Dùng force: true
await button.click({ force: true });
force bỏ qua một số actionability check. Nếu mục tiêu test là phát event cưỡng chế thì dùng được. Còn nếu overlay đang che nút mà user ngoài đời không bấm nổi, force click chỉ giúp automation làm được điều user không làm được rồi báo pass.
Chờ networkidle cho mọi trang
Một trang có polling, analytics hoặc WebSocket có thể chẳng bao giờ thật sự “idle”. Chiều ngược lại, hết network request rồi nhưng React vẫn chưa render trạng thái cuối.
Nếu requirement nói user nhìn thấy dòng “Đã lưu”, chờ dòng đó. Nếu cần response PUT /profile, chờ response đó. networkidle không phải tín hiệu thần thánh đại diện cho mọi flow.
Nếu locator khớp hai element thì sao?
Với action cần một target như click(), Playwright dùng strictness và không tự chọn đại một element cho mình. Ví dụ trang có hai nút Lưu, một ở form chính và một trong modal đang ẩn sai cách, locator quá rộng sẽ fail.
Sửa locator để nó nói đúng thứ mình muốn thao tác:
const profileForm = page.getByRole('form', { name: 'Thông tin cá nhân' });
await profileForm.getByRole('button', { name: 'Lưu' }).click();
Đừng chữa bằng .first() nếu bản thân mình cũng không biết tại sao có hai element. .first() chỉ biến “locator đang mơ hồ” thành “chọn cái đầu tiên và cầu nguyện”.
Trả lời phỏng vấn thế nào?
Một bản ngắn, nói được trong khoảng nửa phút:
Playwright auto-wait dựa trên actionability của Locator. Ví dụ trước khi click, nó chờ locator khớp đúng một element và element đó visible, stable, enabled, đồng thời không bị thứ khác chặn pointer event. Mỗi action có bộ check khác nhau. Auto-wait chỉ giúp action chạy đúng lúc, nó không biết business result mình đang chờ. Sau action tôi dùng web-first assertion hoặc chờ response, URL hay event cụ thể. Tôi tránh
waitForTimeout()vì đó là sleep cố định và dễ làm test flaky.
Nếu interviewer hỏi sâu thêm thì mới nói về force, assertion timeout, waitForResponse() và strict locator. Không cần trút cả documentation ra trong câu đầu.