Wiki

← Tất cả bài wiki

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). expect gố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 theo intervals cho tới khi nó return không throw (mọi assertion bên trong pass) hoặc timeout hết hạn.
  • Giải quyết bài toán web-first không cover: web-first assertion poll DOM, còn toPass poll 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: toPass là '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ọi softExpect(...) 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.configure trả về instance mới — gán vào biến rồi dùng biến đó; gọi expect.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 + sleep tự 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.configure hợ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 expect hay locator.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.configure không mutate expect gốc: nhớ dùng đúng biến instance đã tạo; nhiều người configure xong vẫn gọi expect(...) gốc rồi tưởng config 'không ăn'.
  • So sánh waitForFunction/waitForResponse: hai API đó chờ sự kiện trang; toPass chờ điều kiện code định nghĩa. Chờ network event thì waitForResponse tự nhiên hơn, chờ kết quả nghiệp vụ thì toPass.