Wiki

← Tất cả bài wiki

data-testid Attribute (Hợp đồng ổn định giữa Dev & QA)

automationplaywright

data-testid là attribute HTML chuyên dụng cho automation — không ảnh hưởng UI, không bị CSS/JS dùng, và chỉ đổi khi team chủ động đổi. Playwright đọc nó qua getByTestId() và cho phép đổi tên attribute bằng testIdAttribute trong config. Đây là phương án dự phòng chuẩn khi getBy* ngữ nghĩa không đủ, và là "hợp đồng" rõ ràng giữa dev và QA.

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

  • Attribute riêng cho test: <button data-testid="save-btn"> — attribute này tồn tại DUY NHẤT để automation bắt element. Không style, không logic app nào phụ thuộc vào nó.
  • Hợp đồng 2 chiều: dev thêm testid khi code, QA dùng testid khi test. Refactor UI đổi class/structure vẫn giữ testid → test không vỡ; đổi testid = chủ động đổi contract.
  • Tên attribute tuỳ biến: chuẩn de-facto là data-testid, nhưng nhiều team dùng data-test, data-qa, data-cy (Cypress) — Playwright cho đổi qua config, không cần sửa markup.
  • Vị trí trong thang ưu tiên: nằm SAU getByRole/getByLabel vì không kiểm chứng accessibility; nằm TRƯỚC CSS/XPath thuần vì ổn định hơn hẳn cấu trúc markup.
  • Giải quyết vấn đề "không có gì để bắt": icon button, item trong list trùng lặp, chart/canvas, vùng UI generate động — đều không có text/role đủ phân biệt, testid là câu trả lời.

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

  • getByTestId(id): page.getByTestId('save-btn') — đọc theo testIdAttribute hiện tại (mặc định data-testid).
  • Cấu hình testIdAttribute: set trong playwright.config.ts → use: { testIdAttribute: 'data-qa' } — sau đó getByTestId tự đọc data-qa. Làm 1 lần cho cả project.
  • Tương đương CSS: page.getByTestId('x') === page.locator('[data-testid="x"]') === page.locator('data-testid=x') — chọn getByTestId vì ngắn và tôn trọng config.
  • Testid trong component lồng nhau: chain bình thường — page.getByTestId('order-row').filter({ hasText: 'A123' }).getByTestId('btn-cancel').
  • Với framework component: React/Vue/Angular đều render attribute bình thường; với component library (MUI...) nhớ đặt testid lên DOM element thật chứ không phải wrapper bị swallow.

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

  • Cấu hình attribute khác mặc định: ``ts // playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ use: { // Team dùng data-qa thay vì data-testid testIdAttribute: 'data-qa', }, }); // Sau đó getByTestId('x') sẽ tìm [data-qa="x"] ``
  • Dùng trong test: ``ts import { test, expect } from '@playwright/test'; test('xoá item trong giỏ', async ({ page }) => { await page.goto('/cart'); // icon thùng rác không có text — testid là chuẩn const row = page.getByTestId('cart-item').filter({ hasText: 'Áo thun' }); await row.getByTestId('btn-remove').click(); await expect(page.getByTestId('cart-item')).toHaveCount(0); }); ``
  • Khi nào testid cứu test: UI dashboard toàn chart <canvas> → getByTestId('revenue-chart'); nút icon ×/🗑/⋮ → getByTestId('btn-close-modal'); pagination → getByTestId('page-3').
  • Convention đặt tên gợi ý: kebab-case, có namespace theo màn hình — checkout-btn-submit, cart-item-{id}, user-menu-dropdown. Tránh btn1, test123 vô nghĩa.

Best practices & Khi nào dùng

  • Đặt rule trong team: ai thêm UI component interactive mới thì kèm testid — review PR như review đổi API. Thiếu testid = PR chưa xong.
  • Kết hợp, không thay thế: giữ getByRole cho phần có ngữ nghĩa, testid cho phần "mù" — một test tốt thường trộn cả hai.
  • Thống nhất 1 tên attribute cho toàn hệ thống: cấu hình testIdAttribute 1 lần ở config chung; đừng để module A dùng data-test, module B dùng data-qa.
  • Generate id động thì giữ pattern ổn định: data-testid="todo-item-42" tốt vì filter được bằng getByTestId('todo-item-42') hoặc CSS [data-testid^="todo-item-"] khi cần tất cả.
  • Component library 3rd party: với MUI/AntD, prop để pass attribute xuống DOM thường là data-testid truyền thẳng hoặc inputProps/slotProps — verify bằng inspect, đừng đoán.

Gotchas, So sánh & Lưu ý

  • Testid KHÔNG phải free pass: lạm dụng testid trên mọi element = từ bỏ kiểm tra accessibility; test chỉ chứng minh "bấm được", không chứng minh "dùng được".
  • Production có lộ testid không? — attribute này không hại gì (không chứa secret), nhiều công ty giữ luôn trong prod build để test trên staging/prod giống nhau. Nếu muốn strip, dùng babel plugin — nhưng phải chấp nhận test chỉ chạy đúng trên build có testid.
  • Quên sync config = lỗi khó hiểu: nếu code dùng data-qa mà config vẫn mặc định data-testid, getByTestId sẽ "không thấy" element dù inspect rõ ràng — kiểm tra testIdAttribute đầu tiên khi locator bất ngờ fail.
  • So sánh với id (#id): id vẫn dùng cho CSS anchor/JS app → có thể bị dev đổi vì lý do khác; testid chỉ phục vụ test nên contract sạch hơn.
  • So sánh Cypress: cy.get('[data-cy=x]') phải viết CSS selector tay; Playwright có getByTestId + testIdAttribute config nên đổi tên attribute không phải sửa từng chỗ.
  • Testid thay đổi theo dữ liệu: data-testid={dynamic} do dev build từ state (vd: tên user) sẽ phá test khi data seed đổi — giữ phần định danh ổn định (cart-item) + filter theo nội dung.