· playwright / stagehand / browser-automation

Playwright vs Stagehand: Tự Động Hóa Trình Duyệt (2026)

Playwright thắng với UI ổn định — Stagehand đáng chi khi selector hỏng trên trang bên ngoài, layout AI tạo, hoặc component thay đổi liên tục.

Bởi

1.666 từ · 9 phút đọc

Bắt đầu với Playwright. Chỉ chuyển sang Stagehand khi UI thực sự khó đoán — trang bên thứ ba, component thay đổi liên tục, layout do LLM tạo ra, hoặc những task mà mô tả ý định bằng tiếng Anh bình thường lại dễ hơn là tìm đúng CSS selector.

Bài viết này dành cho ai

Các developer TypeScript đang viết end-to-end test hoặc script tự động hóa trình duyệt. Nếu bạn không phải đang chọn giữa CSS selector và natural-language prompt, thì đây không phải là quyết định của bạn.

Chúng tôi đã thử nghiệm gì

Playwright v1.60.0 (phát hành 2026-05-11), kèm các trình duyệt: Chromium 148.0.7778.96, Firefox 150.0.2, WebKit 26.4.

Stagehand v3.5.0 (@browserbasehq/stagehand, phát hành 2026-06-03), chạy trên Browserbase, cấu hình với claude-sonnet-4-6 làm backend.

Ba task đại diện, được chọn vì chúng phơi bày các kiểu lỗi khác nhau:

  1. Form fill cố định — một trang login nội bộ ổn định với form nhiều trường và selector đã biết trước.
  2. Scrape nội dung động — trang liệt kê sản phẩm bên ngoài, class thay đổi sau mỗi lần deploy.
  3. Luồng xác thực nhiều bước — chuỗi OAuth redirect qua hai domain, có một popup bất ngờ xuất hiện giữa chừng.

Playwright vs Stagehand: so sánh trực tiếp

Task 1: form fill cố định

Playwright xử lý xong trong 12 dòng. Viết một lần, chạy giống hệt mỗi lần, và selector hoặc hoạt động hoặc không — thông báo lỗi cho bạn biết chính xác locator nào bị hỏng.

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

test('form fill', async ({ page }) => {
  await page.goto('https://app.internal/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('hunter2');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByText('Dashboard')).toBeVisible();
});

Stagehand cũng hoàn thành task, nhưng lần CUA đầu tiên tiêu tốn khoảng 50.000 LLM token và mất 20–30 giây trong khi model phân tích cấu trúc trang. Cache replay giảm xuống còn 0 token và khoảng 2–3 giây — nhưng chỉ sau lần chạy đầu tiên, và chỉ khi trang không thay đổi gì.

Với các UI nội bộ ổn định, khoản chi phí đó khó mà hợp lý. Selector bạn viết trong Playwright cho app nội bộ sẽ không tự hỏng.

Task 2: scrape nội dung động

Đây là lúc bài toán lật ngược. Trang liệt kê sản phẩm bên ngoài thay đổi class name sau mỗi lần deploy; bất kỳ selector hard-coded nào cũng hỏng trong vòng một tuần.

Playwright vẫn có thể xử lý được nhờ getByRole, getByText, hoặc aria attribute, nhưng khi những thứ đó không có mặt — HTML cũ, component tùy chỉnh, không có semantic markup — bạn phải viết XPath dễ vỡ và dự trù thời gian bảo trì kèm theo.

Stagehand v3.5.0 bổ sung screenshot: true cho extract(), giúp chuyển task sang một LLM multimodal đọc trang theo dạng trực quan:

const { productName, price } = await stagehand.page.extract({
  instruction: 'Extract the first product name and price from the listing',
  schema: z.object({
    productName: z.string(),
    price: z.string(),
  }),
  screenshot: true,
});

Không cần selector. Việc trích xuất vẫn hoạt động qua ba lần deploy liên tiếp — những lần đó đã làm hỏng hai phương án Playwright thay thế. Với các trang bên ngoài thực sự khó đoán, đây là giá trị thực sự.

Task 3: luồng xác thực nhiều bước

Luồng OAuth có một điểm bẫy: một popup chấp nhận cookie xuất hiện đột ngột giữa quá trình redirect, và trình duyệt headless xử lý nó khác với headed. Playwright bắt được lỗi và hiển thị output rõ ràng trỏ thẳng vào popup overlay. Sửa chỉ cần thêm một conditional wait.

Stagehand cũng hoàn thành luồng này, nhưng chúng tôi gặp Issue #1635 (tính đến tháng 5 năm 2026, trạng thái trong v3.5.0 chưa rõ): lỗi timing screenshot trong CUA mode. Một screenshot cũ khiến LLM nghĩ rằng hành động trước đó không có tác dụng, nên nó lặp lại click — luồng vẫn hoàn thành, nhưng tốn thêm hai round trip. Issue #1558 (action caching âm thầm bỏ qua custom tool call khi replay, fix PR #1562 chưa được merge tính đến 2026-05-11) không xuất hiện trong test cụ thể của chúng tôi, nhưng đáng lưu ý với bất kỳ CUA agent nào dùng custom tool.

Không bug nào là thảm họa. Cả hai đều là lỗi bạn phát hiện trong QA, không phải production. Nhưng đáng biết trước khi coi CUA mode là production-ready.

Chi phí

Playwright là open-source (Apache 2.0). Miễn phí. CI runner của bạn là khoản chi; chi phí trình duyệt bằng không.

Stagehand chạy trên Browserbase. Giá tính đến 2026-06-08:

GóiGiáGiờ trình duyệt/thángGiới hạn session
Free$01 giờTối đa 15 phút
Developer$20/tháng100 giờ
Startup$99/tháng500 giờ
ScaleThương lượngLinh hoạt

Gói free không phù hợp cho bất cứ thứ gì ngoài mục đích thử nghiệm. Một CI suite chạy 200 test mỗi ngày, mỗi test trung bình 90 giây, sẽ dùng hết gói Developer trong khoảng 5 ngày. Startup đủ cho khoảng 25 ngày với cùng tải đó — sau đó bạn vào vùng Scale hoặc phải xem lại chiến lược test. Để có bức tranh đầy đủ hơn về chi phí hạ tầng AI agent, xem Chi phí thực sự khi vận hành một đội AI agent năm 2026.

Chưa kể chi phí LLM token. CUA mode phát sinh thêm token theo độ phức tạp của trang; một bộ test 50 action có thể chạm đến hàng triệu token ngay lần đầu. Cache replay giảm gần về không — nhưng cache miss là điều chắc chắn xảy ra mỗi khi trang thay đổi, mà đó chính xác là lý do bạn chọn Stagehand.

Với test suite nội bộ trên UI đã biết, Playwright ở $0 là lựa chọn tốt hơn. Chi phí của Stagehand chỉ hợp lý khi lựa chọn thay thế là một kỹ sư ngồi viết lại selector sau mỗi sprint.

Độ tin cậy

Tỷ lệ flake của Playwright trên các task trên: 0 trên 50 lần chạy. Thông báo lỗi chính xác — locator nào, bước nào, frame nào. Debug output đọc được mà không cần model inference.

Stagehand khó đánh giá hơn. CUA mode tạo ra một mức non-determinism cố định: cách LLM diễn giải cùng một trang có thể thay đổi. Action cache giảm thiểu điều này với các tình huống ổn định, nhưng trang thay đổi đồng nghĩa với LLM call mới — và LLM call mới có thể tạo ra chuỗi action khác với lần đầu. Chúng tôi quan sát thấy điều này trong task scrape khi trang sản phẩm deploy thêm header mới; Stagehand retry thành công, nhưng mất hai lần thay vì một.

Retry hoạt động ở tầng model: Stagehand sẽ thử lại nếu model báo không chắc chắn, nhưng không có retry policy có thể cấu hình tương tự như expect().toHaveTimeout() của Playwright. Nếu bạn cần cửa sổ retry có thể đoán trước, bạn phải tự xây dựng.

Kết luận

Tình huốngChọn
UI nội bộ ổn định, selector đã biếtPlaywright
Trang bên ngoài có selector hay hỏngStagehand
UI do LLM tạo hoặc thay đổi liên tụcStagehand
Ngân sách CI bằng khôngPlaywright
Xác thực nhiều bước qua domain đã biếtPlaywright
Task bạn mô tả cho người thực thi còn nhanh hơn viết selectorStagehand

Playwright là lựa chọn mặc định phù hợp cho các dự án TypeScript mới. Nhanh hơn, rẻ hơn, và kết quả có thể đoán trước. Stagehand giải quyết một vấn đề cụ thể: khi viết CSS selector chính là điểm tắc nghẽn, vì trang quá khó đoán hoặc vì bạn không kiểm soát được markup.

Hai công cụ không loại trừ nhau. Tùy chọn boxes mới của Playwright v1.60.0 trên ariaSnapshot() — được ghi chú là “hữu ích cho AI” — là tín hiệu cho thấy Playwright đang tự hướng đến hybrid. Chạy Playwright cho trang của bạn và Stagehand cho các tích hợp bên thứ ba là cách phân chia hợp lý.

Nếu bạn còn đang chọn giữa Playwright và Cypress, xem Playwright vs Cypress 2026. Về vị trí của E2E test trong chiến lược kiểm thử tổng thể, xem Test pyramid đã chết — chiến lược kiểm thử thay thế năm 2026.

Lưu ý

Các con số về tốc độ (“20–40% nhanh hơn”, “a11y-tree nhanh hơn nhiều”) đến từ blog của chính Browserbase — không phải benchmark độc lập. Không có benchmark head-to-head độc lập nào tồn tại trong tài liệu sơ cấp tính đến thời điểm viết bài này. Hãy coi các tuyên bố về hiệu năng của nhà cung cấp là định hướng, không phải số liệu đã đo đạc.

Không có số liệu lượt tải Stagehand đáng tin — con số 1M+ được chia sẻ rộng rãi đã bị bác bỏ. Kiểm tra trực tiếp tại npm trends nếu bạn cần số liệu hiện tại.

Giá Browserbase có thể thay đổi mà không báo trước. Xác minh giá hiện tại tại browserbase.com trước khi quyết định gói.

Hai bug đang mở (#1635, #1558) xuất hiện trước v3.5.0. Trạng thái fix của chúng trong bản hiện tại chưa được xác nhận — kiểm tra với Browserbase trước khi đưa CUA mode lên production.

Tham khảo