Trang trò chơi đã được viết xong và mở được trong trình duyệt: bàn cờ hiện lên, các quân cờ di chuyển, nút "Ván mới" cũng ở đó. Chỉ có điều agent chưa từng nhìn thấy trang này. Agent làm việc với nội dung tệp, còn bạn khởi chạy trình duyệt, tự xem rồi kể cho agent biết kết quả. Playwright lấp đúng khoảng trống ấy: agent mở chính trang đó, tìm phần tử cần thiết, nhấn vào và đọc xem điều gì đã thay đổi.

Ta sẽ lần lượt xem chu trình này qua ví dụ một trang cờ vua: mở trang, quan sát, chọn phần tử, thao tác rồi đọc trạng thái mới. Phần cài đặt và kết nối server không nhắc lại ở đây. Nếu bạn chưa kết nối Playwright, hướng dẫn nằm trong README của dự án.

Mở đúng trang cần xem

Dòng Connected chứng minh điều gì?
Chỉ một điều: Claude Code đã kết nối được với server Playwright và nhận danh sách công cụ. Chưa có trang nào được mở, thậm chí trình duyệt có thể còn chưa chạy. Connected cho biết kết nối đã hoạt động, chứ không chứng minh trò chơi của bạn đang chạy hay agent nhìn thấy nó.

Bạn có thể xem trạng thái hiện tại bằng lệnh /mcp: lệnh này liệt kê các server đã kết nối và khả năng của từng server. Công việc thực sự bắt đầu từ đây, và điều đầu tiên cần có là địa chỉ trang.

Trò chơi chạy cục bộ có thể nằm trong một tệp trên đĩa hoặc trên server cục bộ. Cổng của server không cố định, mà tùy vào cách bạn khởi chạy trò chơi. 5173, 8000, 3000 - mỗi cách chạy có thể dùng một cổng khác nhau. Hãy lấy địa chỉ từ nơi nó thực sự được in ra: đầu ra của lệnh khởi chạy hoặc thanh địa chỉ của trình duyệt mà bạn vừa dùng để xem trò chơi. Đoán sai cổng có thể đưa bạn đến trang trống, thậm chí là một dự án khác; câu trả lời của công cụ không phải lúc nào cũng giúp nhận ra điều đó.

Việc mở trang là một lời gọi riêng, browser_navigate, với một tham số: url. Bạn có thể giao việc cho agent bằng ngôn ngữ thông thường:

Mở http://localhost:5173/ bằng Playwright
và cho tôi biết tiêu đề của trang vừa mở.

Sau khi mở trang, hãy kiểm tra hai thứ: địa chỉ agent thực sự truy cập và tiêu đề trang. Việc này chỉ mất một giây nhưng giúp tránh cả loạt thao tác nhầm chỗ. Càng đáng làm nếu bạn đang chạy vài dự án cục bộ cùng lúc và một dự án khác cũng phản hồi trên localhost.

Đây không phải cửa sổ trình duyệt của bạn

Playwright tự khởi chạy một trình duyệt riêng. Các tab, tiện ích mở rộng và phiên đăng nhập của bạn không được chuyển sang đó: theo mặc định, trình duyệt dùng một hồ sơ riêng, tách biệt với trình duyệt bạn vẫn dùng. Với trò chơi cục bộ thì không ảnh hưởng gì, nhưng đừng mặc định rằng "nó sẽ mở trang mà tôi đã đăng nhập".

Agent nhìn thấy gì trên trang

Bạn có thể quan sát trang đã mở theo ba cách khác nhau, mỗi cách trả về một loại dữ liệu hoàn toàn khác:

Muốn biếtCông cụKết quả trả về
Trang trông như thế nàobrowser_take_screenshotảnh chụp trang
Trang gồm những gìbrowser_snapshotcây phần tử dạng văn bản: vai trò, tên và tham chiếu đến phần tử
Đọc một giá trị cụ thểbrowser_evaluatekết quả của một biểu thức nhỏ được tính ngay trên trang
Một trang cung cấp ba loại dữ liệu khác nhau: hình ảnh, mô tả các phần tử và giá trị thuộc tínhTrangcờ vuaScreenshot → diện mạobrowser_take_screenshotBàn cờ trông thế nào?Snapshot → phần tửbrowser_snapshotNút "Ván mới" ở đâu?Biểu thức → giá trịbrowser_evaluatedata-turn có giá trị gì?
Chọn cách quan sát dựa trên câu hỏi bạn đặt ra về trang

Cả ba cách đều bắt đầu từ cùng một trang nhưng trả về dữ liệu khác nhau. Snapshot giúp tìm nút, còn giá trị data-turn cần được đọc riêng. Chỉ nhìn ảnh bàn cờ thì không biết được các thuộc tính HTML của nó.

Screenshot là một bức ảnh. Nó trả lời tốt những câu như "trang trông thế nào" hay "các phần tử nằm ở đâu", nhưng không cung cấp tham chiếu đến phần tử để dùng với browser_click. Ở chế độ thông thường của Playwright MCP, thao tác dựa trên accessibility snapshot. Nhấp theo tọa độ là khả năng riêng, chỉ dùng được khi các công cụ tương ứng được bật.

Accessibility snapshot chính là phần mô tả các phần tử. Đó không phải hình ảnh mà là văn bản: danh sách những gì tạo nên trang, kèm vai trò (nút, liên kết, ô nhập), tên và tham chiếu để agent có thể truy cập phần tử sau đó. Agent dựa vào danh sách này khi cần nhấn vào đâu đó. Nếu viết sơ lược, một nút trong đó trông gần như sau:

- button "Ván mới" [ref=e12]
- button "Hoàn tác nước đi" [ref=e13]

Đây là phác thảo để bạn hình dung định dạng, không phải đầu ra thật của công cụ: định dạng chính xác tùy phiên bản server, và ta sẽ không chạy công cụ chỉ để viết bài này. Điều quan trọng không phải dấu câu, mà là nút được gửi cho agent dưới dạng một dòng gồm vai trò, tên và tham chiếu, chứ không phải hình chữ nhật có chữ bên trong.

Đây là điểm nhiều người dễ vướng. Thuộc tính data-turn cho biết bên nào đang đi trong trò chơi của chúng ta, nhưng nó không xuất hiện trong accessibility snapshot. Snapshot được tạo từ vai trò, tên, trạng thái và tham chiếu của phần tử, chứ không phải từ toàn bộ markup trang. Thuộc tính data bất kỳ không nằm trong danh sách đó. Giá trị của thuộc tính đôi khi vẫn xuất hiện riêng trong snapshot nếu trang cũng hiển thị nó dưới dạng văn bản. Nhưng đó là văn bản trên trang, không phải thuộc tính đã được đọc; đừng nhầm hai thứ này với nhau.

Muốn đọc các giá trị như vậy, cần gọi riêng công cụ browser_evaluate. Công cụ chạy một biểu thức ngắn trong ngữ cảnh của trang rồi trả về kết quả. Đây là chạy mã trên trang, không chỉ đọc nội dung, nên đôi khi agent sẽ xin phép trước khi thực hiện. Agent tự viết biểu thức, ví dụ như sau:

// tìm phần tử có nhãn lượt đi và trả về giá trị của nó
() => document.querySelector('[data-turn]')?.getAttribute('data-turn') ?? null
Snapshot và markup không phải một

Accessibility snapshot là phần trích lược, không phải bản sao đầy đủ của markup trang. Snapshot có những thông tin giúp nhận diện và nhấn vào phần tử, nhưng không chứa tất cả mọi thứ. Vì vậy, "agent đã tạo snapshot" và "agent đã đọc mọi thuộc tính" là hai khẳng định khác nhau; điều thứ hai không thể suy ra từ điều thứ nhất.

Cách agent chọn phần tử

Vì sao nhấp theo tọa độ có lúc lại trượt khỏi nút?
Vì tọa độ xác định một điểm trên màn hình, chứ không xác định nút. Bạn kéo rộng cửa sổ, thu gọn thanh bên, đổi cỡ chữ hoặc trang có thêm một dòng ở phía trên - điểm nhấp vẫn ở nguyên chỗ cũ, còn nút đã dịch đi nơi khác. Lệnh nhấp vẫn chạy bình thường, chỉ là nhấp vào chỗ trống.

Vì vậy, cách chọn phần tử chính lại khác. Công cụ browser_click có hai trường cần chú ý ở đây: element là mô tả dễ hiểu với người, chẳng hạn "nút Ván mới", giúp bạn biết agent định nhấn vào đâu; còn target là tham chiếu đến phần tử trong snapshot hoặc một selector duy nhất. Ở đây ta sẽ chọn bằng tham chiếu trong snapshot. Tham chiếu gắn với snapshot đã cung cấp nó, nên thao tác nhấp sẽ nhắm đúng phần tử đó. Nhập văn bản cũng tương tự, chỉ khác là công cụ có tên browser_type và bạn cung cấp thêm nội dung cần nhập.

Điều ổn định không phải bản thân tham chiếu mà là cách tìm ra nó. Vai trò và tên thường khá ổn định: nút "Ván mới" vẫn là nút "Ván mới" kể cả khi nhà thiết kế chuyển nó sang mép bên kia màn hình. Agent dựa vào cặp thông tin này để tìm lại phần tử trong mỗi snapshot mới và lấy tham chiếu mới nhất. Đó là lý do bài viết nhắc đi nhắc lại quy tắc "sau mỗi thao tác, hãy quan sát lại".

Chọn một ô cờ thì khó hơn: có 64 ô và tất cả đều có cùng vai trò. Lúc này, nhãn do chính ứng dụng cung cấp sẽ hữu ích. Nếu ô có tên riêng như e2 hoặc e4, bạn có thể chọn chính xác; nếu mọi ô trong snapshot đều không có tên, chỉ dựa vào vai trò và tên thì không thể chọn đúng ô. Khi đó hãy kiểm tra markup xem ô có dấu hiệu duy nhất nào khác không, hoặc dùng công cụ tọa độ nếu có.

Playwright MCP cũng có công cụ tọa độ, nhưng ở cấu hình thông thường chúng bị tắt và cần bật riêng. Chúng hữu ích khi không thể tiếp cận phần tử bằng cách khác, chẳng hạn trên canvas. Vì cấu hình mặc định không có các công cụ đó nên chỉ dựa vào ảnh thì thật sự không thể thao tác được. Đó chính là điều cảnh báo về screenshot ở đầu phần này muốn nói.

Thao tác và trạng thái mới

Chu trình làm việc: snapshot mới, chọn phần tử, thao tác, đọc trạng thái mới rồi quay lại snapshot trước thao tác tiếp theo1. Snapshot mớiTrang đang có gì?2. Chọn phần tửVai trò, tên → tham chiếutừ snapshot hiện tại3. Thao tácNhấp hoặc nhập văn bản4. Trạng thái mớiSnapshot và, nếu cần,đọc thuộc tínhThao tác tiếp theo dựa trên dữ liệu mới
Sau thao tác, chu trình quay lại bước quan sát

Mũi tên bên trái khép kín chu trình: trước thao tác tiếp theo, agent dùng mô tả mới nhất về trang. Nếu phản hồi của thao tác trước đã có snapshot mới thì bạn có thể dựa vào đó. Thao tác nhấp đã chạy không có nghĩa là thay đổi mong muốn đã xảy ra.

Hãy ghép mọi thứ thành một quy trình có thể giao trọn cho agent. Năm bước, và sau mỗi bước đều biết cần thấy điều gì. Quy trình này giả định các ô trên bàn cờ đã được gắn nhãn, chính là e2 và e4 đã nói ở phần trước. Nếu không có nhãn, ở bước 4 cần một cách khác để chọn ô thật chính xác.

BướcAgent thực hiệnKết quả cần thấy
1mở địa chỉ trò chơitiêu đề trang, bàn cờ đã hiện
2nhấn nút "Ván mới"các quân cờ ở vị trí ban đầu
3đọc data-turnwhite
4đi nước e2-e4 qua giao diện trangcon tốt tiến lên hai ô
5đọc data-turn và quân cờ ở e4black, tốt trắng ở e4
Kiểm tra nước e2-e4: trước nước đi, data-turn là white và tốt ở e2; sau nước đi, ta dự kiến black, ô e2 trống và tốt trắng ở e4; kết luận sau khi đọc lạiTrước nước đidata-turn: whitee2: tốt trắnge4: trốngKết quả mong đợidata-turn: blacke2: trốnge4: tốt trắnge2-e4Đọc lại rồi so sánhDữ liệu có khớp với dự kiến không?
Kiểm tra lượt đi đã đổi và vị trí của con tốt

Bên trái là trạng thái ban đầu, bên phải là kết quả mong đợi sau nước đi. Khung phía dưới nối hai trạng thái bằng bước kiểm tra: agent đọc lại nhãn lượt đi và vị trí các quân cờ, rồi so sánh giá trị. Để kiểm tra kỹ hơn, hãy xác nhận ô e2 ban đầu đã trống. Chỉ thấy white đổi thành black thì chưa đủ để kết luận con tốt đã đến đúng e4.

Với data-turn, cần nói rõ đây là quy ước của một ứng dụng cụ thể. Tác giả trang quyết định đặt thuộc tính này nên ta mới đọc được nó. Playwright không tự tạo ra thuộc tính đó và cũng không lấy nó từ đâu khác; nó chỉ lấy được những gì đã có trên trang. Trang khác không có nhãn này thì cách làm này sẽ không dùng được, bạn cần dựa vào những gì có thể quan sát trực tiếp.

Và đây là quy tắc quan trọng nhất sau mỗi thao tác: hãy đọc lại trạng thái. Snapshot tạo trước cú nhấp chỉ mô tả trang ở thời điểm trước cú nhấp, dù trông nó mới đến đâu. Sau khi bàn cờ được vẽ lại, tham chiếu đến phần tử trong snapshot cũ có thể không còn dùng được; giá trị data-turn đọc trước đó cũng có thể đã thay đổi. Đã thao tác thì hãy xem lại.

Không có nhãn thì hãy nói là không có

Nếu ứng dụng không trả về giá trị cần tìm, câu trả lời đúng của agent là "không đọc được vì trang không có thuộc tính đó". Một giá trị black nghe hợp lý, được suy ra từ việc nước đi có vẻ đã thực hiện, trông giống hệt giá trị đọc thật nhưng hoàn toàn không chứng minh được điều gì. Hãy yêu cầu agent hiển thị chính các giá trị đã đọc, thay vì chỉ thuật lại.

Khi thao tác không thành công

Lỗi thường gặp nhất là "không tìm thấy phần tử". Hãy kiểm tra nhanh theo thứ tự sau:

  1. kiểm tra xem có đúng trang cần mở không. Địa chỉ và tiêu đề - đúng hai thứ đã kiểm tra ở phần đầu;
  2. đảm bảo trang đã tải xong. Trò chơi có thể chưa tải hết, hoặc nút chỉ xuất hiện một chút sau khi gửi yêu cầu;
  3. lấy snapshot mới. Đừng dùng snapshot cũ đã có trong cuộc trò chuyện;
  4. chọn lại phần tử dựa trên mô tả mới nhất trong snapshot đó rồi thử thao tác lần nữa.

Đến bước thứ ba, bạn có thể phát hiện một nguyên nhân: phần tử vẫn còn trên trang nhưng tham chiếu đến nó đã cũ.

Screenshot cũng hữu ích khi kiểm tra sự cố, nhưng không giải quyết được mọi việc. Nó trả lời câu hỏi "trang trông thế nào và các phần tử ở đâu" - chẳng hạn cho thấy bàn cờ đang trống hoặc có một hộp thoại phủ lên trên. Nếu lượt đi được hiển thị bằng văn bản trên trang thì screenshot cũng cho thấy điều đó. Nhưng chỉ đọc thuộc tính mới xác nhận được giá trị chính xác của data-turn. Hai câu hỏi khác nhau cần hai công cụ khác nhau; nhầm lẫn chúng chẳng khác nào nhìn ảnh rồi tranh luận về dữ liệu.

Thay đổi cách giao việc

Yêu cầu dành cho agent không còn là danh sách các cú nhấp, mà trở thành một quy trình có các điểm kiểm tra. Đây là toàn bộ ví dụ ta vừa phân tích, gói gọn trong một yêu cầu:

Mở http://localhost:5173/ bằng Playwright.
1) nhấn "Ván mới";
2) đọc data-turn và cho tôi biết giá trị;
3) đi nước e2-e4 qua giao diện trang;
4) đọc lại data-turn và xem quân cờ nào đang ở e4.
Nếu trang không có một trong các giá trị này, hãy nói rõ
và đừng tự điền giá trị đó.

Có vài điều bạn nên nhớ:

  • địa chỉ phải là địa chỉ thật lấy từ đầu ra khi chạy, không phải một địa chỉ ước chừng;
  • yêu cầu phải nói "đọc và cho tôi biết"; nếu không, bạn chỉ nhận được lời kể về kết quả;
  • cần cho thấy trạng thái trước và sau thao tác. Một giá trị ở cuối không chứng minh được gì nếu ta không biết ban đầu nó là gì;
  • snapshot, screenshot và việc đọc thuộc tính trả lời các câu hỏi khác nhau; hãy chọn công cụ theo câu hỏi chứ đừng theo thói quen;
  • một quy trình mẫu là đủ. Đi được nước e2-e4 không có nghĩa cả ván cờ tiếp theo sẽ đúng luật; kiểm tra cả ván là việc riêng và nghiêm túc hơn nhiều.

Cách dễ nhớ nhất là: trình duyệt cho agent đôi tay và đôi mắt, chứ không cho sẵn kết luận. Agent có thể tự mở trang, nhấn nút và đọc giá trị; còn bạn vẫn là người quyết định giá trị đó có ý nghĩa gì.


Tên các công cụ và tham số được đối chiếu với README Playwright MCP ngày 19 tháng 9 năm 2026 và có thể thay đổi theo từng phiên bản. Trình tự chung - mở trang, quan sát, chọn phần tử, thao tác rồi đọc lại - vẫn giữ nguyên.