Wiki

← Tất cả bài wiki

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 list trê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ọi test.step — gọn stack trace khi step nằm trong helper/POM.
  • Return value: giá trị return trong 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: true khi 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.step mộ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.step hay describe mini — 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}.