Soft Assertions (expect.soft — gom nhiều lỗi một lượt chạy)
automationplaywright
expect.soft là cơ chế soft assertion: khi một soft assertion fail, test KHÔNG dừng lại mà chạy tiếp, gom tất cả lỗi lại và chỉ fail ở cuối test kèm danh sách đầy đủ. Cực kỳ hữu ích khi verify một màn hình có nhiều checkpoint độc lập (form nhiều field, dashboard nhiều widget) — một lượt chạy thấy hết lỗi thay vì sửa từng lỗi một.
Khái niệm & Bản chất
- Hard assertion (mặc định):
expect(...).toBe...fail → throw ngay → test dừng tại dòng đó. Các check phía sau không chạy → bạn chỉ biết 1 lỗi mỗi lần chạy. - Soft assertion:
expect.soft(...).toBe...fail → Playwright ghi nhận lỗi vào danh sách nội bộ nhưng không throw, test chạy tiếp. Khi test kết thúc, nếu có ≥1 soft failure thì test bị đánh fail, report hiện đầy đủ tất cả lỗi. - Vẫn là web-first:
expect.soft(locator)giữ nguyên cơ chế auto-retry — nó chờ điều kiện như bình thường, chỉ khác ở hành vi khi fail (không abort test). - Triết lý: phù hợp kiểm tra 'bề mặt' — nhiều tiêu chí độc lập trên cùng một trạng thái đã dựng. Không phù hợp cho điều kiện mà bước tiếp theo phụ thuộc vào (soft fail rồi vẫn click sẽ sinh lỗi cascade).
Cách hoạt động & API chính
- API:
await expect.soft(actual).<matcher>(expected)— y hệtexpect, thêm.softở giữa. Áp dụng được cho cả locator assertion lẫn non-retrying (expect.soft(value).toBe(...)). - Lỗi được gom cuối test: Playwright liệt kê từng soft failure kèm message, locator, diff — trong report/trace đọc thấy hết, không mất lỗi đầu.
- Tương tác với
test.step: soft fail trong step A vẫn cho step B chạy; kết quả test cuối cùng là failed nhưng log giữ nguyên tiến trình — nhìn report biết chính xác những checkpoint nào đỏ. - Kết hợp
expect.configurecó thể tạo instance expect mặc định soft:const softExpect = expect.configure({ soft: true })— xem bài expect.configure & toPass. - Không có API 'assert all now' — không thể ép test fail sớm ở giữa chừng sau khi gom soft errors; điểm kết thúc duy nhất là hết test (hoặc throw thủ công).
Ví dụ & Cú pháp thực tế
- ```ts import { test, expect } from '@playwright/test'; test('kiểm tra toàn bộ form profile một lượt', async ({ page }) => { await page.goto('/profile'); // Tất cả check độc lập — dùng soft để thấy hết lỗi một lần await expect.soft(page.getByLabel('Họ tên')).toHaveValue('Nguyễn Văn A'); await expect.soft(page.getByLabel('Email')).toHaveValue('a@hoctest.com'); await expect.soft(page.getByLabel('SĐT')).toHaveValue('0901234567'); await expect.soft(page.getByText('Thành viên VIP')).toBeVisible(); await expect.soft(page.getByRole('button', { name: 'Nâng cấp' })).toBeEnabled(); // Nếu 3/5 check trên sai, report liệt kê đủ 3 lỗi — không phải chạy 3 lần });
- ```ts test('soft + hard kết hợp đúng chỗ', async ({ page }) => { await page.goto('/cart'); // Điều kiện BẮT BUỘC để đi tiếp — hard assertion await expect(page.getByRole('button', { name: 'Thanh toán' })).toBeEnabled(); await page.getByRole('button', { name: 'Thanh toán' }).click(); // Sau đó verify nhiều thứ trên trang checkout — soft await expect.soft(page).toHaveURL(/checkout/); await expect.soft(page.getByTestId('order-total')).toHaveText(/\d+/); await expect.soft(page.getByTestId('shipping-fee')).toBeVisible(); });
- ```ts // Soft trong loop — kiểm tra từng dòng bảng, gom lỗi toàn bảng test('mọi dòng đều có status hợp lệ', async ({ page }) => { await page.goto('/orders'); const rows = page.getByRole('row'); const count = await rows.count(); for (let i = 1; i < count; i++) { await expect.soft( rows.nth(i).getByRole('cell', { name: /pending|paid|shipped/ }) ).toBeVisible(); } });
Best practices & Khi nào dùng
- Dùng cho checkpoint độc lập, cùng một trạng thái: form nhiều field, dashboard nhiều widget, bảng nhiều cột — nơi một lỗi không làm các check còn lại vô nghĩa.
- Bắt buộc hard assertion cho precondition: đã login chưa, đã vào đúng trang chưa, nút có click được không — fail sớm tiết kiệm thời gian và tránh loạt lỗi cascade vô nghĩa.
- Quy tắc 'setup hard, verify soft': dựng state (điều hướng, điền form, click) thì hard; kiểm chứng kết quả nhiều điểm thì soft.
- Đặt message rõ ràng — vì lỗi gom lại, message mô tả checkpoint giúp đọc report nhanh:
expect.soft(el, 'avatar phải hiển thị').toBeVisible(). - Kết hợp
test.stepđể nhóm: gom soft assertions vào step 'Verify header', 'Verify footer' — report phân nhóm đẹp, dễ khoanh vùng lỗi.
Gotchas, So sánh & Lưu ý
- Soft fail không dừng test → lỗi cascade: nếu step sau phụ thuộc điều kiện vừa soft-fail (ví dụ soft-check menu hiển thị rồi click vào nó), bạn sẽ nhận thêm lỗi phụ rác — thiết kế test sao cho phần sau soft-check cũng độc lập.
- Lỗi chỉ 'bùng' ở cuối test: soft failure không interrupt tức thì, nên không dùng cho điều kiện an toàn (ví dụ 'phải đăng xuất trước khi tiếp tục').
- Một số assertion trong
toPass/hook: soft errors gom trongtoPassretry block vẫn được xử lý như fail của lần thử đó — block được retry, không phải fail ngay. - Soft ≠ ignore: test vẫn FAIL cuối cùng nếu có bất kỳ soft error nào — nhiều người lầm tưởng soft là 'warning'. Muốn warning thật phải tự
try/catchvà log. - So với TestNG
SoftAssert/ pytestpytest-check: cùng ý tưởng, nhưng Playwright tự động gom và fail cuối test mà không cần gọiassertAll()— ít quên hơn. - Report dài: một test 20 soft assertions fail 15 cái → error message rất dài. Hợp lý thì tách thành vài test nhỏ hoặc nhóm theo step.
