·

Playwright MCP với Gemini CLI

Cài đặt Playwright MCP trong Gemini CLI để AI agent có thể điều khiển trình duyệt thật để kiểm thử và kiểm tra trang web ngay trong trình soạn thảo.

Gemini CLI của Google là một lựa chọn đáng chú ý cho team đã quen với ecosystem Google Cloud hoặc muốn tận dụng context window (cửa sổ ngữ cảnh) rất lớn của model Gemini khi làm việc với dự án có nhiều file. Khi kết hợp với Playwright MCP, Gemini CLI có thể tự mở trình duyệt, đọc cấu trúc trang, và sinh ra test E2E (end-to-end) từ user story viết bằng ngôn ngữ tự nhiên. Bài này hướng dẫn cách cài đặt, cách viết prompt hiệu quả dựa trên user story, một ví dụ thực chiến với luồng đăng nhập, và so sánh chất lượng test sinh ra giữa Gemini CLI với Claude Code để bạn có cơ sở lựa chọn công cụ phù hợp cho từng loại task.

Cài Đặt Và Kết Nối Playwright MCP Với Gemini CLI

Gemini CLI đọc cấu hình MCP server từ file ~/.gemini/settings.json (phạm vi toàn cục) hoặc .gemini/settings.json trong project (phạm vi project, nên dùng cách này để chia sẻ với team qua git). Thêm block sau vào file:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest"],
      "trust": true
    }
  }
}

Trường trust: true cho phép Gemini CLI gọi tool của server này mà không hỏi xác nhận mỗi lần — hữu ích khi bạn đã quen thuộc với Playwright MCP, nhưng nên để false (hoặc bỏ hẳn trường này) trong giai đoạn đầu để quan sát agent gọi tool nào.

Sau khi lưu file, khởi động Gemini CLI và dùng lệnh slash để kiểm tra trạng thái server:

/mcp

Gemini CLI sẽ liệt kê server playwright cùng danh sách tool khả dụng (browser_navigate, browser_click, browser_snapshot, browser_type, v.v.). Nếu server hiện trạng thái lỗi, kiểm tra lại Node.js đã có trong PATH và chạy:

npx playwright install chromium

để đảm bảo trình duyệt headless đã sẵn sàng.

Một điểm khác biệt của Gemini CLI so với các agent CLI khác: do context window lớn (thường 1 triệu token với các bản Gemini 1.5/2.x Pro), Gemini CLI có xu hướng giữ lại toàn bộ lịch sử snapshot trang trong một session dài mà không tóm lược lại — điều này có lợi khi bạn cần agent "nhớ" cấu trúc trang đã khám phá trước đó, nhưng cũng khiến chi phí token tăng nhanh nếu session kéo dài qua nhiều luồng không liên quan. Nên tách mỗi luồng nghiệp vụ lớn thành một session riêng.

Mẹo: Đặt trust: false khi mới cấu hình Playwright MCP với Gemini CLI để bạn được hỏi xác nhận (approve) mỗi tool call đầu tiên — đây là cách nhanh nhất để học được chính xác cách agent "nhìn thấy" trang, từ đó viết prompt chính xác hơn về sau.

Sinh Test Playwright Từ User Story Và Requirement Trong Gemini CLI

Điểm mạnh thực tế của Gemini CLI là khả năng đọc và tổng hợp một tài liệu requirement dài (ví dụ file PRD hoặc user story) rồi tự suy ra các bước cần kiểm thử, nhờ context window lớn. Cách làm hiệu quả là đưa cả file requirement vào cùng prompt sinh test.

Ví dụ, giả sử bạn có file docs/user-story-reset-password.md mô tả luồng quên mật khẩu. Prompt gợi ý:

Đọc file docs/user-story-reset-password.md để hiểu yêu cầu nghiệp vụ. Sau đó,
mở http://localhost:3000/forgot-password và thực hiện đúng luồng được mô tả:
nhập email "test@test.com", submit form, và kiểm tra thông báo "Check your
email for reset instructions" xuất hiện. Ghi lại từng bước bạn thực hiện.

Gemini CLI sẽ đọc file markdown trước (dùng tool đọc file có sẵn), sau đó điều phối các tool của Playwright MCP: browser_navigatebrowser_snapshot để lấy accessibility tree (cây truy cập theo role/label của phần tử) → browser_type để điền email → browser_click để submit → browser_snapshot lại để xác nhận thông báo hiển thị đúng.

Sau khi xác nhận luồng chạy đúng thực tế, yêu cầu sinh file test chính thức, kèm yêu cầu bám sát đúng các tiêu chí trong user story:

Dựa trên luồng vừa thực hiện và các tiêu chí chấp nhận (acceptance criteria)
trong docs/user-story-reset-password.md, viết file
tests/e2e/forgot-password.spec.ts theo chuẩn @playwright/test. Đảm bảo test
phủ đủ các trường hợp: email hợp lệ, email không tồn tại trong hệ thống, và
email để trống. Dùng getByRole/getByLabel, có assertion rõ ràng cho mỗi case.

Kết quả tham khảo:

import { test, expect } from '@playwright/test';

test.describe('Forgot password flow', () => {
  test('shows confirmation message for a valid email', async ({ page }) => {
    await page.goto('/forgot-password');
    await page.getByLabel('Email').fill('test@test.com');
    await page.getByRole('button', { name: 'Send reset link' }).click();
    await expect(page.getByText('Check your email for reset instructions')).toBeVisible();
  });

  test('shows generic message for a non-existing email (no user enumeration)', async ({ page }) => {
    await page.goto('/forgot-password');
    await page.getByLabel('Email').fill('does-not-exist@test.com');
    await page.getByRole('button', { name: 'Send reset link' }).click();
    await expect(page.getByText('Check your email for reset instructions')).toBeVisible();
  });

  test('shows validation error when email is empty', async ({ page }) => {
    await page.goto('/forgot-password');
    await page.getByRole('button', { name: 'Send reset link' }).click();
    await expect(page.getByText('Email is required')).toBeVisible();
  });
});

Chú ý case thứ hai trong ví dụ trên — đây là điểm mà việc cho agent đọc trực tiếp user story tạo ra giá trị thật: nếu tài liệu ghi rõ yêu cầu bảo mật "không được lộ thông tin email nào tồn tại trong hệ thống", agent sẽ tự sinh test case kiểm tra đúng hành vi bảo mật này, thay vì chỉ test happy path.

Mẹo: Khi có sẵn tài liệu requirement hoặc user story dạng markdown, luôn trỏ agent đọc file đó trước khi mô tả luồng bằng lời — Gemini CLI với context window lớn xử lý rất tốt việc đối chiếu ngược lại acceptance criteria để tự sinh thêm các edge case mà bạn có thể quên nhắc tới trong prompt.

Ví Dụ Thực Chiến: Sinh Test E2E Cho Luồng Đăng Nhập Trong Gemini CLI

Luồng đăng nhập tưởng đơn giản nhưng thường có nhiều nhánh cần test: đăng nhập thành công, sai mật khẩu, tài khoản bị khoá, và giữ trạng thái đăng nhập (remember me). Đây là ví dụ tốt để thấy Gemini CLI xử lý nhiều nhánh rẽ trong một session.

Bước 1, khám phá từng nhánh bằng prompt riêng biệt:

Mở http://localhost:3000/login. Nhập email "test@test.com" và password sai
"wrongpass123", bấm "Log in". Ghi lại thông báo lỗi hiển thị chính xác.

Sau khi agent báo lại thông báo lỗi thực tế (ví dụ "Invalid email or password"), tiếp tục nhánh thành công:

Giờ nhập đúng password "12345678" và bấm "Log in" lại. Xác nhận đã chuyển đến
/dashboard và tên người dùng hiển thị đúng ở góc trên bên phải.

Sau khi cả hai nhánh đã được xác nhận, yêu cầu gộp thành file test hoàn chỉnh:

Viết file tests/e2e/login.spec.ts bao gồm cả hai trường hợp đã thực hiện:
sai mật khẩu (kiểm tra đúng nội dung thông báo lỗi) và đăng nhập thành công
(kiểm tra chuyển trang và tên hiển thị). Nhóm hai test trong cùng
test.describe('Login'), không dùng chung state giữa hai test.

Kết quả tham khảo:

import { test, expect } from '@playwright/test';

test.describe('Login', () => {
  test('shows error message on invalid credentials', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill('test@test.com');
    await page.getByLabel('Password').fill('wrongpass123');
    await page.getByRole('button', { name: 'Log in' }).click();
    await expect(page.getByText('Invalid email or password')).toBeVisible();
  });

  test('logs in successfully and shows the correct user name', async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Email').fill('test@test.com');
    await page.getByLabel('Password').fill('12345678');
    await page.getByRole('button', { name: 'Log in' }).click();
    await expect(page).toHaveURL(/.*dashboard/);
    await expect(page.getByTestId('current-user-name')).toBeVisible();
  });
});

Mẹo: Với luồng có nhiều nhánh rẽ (đúng/sai/khoá tài khoản), hãy để agent khám phá và ghi lại thông báo lỗi/thành công thực tế trên UI trước — đừng tự đoán nội dung message rồi bắt agent viết theo, vì message thật trên UI thường khác với những gì bạn nhớ, dẫn đến test fail ngay khi mới viết xong.

So Sánh Chất Lượng Test Playwright MCP Giữa Gemini CLI Và Claude Code

Sau khi dùng cả hai công cụ trên cùng một codebase thực tế, có vài khác biệt đáng lưu ý khi chọn công cụ cho từng loại task:

  • Độ chính xác locator: Claude Code có xu hướng ưu tiên getByRolegetByLabel một cách nhất quán hơn ngay từ lần sinh đầu tiên, trong khi Gemini CLI đôi lúc lẫn getByText cho các phần tử tương tác được (như button) — nên luôn review và yêu cầu sửa lại thành getByRole nếu thấy agent dùng getByText cho nút bấm hoặc link.
  • Khả năng xử lý tài liệu dài: nhờ context window lớn, Gemini CLI vượt trội khi bạn cần agent đọc một tài liệu requirement dài (PRD, spec nhiều trang) rồi đối chiếu với hành vi UI thực tế — đây là use case Gemini CLI làm tốt hơn rõ rệt.
  • Tốc độ phản hồi trong vòng lặp ngắn: với các tác vụ tương tác nhanh, nhiều vòng gọi tool liên tiếp (như luồng checkout nhiều bước), Claude Code thường phản hồi nhanh hơn và ít bị "lặp lại hành động không cần thiết" hơn so với Gemini CLI trong một số phiên bản.
  • Chất lượng comment và tổ chức code: Claude Code có xu hướng sinh code test gọn hơn, ít comment dư thừa; Gemini CLI đôi khi thêm comment giải thích từng dòng khá dài dòng, cần dặn rõ trong prompt "không thêm comment giải thích hiển nhiên" nếu muốn code sinh ra gọn hơn.

Kết luận thực tế: không có công cụ nào "thắng tuyệt đối" — nếu team đã có tài liệu requirement chi tiết và muốn agent tự suy luận edge case từ đó, Gemini CLI là lựa chọn tốt; nếu ưu tiên tốc độ và độ chính xác locator ngay từ lần sinh đầu, Claude Code vẫn nhất quán hơn ở thời điểm hiện tại. Nhiều team thực tế dùng cả hai: Gemini CLI cho giai đoạn phân tích requirement và sinh khung test ban đầu, Claude Code cho vòng lặp sửa lỗi và tinh chỉnh nhanh.

Mẹo: Đừng chọn công cụ dựa trên cảm tính "nghe nói cái này tốt hơn" — hãy chạy thử cùng một user story trên cả hai công cụ với cùng prompt, so sánh trực tiếp file .spec.ts sinh ra, rồi quyết định dựa trên chất lượng thực tế cho đúng loại nghiệp vụ của bạn.

Lời Khuyên Khi Dùng Playwright MCP Với Gemini CLI

Mẹo: Khi làm việc với Gemini CLI và Playwright MCP trong dự án lớn, hãy đưa file .gemini/settings.json vào .gitignore nếu nó chứa API key riêng, và tách một file .gemini/settings.project.json không chứa secret để commit chung — tránh vô tình lộ key khi share cấu hình MCP cho cả team.