expect.configure & toPass (tuỳ biến retry — poll logic nghiệp vụ)
automationplaywright
expect.configure tạo một bản expect riêng với default mới (timeout, soft, message) để không phải lặp option ở từng assertion. Còn expect(...).toPass biến cả một khối code thành 'retryable assertion': Playwright chạy lại block theo interval cho tới khi không throw hoặc hết timeout — công cụ chuẩn để poll logic ngoài DOM như chờ đơn hàng chuyển trạng thái qua API.
Khái niệm & Bản chất
expect.configure(options)trả về một expect instance MỚI với default đã tuỳ biến — các option:timeout(ms, default retry timeout),soft(biến mọi assertion của instance này thành soft),message(prefix message).expectgốc không đổi.expect(block).toPass(options)— 'expected to pass': bạn truyền một function chứa bất kỳ code nào; Playwright gọi function đó lặp lại theointervalscho tới khi nó return không throw (mọi assertion bên trong pass) hoặctimeouthết hạn.- Giải quyết bài toán web-first không cover: web-first assertion poll DOM, còn
toPasspoll bất cứ thứ gì code được — gọi API kiểm tra trạng thái đơn, đọc DB/queue, kiểm tra file download xuất hiện... - Bản chất:
toPasslà 'vòng lặp retry có kiểm soát' thay thếwhile(true) { try {...} catch { sleep } }thủ công — có timeout rõ ràng, interval cấu hình được, error report gọn.
Cách hoạt động & API chính
- Cấu hình instance:
const softExpect = expect.configure({ soft: true })— mọisoftExpect(...)sau đó tự soft. Tương tựconst slowExpect = expect.configure({ timeout: 30_000 }). - Option của toPass:
{ timeout: 30_000 }(tổng thời gian thử, mặc định 0 = không giới hạn? — thực tế mặc định timeout của test) vàintervals: [500, 1_000, 2_000, 5_000](chuỗi delay giữa các lần thử; lặp lại giá trị cuối khi hết mảng). - Bên trong block toPass: viết assertion thường (không cần web-first vì toPass tự retry cả block) hoặc logic tuỳ ý — throw gì cũng tính là 'chưa pass'.
- Timeout per-assertion vẫn ăn: mọi web-first matcher nhận
{ timeout }—expect(locator).toBeVisible({ timeout: 15_000 })là cách đơn giản nhất khi chỉ cần chờ lâu hơn, không cần configure instance mới. - Lưu ý scope:
expect.configuretrả về instance mới — gán vào biến rồi dùng biến đó; gọiexpect.configure({...})mà không gán thì không có tác dụng gì.
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; // Instance expect mặc định soft cho toàn bộ verify phụ const softExpect = expect.configure({ soft: true }); test('dashboard — mọi check đều soft', async ({ page }) => { await page.goto('/dashboard'); await softExpect(page.getByTestId('kpi-revenue')).toBeVisible(); await softExpect(page.getByTestId('kpi-orders')).toBeVisible(); await softExpect(page.getByTestId('chart')).toBeVisible(); });
- ``
ts test('poll API chờ đơn hàng chuyển trạng thái', async ({ page, request }) => { await page.goto('/cart'); await page.getByRole('button', { name: 'Đặt hàng' }).click(); const orderId = await page.getByTestId('order-id').textContent(); // Backend xử lý async — poll API tới khi status = 'confirmed' await expect(async () => { const res = await request.get(/api/orders/${orderId}`); const body = await res.json(); expect(body.status).toBe('confirmed'); // throw → toPass retry }).toPass({ timeout: 30_000, intervals: [500, 1_000, 2_000, 5_000], // thử lại ngày càng thưa }); await expect(page.getByText('Đơn hàng đã xác nhận')).toBeVisible(); }); - ```ts // toPass cho việc 'DOM cuối cùng cũng ổn định' — dữ liệu cập nhật lại liên tục test('bảng realtime cuối cùng hiển thị đúng', async ({ page }) => { await page.goto('/live-orders'); await expect(async () => { // Re-query mỗi lần thử — dữ liệu realtime thay đổi const count = await page.getByRole('row').count(); expect(count).toBeGreaterThanOrEqual(5); await expect(page.getByText('Đã đồng bộ')).toBeVisible(); }).toPass({ intervals: [1_000, 2_000, 5_000], timeout: 20_000 }); });
Best practices & Khi nào dùng
- toPass cho polling ngoài DOM: backend job async (đơn hàng confirm, email gửi xong, report generate xong) — đây là use-case vàng, thay thế hoàn toàn vòng
while + sleeptự viết. - Intervals tăng dần: bắt đầu dày (500ms) để bắt trường hợp nhanh, giãn dần (5s) để không spam server — pattern exponential-ish backoff.
- Bên trong toPass chỉ nên có 'thứ đang chờ': đừng nhét hành động có side-effect (click, điền form) vào block — nó chạy NHIỀU lần, click lặp gây bug khó lường.
expect.configurehợp khi một file/suite có convention riêng: vd file smoke toàn soft assertion, hoặc màn hình load nặng cần timeout 30s — tạo instance một lần đầu file.- Kết hợp message:
expect.configure({ message: 'Checkout flow' })để prefix mọi lỗi — đọc report biết ngay lỗi thuộc nhóm nào.
Gotchas, So sánh & Lưu ý
- toPass KHÔNG phải web-first assertion: nó retry cả function, không phải poll một matcher — bên trong dùng non-retrying
expecthaylocator.count()đều được vì block tự chạy lại. - Side-effect trong block = tai hoạ:
page.click()trong toPass chạy lại mỗi lần thử → double submit, tạo đơn trùng... Chỉ để code đọc/verify, không để code ghi. - Timeout mặc định của toPass: nếu không truyền
timeout, nó có thể chạy tới khi hết test timeout — luôn set explicit để fail sớm và rõ nghĩa. - Assertion bên trong toPass nên 'rẻ': mỗi interval là một lần evaluate/request — poll API nặng (join nhiều bảng) cân nhắc interval thưa hơn.
expect.configurekhông mutateexpectgốc: nhớ dùng đúng biến instance đã tạo; nhiều người configure xong vẫn gọiexpect(...)gốc rồi tưởng config 'không ăn'.- So sánh
waitForFunction/waitForResponse: hai API đó chờ sự kiện trang;toPasschờ điều kiện code định nghĩa. Chờ network event thìwaitForResponsetự nhiên hơn, chờ kết quả nghiệp vụ thìtoPass.
