test.step (chia nhỏ test thành các bước có tên)
automationplaywright
test.step bọc một nhóm action/assertion thành một bước có tên, hiển thị dạng cây trên HTML report và trace viewer. Nó giải quyết hai vấn đề: report đọc được như test case manual cho cả người không code, và khi test fail bạn biết ngay nó chết ở bước nào thay vì lần trong cả khối code dài.
Khái niệm & Bản chất
test.step(title, body)bọc một đoạn code thành step có tên — mỗi step có trạng thái pass/fail và thời gian chạy riêng trên report.- Step hiển thị dạng cây lồng nhau trong HTML report, trace viewer, và reporter
listtrên console — giống mục lục của test case. - Step không phải assertion — step chứa action + expect bên trong; step fail (exception/expect fail) thì test fail tại step đó.
- Step có thể lồng nhau nhiều cấp: step cha 'Checkout' chứa step con 'Nhập địa chỉ', 'Chọn thanh toán'...
- Step không tạo isolation — nó chỉ là cách đánh dấu/log, state page và biến chạy tiếp bình thường giữa các step.
Cách hoạt động & API chính
- Signature:
await test.step('tên step', async () => { ... })— bắt buộc await, quên await thì step chạy 'mồ côi' và report sai thứ tự. - Option
{ box: true }: ẩn location nội bộ của step, lỗi báo về đúng dòng gọitest.step— gọn stack trace khi step nằm trong helper/POM. - Return value: giá trị
returntrong body được trả ra ngoài — dùng để truyền data giữa các step (vd orderId, mã OTP). test.step.skip('tên', async () => {...})— đánh dấu step chưa implement, tương tự todo.- Step được đo timeout cùng budget của test — step không có timeout riêng; test timeout thì step đang chạy bị kéo fail.
Ví dụ & Cú pháp thực tế
- Chia flow e2e thành các step đọc được như test case:
``
ts import { test, expect } from '@playwright/test'; test('đặt hàng end-to-end', async ({ page }) => { await test.step('Đăng nhập', async () => { await page.goto('/login'); await page.getByLabel('Email').fill('qa@hoctest.com'); await page.getByLabel('Mật khẩu').fill('secret123'); await page.getByRole('button', { name: 'Đăng nhập' }).click(); await expect(page).toHaveURL(/dashboard/); }); const orderId = await test.step('Tạo đơn hàng', async () => { await page.getByRole('link', { name: 'Sản phẩm' }).first().click(); await page.getByRole('button', { name: 'Mua ngay' }).click(); await page.getByRole('button', { name: 'Đặt hàng' }).click(); // lấy mã đơn trả về cho step sau return await page.locator('.order-id').innerText(); }); await test.step(Kiểm tra đơn ${orderId} trong lịch sử, async () => { await page.goto('/orders'); await expect(page.getByText(orderId)).toBeVisible(); }); });`` - Step lồng nhau cho sub-flow:
``
ts await test.step('Thanh toán', async () => { await test.step('Nhập thông tin thẻ', async () => { // ... }); await test.step('Xác nhận OTP', async () => { // ... }); });`` - Dùng
box: truekhi step nằm trong helper dùng chung — lỗi trỏ về nơi gọi thay vì nội bộ helper: ``ts await test.step('Đăng nhập bằng tài khoản seed', async () => { await loginAs(page, 'qa@hoctest.com'); }, { box: true });``
Best practices & Khi nào dùng
- Đặt tên step theo ngôn ngữ business giống test case manual ('Đăng nhập', 'Thêm sản phẩm vào giỏ', 'Xác nhận thanh toán') — manual QA/PM đọc report hiểu luôn không cần xem code.
- 1 step = 1 ý nghĩa nghiệp vụ — đừng bọc từng
click()thành step riêng (report rối, trace nặng, chậm hơn). - Dùng step để tách phase arrange → act → assert trong test dài, hoặc các flow nhiều màn hình.
- Test ngắn 3–5 dòng không cần step — step sinh ra để giải nén test dài/phức tạp, lạm dụng chỉ thêm noise.
- Khi các bước phụ thuộc hoàn toàn lẫn nhau (bước sau cần kết quả bước trước), 1 test nhiều step thường tốt hơn nhiều test serial.
Gotchas, So sánh & Lưu ý
- Quên
await test.step(...)→ step hiển thị sai/nested lung tung, log mất đồng bộ — bật lint rule hoặc quen gõawait test.stepmột mạch. - Step fail ở giữa → các step sau không chạy (test dừng), report đánh dấu step fail đỏ — đúng mong đợi nhưng đừng hi vọng step sau tự chạy tiếp.
- Step trong trace viewer tăng khả năng điều hướng — mỗi step có snapshot DOM/network riêng, debug rất nhanh; đây là lý do nên dùng step trong test e2e dài.
- So sánh: giống
allure.stephaydescribemini — nhưng step là runtime construct, không tạo test case riêng như parameterized test. - Đừng nhồi logic nghiệp vụ phức tạp vào step ở test level — logic dùng lại nhiều nơi thuộc về POM/helper; step ở test chỉ nên là lớp 'mục lục'.
- Step title nên động khi cần (template literal với data) để report tự giải thích:
Kiểm tra đơn ${orderId}.
