·

Playwright MCP với OpenCode

Cài đặt Playwright MCP trong OpenCode để 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.

OpenCode là một trong những AI coding agent mã nguồn mở đang được nhiều team dùng thay thế cho các CLI thương mại, và điểm mạnh của nó là hỗ trợ MCP (Model Context Protocol) khá đầy đủ ngay từ bản core. Khi gắn Playwright MCP vào OpenCode, bạn có một agent có thể tự mở trình duyệt thật, thao tác như người dùng, và sinh ra file test E2E (end-to-end) hoàn chỉnh chỉ từ một đoạn mô tả bằng tiếng Việt hoặc tiếng Anh. Bài này đi từ bước cài đặt, cách viết prompt hiệu quả, một ví dụ thực chiến với luồng checkout, đến các hạn chế thực tế bạn cần biết trước khi đưa vào workflow chính thức của team.

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

OpenCode quản lý MCP server qua file cấu hình opencode.json (hoặc .opencode/config.json tùy phiên bản bạn dùng) đặt ở root project. Cách nhanh nhất là khai báo trực tiếp:

{
  "mcp": {
    "playwright": {
      "type": "local",
      "command": ["npx", "@playwright/mcp@latest"],
      "enabled": true
    }
  }
}

Nếu bạn dùng OpenCode qua CLI thuần, có thể thêm server bằng lệnh trực tiếp thay vì sửa file thủ công:

opencode mcp add playwright -- npx @playwright/mcp@latest

Sau khi lưu cấu hình, khởi động lại session OpenCode và gõ lệnh kiểm tra danh sách server:

opencode mcp list

Kết quả nên hiện playwright ở trạng thái connected. Nếu thấy error hoặc disconnected, nguyên nhân phổ biến nhất là máy chưa có Node.js/npx trong PATH, hoặc chưa cài trình duyệt cho Playwright — chạy trước:

npx playwright install chromium

Một điểm cần lưu ý với OpenCode: khác với một số agent CLI khác tự động cấp quyền gọi tool ngay, OpenCode theo mặc định sẽ hỏi xác nhận (permission prompt) mỗi khi agent gọi một MCP tool mới lần đầu trong session. Nếu bạn muốn tăng tốc độ làm việc khi đã tin tưởng Playwright MCP, có thể thêm rule cho phép trước trong file cấu hình permission của project:

{
  "permission": {
    "mcp": {
      "playwright": "allow"
    }
  }
}

Mẹo: Đừng set permission thành "allow" toàn cục ngay từ đầu khi mới làm quen Playwright MCP — hãy để ở "ask" trong vài buổi đầu để bạn quan sát chính xác agent đang gọi tool nào (browser_navigate, browser_click, browser_type...), từ đó hiểu được cách agent "nhìn" trang web trước khi tự động hoá hoàn toàn.

Sinh Và Chạy Test E2E Từ Prompt Ngôn Ngữ Tự Nhiên Trong OpenCode

Workflow chuẩn khi làm việc với Playwright MCP trong OpenCode gồm ba giai đoạn: mô tả luồng cần kiểm thử, quan sát agent thực thi thử qua trình duyệt thật, rồi yêu cầu đóng gói lại thành file test chính thức theo chuẩn @playwright/test.

Bắt đầu bằng một prompt mô tả luồng, viết cụ thể URL và dữ liệu test thay vì mô tả chung chung:

Ứng dụng đang chạy ở http://localhost:3000. Mở trang /login, đăng nhập bằng
email "test@test.com" và password "12345678". Sau khi vào dashboard, kiểm tra
menu "Notifications" hiển thị đúng số lượng thông báo chưa đọc.

OpenCode sẽ điều phối các tool call theo trình tự: browser_navigate để mở URL, browser_snapshot để đọc accessibility tree (cây truy cập — cấu trúc trang theo role và label của từng phần tử, không phải ảnh chụp màn hình dạng pixel), sau đó browser_typebrowser_click để điền form và bấm nút đăng nhập, cuối cùng browser_snapshot lại một lần nữa để xác nhận trạng thái trang sau khi đăng nhập. Toàn bộ log này hiện trực tiếp trong terminal hoặc TUI của OpenCode, kèm tóm tắt bằng lời sau mỗi bước.

Sau khi xác nhận agent thực hiện đúng luồng, yêu cầu sinh file test chính thức:

Luồng trên chạy đúng. Hãy viết lại thành file tests/e2e/login-notifications.spec.ts
theo chuẩn @playwright/test. Dùng getByRole/getByLabel, không dùng CSS selector.
Thêm expect() rõ ràng cho số lượng thông báo hiển thị, và tách phần login vào
một helper function riêng để tái sử dụng cho các test khác sau này.

Kết quả tham khảo:

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

async function login(page: import('@playwright/test').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/);
}

test('unread notifications count is displayed correctly after login', async ({ page }) => {
  await login(page);
  await expect(page.getByRole('button', { name: /Notifications/ })).toContainText('3');
});

Chạy lại độc lập để kiểm tra file không phụ thuộc vào trạng thái session mà agent vừa thao tác:

npx playwright test tests/e2e/login-notifications.spec.ts

Mẹo: Khi viết prompt cho OpenCode, luôn nêu rõ số liệu kỳ vọng cụ thể (ví dụ "3 thông báo chưa đọc") thay vì mô tả mơ hồ như "hiển thị đúng thông báo" — agent cần một giá trị cụ thể để sinh ra expect() có ý nghĩa, nếu không nó sẽ chỉ kiểm tra phần tử "có tồn tại" mà không thật sự assert đúng nghiệp vụ.

Ví Dụ Thực Chiến: Sinh Test E2E Cho Luồng Checkout Trong OpenCode

Luồng checkout là ví dụ tốt để thấy sức mạnh thật của Playwright MCP, vì nó có nhiều bước, nhiều trạng thái trung gian (giỏ hàng, địa chỉ giao hàng, phương thức thanh toán) mà nếu viết test thủ công sẽ tốn khá nhiều thời gian dò selector.

Bước 1 — mô tả luồng theo từng đoạn nhỏ, không dồn hết vào một prompt dài:

Mở http://localhost:3000/products, thêm sản phẩm "Wireless Mouse" vào giỏ hàng,
sau đó vào trang /cart để xác nhận sản phẩm đã có trong giỏ với số lượng là 1.

Sau khi agent xác nhận bước này chạy đúng qua snapshot, tiếp tục:

Từ trang /cart, bấm "Checkout". Điền địa chỉ giao hàng: tên "Nguyen Van A",
địa chỉ "123 Le Loi, District 1, HCMC", số điện thoại "0901234567". Chọn phương
thức thanh toán "Cash on delivery" và xác nhận đặt hàng. Kiểm tra trang kết quả
hiển thị mã đơn hàng và thông báo "Order placed successfully".

Khi cả hai đoạn luồng đã được xác nhận đúng qua thực thi trực tiếp, gộp lại thành một prompt sinh test hoàn chỉnh:

Viết lại toàn bộ luồng thêm sản phẩm và checkout trên thành file
tests/e2e/checkout-cod.spec.ts. Tách thành các bước rõ ràng bằng test.step(),
mỗi bước có assertion tương ứng. Đặt beforeEach để login trước (dùng lại helper
login() đã có trong tests/e2e/helpers.ts). Đảm bảo test không phụ thuộc dữ liệu
sản phẩm có sẵn — nếu "Wireless Mouse" không tồn tại, test phải fail rõ ràng
với thông báo dễ hiểu, không phải timeout mơ hồ.

Kết quả tham khảo, đã tách rõ từng bước với test.step() để khi CI fail, log chỉ ra chính xác bước nào lỗi:

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

test.beforeEach(async ({ page }) => {
  await login(page);
});

test('user can complete checkout with cash on delivery', async ({ page }) => {
  await test.step('add product to cart', async () => {
    await page.goto('/products');
    await page.getByRole('listitem', { name: /Wireless Mouse/ })
      .getByRole('button', { name: 'Add to cart' })
      .click();
    await page.goto('/cart');
    await expect(page.getByText('Wireless Mouse')).toBeVisible();
    await expect(page.getByLabel('Quantity')).toHaveValue('1');
  });

  await test.step('fill shipping and payment info', async () => {
    await page.getByRole('button', { name: 'Checkout' }).click();
    await page.getByLabel('Full name').fill('Nguyen Van A');
    await page.getByLabel('Address').fill('123 Le Loi, District 1, HCMC');
    await page.getByLabel('Phone number').fill('0901234567');
    await page.getByLabel('Cash on delivery').check();
    await page.getByRole('button', { name: 'Place order' }).click();
  });

  await test.step('verify order confirmation', async () => {
    await expect(page.getByText('Order placed successfully')).toBeVisible();
    await expect(page.getByTestId('order-id')).toBeVisible();
  });
});

Mẹo: Với luồng nhiều bước như checkout, luôn yêu cầu agent dùng test.step() để bọc từng giai đoạn logic — khi test fail trong CI, báo cáo Playwright sẽ chỉ rõ bước nào fail thay vì bạn phải đọc lại toàn bộ đoạn code dài để đoán.

Hạn Chế Cần Biết Của Playwright MCP Khi Dùng Trong OpenCode

Playwright MCP kết hợp với OpenCode hoạt động tốt cho phần lớn use case, nhưng có vài hạn chế thực tế bạn nên biết trước để tránh mất thời gian debug sai hướng:

  • Xử lý pop-up và tab mới chưa ổn định: khi trang web mở tab mới (ví dụ nút "Open in new tab" hoặc OAuth login qua popup Google), agent qua MCP đôi khi không tự động chuyển context sang tab mới, cần bạn nhắc rõ trong prompt "làm việc trên tab vừa mở" hoặc tách flow OAuth ra kiểm thử riêng bằng cách mock.
  • Snapshot lớn có thể vượt giới hạn context: với trang có DOM rất phức tạp (dashboard nhiều widget, bảng dữ liệu hàng nghìn dòng), browser_snapshot trả về accessibility tree quá dài có thể chiếm phần lớn context window (cửa sổ ngữ cảnh) của model, khiến agent phản hồi chậm hoặc bị cắt bớt thông tin. Nên yêu cầu agent thao tác trên các khu vực nhỏ, cụ thể của trang thay vì để nó tự snapshot toàn trang liên tục.
  • Chưa hỗ trợ tốt file upload qua giao diện thực: các thao tác kéo-thả file hoặc chọn file từ dialog hệ điều hành thường không mô phỏng được qua accessibility tree; giải pháp thực tế là dùng page.setInputFiles() viết tay trong bước review sau khi agent sinh code, thay vì trông đợi agent tự làm đúng ngay từ đầu.
  • Độ ổn định phụ thuộc phiên bản OpenCode: vì OpenCode cập nhật khá nhanh, cách xử lý permission và timeout cho MCP tool call có thể thay đổi giữa các bản; luôn khóa version cụ thể trong package.json hoặc ghi chú version OpenCode đã test khi review test suite để tránh tình trạng "chạy được trên máy tôi" khi đồng nghiệp dùng bản khác.

Mẹo: Ghi chú lại phiên bản OpenCode và @playwright/mcp đã dùng để sinh test suite ngay trong README của thư mục tests/e2e/ — khi có lỗi lạ xảy ra sau một thời gian, đây là thông tin đầu tiên giúp bạn xác định có phải do nâng cấp version gây ra hay không.

Mẹo Tổng Hợp Khi Dùng Playwright MCP Với OpenCode

Vài kinh nghiệm gom lại từ quá trình triển khai thực tế: luôn để permission ở mức "ask" trong giai đoạn làm quen để hiểu rõ cách agent gọi tool; chia nhỏ prompt theo từng đoạn luồng ngắn để agent thực thi chính xác hơn thay vì mô tả toàn bộ hành trình dài trong một lượt; và luôn chạy lại file test độc lập bằng npx playwright test sau khi agent sinh ra, đừng tin tưởng hoàn toàn kết quả "trông có vẻ đúng" từ log thực thi trong lúc agent đang khám phá luồng.

Mẹo: Nếu team bạn dùng OpenCode cho nhiều project khác nhau, hãy tạo sẵn một file opencode.json mẫu có cấu hình Playwright MCP chuẩn (bao gồm permission, timeout hợp lý) rồi copy vào project mới — điều này giúp mọi thành viên có cùng trải nghiệm ngay từ lần đầu, tránh mỗi người tự cấu hình một kiểu dẫn đến hành vi agent không đồng nhất.