1. Component Testing là gì và vì sao 2026 nên dùng
Component Testing (CT) của Playwright cho phép bạn gắn (mount) một component React, Vue, Svelte hoặc Solid trực tiếp vào một trình duyệt thật rồi kiểm thử nó như thể đó là một trang web thu nhỏ. Khác với unit test chạy trên jsdom giả lập, CT dựng component trong Chromium thực, nên layout, CSS, animation, focus và sự kiện chuột đều đúng như production. Đây là điểm giao thoa quý giá giữa tốc độ của unit test và độ trung thực của E2E.
Trong hệ thống SaaS thực tế, một component như bảng giá, giỏ hàng hay biểu đồ dashboard chứa rất nhiều nhánh logic: trạng thái loading, empty, error, quyền theo gói dịch vụ. Viết E2E cho mọi tổ hợp đó vừa chậm vừa giòn, còn unit test lại không bắt được lỗi CSS đè lên nút bấm. CT lấp đúng khoảng trống ấy: bạn kiểm được hành vi tương tác thật trên một đơn vị nhỏ, chạy nhanh trong mili-giây và cô lập khỏi backend.
Một lý do khiến CT trở nên thiết yếu năm 2026 là các đội front-end ngày càng chia nhỏ giao diện thành hàng trăm component tái sử dụng, và mỗi component lại là điểm hồi quy tiềm tàng. Khi một thư viện thiết kế dùng chung được nâng cấp, chỉ CT mới cho bạn kiểm nhanh từng component trong trình duyệt thật để phát hiện thay đổi vô tình về khoảng cách, màu sắc hay hành vi focus. Nếu không có lớp này, đội thường phát hiện lỗi quá muộn ở E2E, nơi việc truy nguyên component nào gây ra khác biệt tốn rất nhiều công. CT đưa phản hồi về đúng đơn vị nhỏ nhất, nơi chi phí sửa thấp nhất.
2. Cài đặt và khởi tạo CT cho từng framework
Playwright cung cấp một trình khởi tạo riêng cho CT. Lệnh init sẽ tạo playwright-ct.config.ts, thư mục playwright/ chứa index.html và index.ts (nơi bạn nạp CSS toàn cục, provider, theme), rồi cài đúng gói experimental theo framework bạn chọn. Với React bạn dùng ct-react, Vue dùng ct-vue, Svelte dùng ct-svelte, Solid dùng ct-solid — mỗi gói bọc một adapter mount riêng nhưng chia sẻ chung runner Playwright.
# Khởi tạo Component Testing (chọn framework khi được hỏi)
npm init playwright@latest -- --ct
# hoặc cài trực tiếp gói theo framework
npm i -D @playwright/experimental-ct-react # React
npm i -D @playwright/experimental-ct-vue # Vue
npm i -D @playwright/experimental-ct-svelte # Svelte
npm i -D @playwright/experimental-ct-solid # Solid
# chạy CT (khác lệnh 'test' của E2E)
npx playwright test -c playwright-ct.config.ts// playwright-ct.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-react';
export default defineConfig({
testDir: './src',
testMatch: /.*\.ct\.(ts|tsx)$/, // tách file CT khỏi file E2E
snapshotDir: './__snapshots__',
use: {
trace: 'on-first-retry',
ctPort: 3100, // cổng Vite của CT
ctViteConfig: { resolve: { alias: { '@': '/src' } } },
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});// playwright/index.ts — chạy MỘT LẦN trước mọi mount
import '../src/styles/global.css';
import { beforeMount } from '@playwright/experimental-ct-react/hooks';
import { ThemeProvider } from '../src/theme';
// bọc mọi component bằng provider chung (theme, i18n, query client...)
beforeMount(async ({ App }) => {
return (
<ThemeProvider theme="light">
<App />
</ThemeProvider>
);
});3. mount(): trái tim của Component Testing
Hàm mount() nhận vào một component cùng props, sự kiện và children, đóng gói chúng bằng Vite, rồi render trong trình duyệt thật và trả về một locator gốc để bạn thao tác. Điều tinh tế cần nhớ: mọi giá trị bạn truyền vào props phải serialize được qua ranh giới Node ↔ browser, còn callback thì được Playwright bọc lại để bạn spy trong tiến trình test. Sau mount, bạn dùng đúng bộ locator getByRole, getByText và assertion expect quen thuộc.
// PriceTag.ct.tsx — React
import { test, expect } from '@playwright/experimental-ct-react';
import { PriceTag } from './PriceTag';
test('hiển thị giá đã định dạng theo locale', async ({ mount }) => {
const cmp = await mount(<PriceTag amount={1990000} currency="VND" locale="vi-VN" />);
await expect(cmp).toContainText('1.990.000');
await expect(cmp.getByRole('note')).toHaveText('₫');
});
test('bắn event khi bấm "Chọn gói"', async ({ mount }) => {
let picked = null;
const cmp = await mount(<PriceTag amount={99} onSelect={(id) => (picked = id)} planId="pro" />);
await cmp.getByRole('button', { name: 'Chọn gói' }).click();
expect(picked).toBe('pro'); // callback được bắc cầu về test process
});
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!