Sau khi đã biết cách kết nối Playwright MCP với từng công cụ agent riêng lẻ (Claude Code, OpenCode, Gemini CLI, Cursor), câu hỏi thực tế tiếp theo là: làm sao đưa việc này thành một workflow lặp lại được, áp dụng cho mọi feature mới trong team, không chỉ là demo một lần rồi thôi? Bài này trình bày một quy trình đầy đủ từ lúc nhận feature story đến khi có một bộ test regression (kiểm thử hồi quy — đảm bảo tính năng cũ không bị lỗi khi có thay đổi mới) chạy ổn định trong CI, dựa trên kinh nghiệm áp dụng thực tế cho nhiều feature khác nhau.
Tổng Quan Workflow: Từ Feature Story Đến Bộ Test Regression Tự Động
Một workflow AI-driven E2E testing hiệu quả với Playwright MCP thường có 4 giai đoạn rõ ràng, lặp lại cho mỗi feature mới:
- Mô tả và khám phá (explore): Mô tả feature bằng ngôn ngữ tự nhiên cho agent, để agent tự mở trình duyệt và thực thi thử luồng thật, xác nhận hành vi đúng như mong đợi.
- Sinh test scaffold: Yêu cầu agent đóng gói luồng vừa khám phá thành file test theo chuẩn
@playwright/test, có cấu trúc rõ ràng và assertion đầy đủ. - Chạy và diễn giải lỗi: Chạy test thật, để AI đọc log lỗi (nếu có) và phân tích nguyên nhân — phân biệt giữa lỗi do test viết sai và lỗi do bug thật trong ứng dụng.
- Mở rộng và đưa vào regression suite: Sau khi test ổn định, mở rộng thêm các case biên (edge case), rồi tích hợp chính thức vào bộ test regression chạy trên CI mỗi khi có pull request.
Điểm khác biệt quan trọng giữa workflow này và cách viết test E2E truyền thống: thay vì engineer tự dò từng CSS selector và tự đoán assertion cần thiết, agent thực thi trực tiếp trên trình duyệt thật để "nhìn thấy" hành vi UI, giúp giảm đáng kể sai lệch giữa test và hành vi thực tế của ứng dụng — miễn là bạn kiểm soát tốt chất lượng ở từng giai đoạn, không giao trắng hoàn toàn cho AI.
Một sơ đồ thực tế cho workflow này trong một sprint:
[Feature story] --> [Explore qua Playwright MCP] --> [Sinh test scaffold]
--> [Chạy test lần đầu] --> [Fail? --> AI phân tích + fix]
--> [Test pass ổn định] --> [Thêm edge case] --> [Merge vào CI regression suite]
Mẹo: Đừng nhảy thẳng vào bước sinh test khi mới nhận feature story — luôn dành thời gian cho giai đoạn "khám phá" trước, để agent tự thực thi và bạn xác nhận đúng hành vi thực tế của UI. Bỏ qua bước này thường dẫn đến test được sinh ra dựa trên giả định sai về UI, và bạn sẽ phải sửa lại từ đầu ở bước chạy thử.
Bước 1: Mô Tả Feature Và Sinh Khung Test Playwright
Giả sử feature mới là "cho phép user thêm sản phẩm vào wishlist (danh sách yêu thích) từ trang chi tiết sản phẩm". Bắt đầu bằng prompt mô tả cụ thể, có URL và dữ liệu thật:
Ứng dụng chạy tại http://localhost:3000. Mở trang /products/wireless-mouse,
bấm icon "Add to wishlist" cạnh tên sản phẩm. Xác nhận icon đổi trạng thái
thành "đã thêm" (filled heart) và vào trang /wishlist để kiểm tra sản phẩm
"Wireless Mouse" xuất hiện trong danh sách.
Agent điều phối Playwright MCP theo trình tự: browser_navigate mở trang sản phẩm, browser_snapshot đọc accessibility tree (cây truy cập theo role/label) để tìm đúng icon wishlist, browser_click bấm vào, browser_snapshot lại để xác nhận trạng thái đổi, rồi browser_navigate sang /wishlist và browser_snapshot lần cuối để xác nhận sản phẩm xuất hiện.
Sau khi xác nhận luồng đúng, yêu cầu sinh khung test (scaffold) — chú ý dùng từ "khung" hoặc "scaffold" để agent hiểu đây là bản đầu, sẽ còn được mở rộng thêm ở bước sau:
Viết khung test tests/e2e/wishlist.spec.ts theo chuẩn @playwright/test dựa
trên luồng vừa thực hiện. Dùng getByRole/getByLabel, thêm beforeEach để login
(dùng lại helper login() trong tests/e2e/helpers.ts). Đây là bản khung đầu
tiên, chưa cần phủ hết edge case, tập trung vào happy path trước.
Kết quả tham khảo:
import { test, expect } from '@playwright/test';
import { login } from './helpers';
test.beforeEach(async ({ page }) => {
await login(page);
});
test.describe('Wishlist', () => {
test('user can add a product to wishlist from product detail page', async ({ page }) => {
await page.goto('/products/wireless-mouse');
await page.getByRole('button', { name: 'Add to wishlist' }).click();
await expect(page.getByRole('button', { name: 'Remove from wishlist' })).toBeVisible();
await page.goto('/wishlist');
await expect(page.getByText('Wireless Mouse')).toBeVisible();
});
});
Chạy thử ngay để có baseline (mốc chuẩn) trước khi mở rộng:
npx playwright test tests/e2e/wishlist.spec.ts
Mẹo: Luôn yêu cầu rõ ràng "đây là bản khung, chỉ cần happy path" ở bước này — nếu không, agent thường cố gắng nhồi luôn nhiều edge case vào cùng một lần sinh code, khiến bạn khó review và khó xác định chính xác phần nào đã được verify qua thực thi thật, phần nào agent chỉ đoán theo suy luận.
Bước 2: Chạy Test Và Dùng AI Để Diễn Giải Lỗi
Giai đoạn này là nơi giá trị thực tế của AI thể hiện rõ nhất — không chỉ sinh code mà còn đọc và hiểu log lỗi như một QA engineer thật. Chạy test và yêu cầu agent phân tích nếu có fail:
npx playwright test tests/e2e/wishlist.spec.ts --reporter=list
Giả sử test fail với lỗi "Timed out waiting for element with role button and name 'Remove from wishlist'". Prompt cho agent:
Test vừa fail với lỗi trên. Đọc lại screenshot và trace được sinh ra trong
thư mục test-results/, cho tôi biết: (1) đây là lỗi do test viết sai selector,
hay (2) đây là bug thật trong ứng dụng (icon không đổi trạng thái sau khi
click), hay (3) đây là vấn đề timing (element cần thêm thời gian để cập nhật).
Đây là bước quan trọng cần huấn luyện kỹ năng cho cả agent và chính bạn: agent sẽ mở file trace, đối chiếu screenshot tại thời điểm fail với snapshot DOM, và đưa ra chẩn đoán. Ví dụ agent có thể trả lời: "Element 'Add to wishlist' vẫn hiển thị nguyên trạng sau khi click 2 giây, điều này cho thấy khả năng cao là bug thật — sự kiện click có thể không gọi đúng API cập nhật wishlist, không phải do test sai selector."
Nếu đây đúng là bug thật, workflow đúng là dừng lại, báo cho dev backend/frontend liên quan (đính kèm trace và screenshot AI vừa phân tích) — đừng để agent tự "sửa" test để pass bằng cách nới lỏng assertion, vì điều đó che giấu bug thật. Nếu là lỗi timing, yêu cầu agent sửa:
Đây là vấn đề timing, không phải bug thật (đã xác nhận thủ công trên UI).
Sửa lại assertion dùng expect với auto-retry mặc định của Playwright, không
dùng waitForTimeout, và thêm expect(...).toBeVisible() ngay sau bước click
để chờ đúng điều kiện thay vì chờ cố định theo thời gian.
Mẹo: Khi AI báo test fail, luôn hỏi ngược lại "đây là bug thật hay lỗi test" trước khi yêu cầu sửa code test — phản xạ mặc định của nhiều agent là tìm cách làm test pass bằng mọi giá (nới assertion, thêm timeout dài hơn), điều này rất nguy hiểm nếu fail đó thực chất đang cảnh báo một bug thật trong sản phẩm.
Bước 3: Sửa Lỗi, Mở Rộng Coverage Và Đưa Vào Bộ Test Regression
Sau khi test happy path đã pass ổn định (chạy lại vài lần liên tiếp không flaky), bước tiếp theo là mở rộng coverage để phủ các edge case quan trọng trước khi chính thức đưa vào bộ regression chạy trên CI.
Yêu cầu agent liệt kê các edge case nên có, dựa trên luồng đã implement:
Dựa trên feature wishlist đã test ở happy path, liệt kê các edge case quan
trọng nên được kiểm thử thêm (không cần viết code ngay, chỉ liệt kê).
Agent thường đề xuất được các case như: xoá sản phẩm khỏi wishlist, thêm sản phẩm đã có sẵn trong wishlist (không bị duplicate), thêm sản phẩm khi chưa đăng nhập (redirect tới login), và wishlist trống hiển thị empty state đúng. Sau khi bạn review và chọn ra các case thực sự quan trọng, yêu cầu viết tiếp:
Viết thêm các test case sau vào tests/e2e/wishlist.spec.ts: (1) xoá sản phẩm
khỏi wishlist, (2) thêm sản phẩm đã có trong wishlist không tạo bản ghi trùng,
(3) user chưa đăng nhập bấm "Add to wishlist" bị redirect về /login. Với mỗi
case, hãy tự thực thi qua browser trước để xác nhận hành vi thật, sau đó mới
viết assertion.
Kết quả tham khảo (rút gọn, thêm vào cùng file):
test('removing a product from wishlist works correctly', async ({ page }) => {
await page.goto('/wishlist');
await page.getByRole('listitem', { name: /Wireless Mouse/ })
.getByRole('button', { name: 'Remove from wishlist' })
.click();
await expect(page.getByText('Wireless Mouse')).not.toBeVisible();
});
test('adding an already-wishlisted product does not create a duplicate', async ({ page }) => {
await page.goto('/products/wireless-mouse');
await page.getByRole('button', { name: 'Add to wishlist' }).click();
await page.goto('/wishlist');
await expect(page.getByText('Wireless Mouse')).toHaveCount(1);
});
test('unauthenticated user is redirected to login when adding to wishlist', async ({ page, context }) => {
await context.clearCookies();
await page.goto('/products/wireless-mouse');
await page.getByRole('button', { name: 'Add to wishlist' }).click();
await expect(page).toHaveURL(/.*login/);
});
Sau khi toàn bộ file chạy pass ổn định qua vài lần chạy liên tiếp (npx playwright test tests/e2e/wishlist.spec.ts --repeat-each=3 để kiểm tra độ ổn định), bước cuối là chính thức đưa vào CI. Yêu cầu agent kiểm tra file workflow CI hiện có và tích hợp đúng cách:
Đọc file .github/workflows/ci.yml hiện tại. Thêm bước chạy
tests/e2e/wishlist.spec.ts vào đúng job test E2E đã có (không tạo job mới),
đảm bảo chạy sau bước build và start server.
Mẹo: Chỉ đưa test vào CI sau khi đã chạy ổn định với
--repeat-eachít nhất 3-5 lần liên tiếp trên máy local hoặc môi trường staging — một test chỉ chạy pass "một lần duy nhất" rất dễ trở thành nguồn flaky làm chậm cả pipeline CI và khiến team dần mất niềm tin vào bộ test regression.
Mẹo Tổng Hợp Để Duy Trì Workflow Này Lâu Dài
Mẹo: Xây dựng workflow này thành một checklist chuẩn cho cả team (ví dụ lưu trong
docs/e2e-testing-workflow.md): khám phá → sinh khung test happy path → diễn giải lỗi nếu có → mở rộng edge case → xác nhận ổn định → tích hợp CI. Khi mọi người trong team làm theo đúng 5 bước này một cách nhất quán, bộ test regression sinh ra bởi AI sẽ đáng tin cậy hơn nhiều so với việc mỗi người tự "chat với AI" theo cách riêng và không ai kiểm soát được chất lượng đầu ra cuối cùng.