Wiki

← Tất cả bài wiki

Non-retrying Assertions (expect(value) kiểu Jest — assert giá trị đã lấy ra)

automationplaywright

Non-retrying assertion là expect kiểu Jest truyền thống: expect(value).toBe(...) kiểm tra một giá trị JavaScript đã có sẵn (số, chuỗi, object, boolean) đúng một lần, không poll, không chờ. Dùng khi assert dữ liệu đã kéo ra ngoài DOM — kết quả API, giá trị count, object parse được — còn mọi kiểm tra trạng thái UI thì phải dùng web-first locator assertion.

Khái niệm & Bản chất

  • Một nhát, không chờ: expect(count).toBe(5) so sánh count tại đúng thời điểm dòng lệnh chạy — không có retry. Giá trị sau đổi thì assertion này vẫn giữ kết quả đã đánh giá.
  • Cùng 'họ' Jest matchers: toàn bộ core matchers Jest dùng được trong @playwright/test — toBe, toEqual, toContain, toBeGreaterThan, toMatch, toBeNull, toBeTruthy, toHaveProperty, toThrow...
  • Khác biệt cốt lõi với web-first: web-first assert trạng thái tương lai của DOM ('sẽ hiển thị'), non-retrying assert giá trị hiện tại trong tay ('count lúc nãy là 5'). Chọn sai loại = flaky hoặc test pass ảo.
  • Vai trò trong test Playwright: kiểm chứng dữ liệu logic — response API, số liệu tính toán, nội dung file download, giá trị JS trên window, số lượng element đã count() xong.

Cách hoạt động & API chính

  • So sánh nguyên thủy: toBe (===, so reference cho object), toEqual/toStrictEqual (deep equality cho object/array), toBeTruthy/toBeFalsy, toBeNull/toBeDefined/toBeUndefined/toBeNaN.
  • So sánh số: toBeGreaterThan, toBeGreaterThanOrEqual, toBeLessThan, toBeCloseTo(n, digits) — số float bắt buộc toBeCloseTo, đừng toBe(0.1 + 0.2).
  • Chuỗi & collection: toContain(substring|item) (string hoặc array), toMatch(regex|string), toHaveLength(n), toContainEqual(obj) (deep-equal item trong array).
  • Object: toHaveProperty('a.b', value) (nested path), toMatchObject(partial) (subset match — rất hay cho API response chỉ quan tâm vài field).
  • Negation: .not cho mọi matcher: expect(list).not.toContain('admin'). Và toThrow cho function phải throw: expect(() => JSON.parse(bad)).toThrow().
  • Custom message: expect(value, 'mô tả ngữ cảnh').toBe(x) — tham số thứ hai là message hiển thị khi fail, nên dùng cho assert logic khó đọc.

Ví dụ & Cú pháp thực tế

  • ```ts import { test, expect } from '@playwright/test'; test('assert giá trị đã kéo ra', async ({ page }) => { await page.goto('/users'); // count() resolve ra số → non-retrying assertion const rowCount = await page.getByRole('row').count(); expect(rowCount, 'bảng phải có ít nhất 10 dòng data').toBeGreaterThan(10); // textContent đã lấy ra là string thường const title = await page.getByRole('heading').first().textContent(); expect(title).toContain('Danh sách'); expect(title).toMatch(/\d{4}/); // chứa năm });
  • ```ts test('assert API response & object', async ({ request }) => { const res = await request.get('/api/orders/42'); expect(res.ok()).toBeTruthy(); expect(res.status()).toBe(200); const body = await res.json(); // Chỉ quan tâm vài field — toMatchObject expect(body).toMatchObject({ id: 42, status: 'paid', customer: { email: 'qa@hoctest.com' }, }); // Deep equality toàn bộ expect(body.items).toEqual([ expect.objectContaining({ sku: 'PW-101', qty: 2 }), ]); });
  • ```ts test('assert giá trị trên window / computed', async ({ page }) => { await page.goto('/cart'); const total = await page.evaluate( () => (window as any).__cart?.total ?? 0 ); expect(total).toBeCloseTo(199.99, 2); // float: luôn dùng toBeCloseTo // Assert function throw expect(() => JSON.parse('not-json')).toThrow(SyntaxError); });

Best practices & Khi nào dùng

  • Quy tắc chọn: kiểm tra thứ còn có thể thay đổi (DOM, URL, title) → web-first. Kiểm tra thứ đã chốt (response đã parse, số đã count, string đã lấy) → non-retrying.
  • Dùng cho API test thuần: request fixture + expect kiểu Jest là combo chuẩn kiểm thử API trong Playwright — status, body schema, header đều là giá trị đã resolve.
  • toMatchObject cho API response: chỉ khai báo field quan tâm, API thêm field mới không làm vỡ test — tránh toEqual toàn bộ response trừ khi snapshot contract.
  • Kèm message cho assert logic: expect(discount, 'giảm giá 10% cho đơn >500k').toBe(50_000) — khi fail đọc report hiểu ngay ngữ cảnh nghiệp vụ.
  • Assert số lượng sau khi UI ổn định: await expect(rows).toHaveCount(n) trước, rồi expect(await rows.count()).toBe(n) thừa — một cái là đủ; dùng non-retrying khi cần con số để tính tiếp (vd count > 0 để loop).

Gotchas, So sánh & Lưu ý

  • Anti-pattern số 1: expect(await locator.textContent()).toBe('Hello') — vừa mất auto-retry (textContent lấy snapshot tức thì) vừa match exact phá whitespace. Viết lại await expect(locator).toHaveText('Hello').
  • Anti-pattern 2: expect(await locator.isVisible()).toBeTruthy() — isVisible() check ngay, element render chậm 200ms là fail. Viết lại await expect(locator).toBeVisible().
  • toBe vs toEqual: toBe là === — hai object nội dung giống hệt nhau vẫn toBe fail vì khác reference. So nội dung object/array phải toEqual.
  • Số float: 0.1 + 0.2 === 0.3 là false — dùng toBeCloseTo(0.3) thay toBe.
  • Non-retrying trong vòng poll tự viết: nếu bạn tự loop check API, đừng assert cứng trong loop (fail là chết luôn lần đầu) — để toPass bọc ngoài hoặc check điều kiện return, xem bài expect.configure & toPass.
  • Không await được — và cũng không cần: expect(value).toBe() là đồng bộ; thêm await không hại nhưng thừa. Ngược lại web-first BẮT BUỘC await.