Tự viết CLI để kiểm thử ứng dụng Tauri v2 khi các công cụ hiện tại "bó tay"
Sau khi thất bại trong việc thiết lập WebdriverIO cho dự án Tauri v2, tác giả đã tự phát triển tauri-pilot - một công cụ CLI gọn nhẹ giúp kiểm thử E2E, tương tác UI và kiểm tra IPC một cách dễ dàng. Công cụ này hoạt động qua Unix socket, không cần cấu hình phức tạp và được thiết kế để thân thiện với cả con người và AI agents.

Cuối tuần vừa qua, tôi đã dành cả ngày để cố gắng thiết lập kiểm thử đầu cuối (end-to-end testing) cho một ứng dụng Tauri v2. Chỉ sau hai giờ cấu hình WebdriverIO, tôi vẫn không thể kết nối nó với WebView. Tài liệu chính thức chỉ đưa ra một ví dụ tối giản, không hề đề cập đến việc kiểm thử IPC (Inter-Process Communication). Playwright thì hoàn toàn không hoạt động vì Tauri sử dụng WebKitGTK thay vì Chromium.
Tôi đã từ bỏ ý định dùng công cụ có sẵn và tự viết cho mình một giải pháp.
Vấn đề cốt lõi
Nếu bạn tìm kiếm "Tauri e2e testing" trên GitHub, bạn sẽ thấy câu hỏi này được lặp lại liên tục. Tài liệu chính thức chia việc kiểm thử thành hai thế giới riêng biệt: kiểm thử đơn vị (unit tests) bằng Rust cho backend và kiểm thử dựa trên WebDriver cho frontend. Tuy nhiên, không ai hướng dẫn cách xác minh xem các lệnh IPC của Tauri thực sự có hoạt động đúng từ góc độ người dùng hay không.
Bạn thường xuyên phải giả lập (mock) window.__TAURI__ trong các bài kiểm thử frontend và hy vọng môi trường sản xuất (production) hoạt động tương tự.
Nhưng thực tế không phải lúc nào cũng như vậy.
Những gì tôi cần rất đơn giản: kết nối với một ứng dụng Tauri đang chạy, kiểm tra UI, nhấn nút, điền form và kiểm tra kết quả. Giống như Playwright, nhưng dành riêng cho Tauri. Không cần binary WebDriver. Không cần Selenium. Và chắc chắn không cần file cấu hình 200 dòng.
tauri-pilot làm được những gì?
tauri-pilot là một công cụ CLI (command-line interface) viết bằng Rust, giao tiếp với ứng dụng Tauri của bạn qua Unix socket. Bạn chỉ cần thêm một plugin vào ứng dụng (chỉ 2 dòng code, chỉ dành cho bản debug), cài đặt CLI và bắt đầu kiểm thử.
Lệnh snapshot sẽ duyệt qua cây truy cập (accessibility tree) và gán một tham chiếu ngắn (ref) như @e3 cho mọi phần tử tương tác. Bạn sử dụng các tham chiếu này để tương tác:
$ tauri-pilot click @e3
ok
$ tauri-pilot fill @e2 "workspace"
ok
$ tauri-pilot assert text @e1 "PR Dashboard"
ok
$ tauri-pilot assert visible @e7
ok
Mã trả về 0 nghĩa là passed, 1 nghĩa là failed. Đơn giản vậy thôi. Không cần học thêm framework kiểm thử nào cả.
Cài đặt chỉ mất 2 phút
Thêm plugin vào ứng dụng Tauri của bạn:
// src-tauri/src/main.rs
fn main() {
let mut builder = tauri::Builder::default();
#[cfg(debug_assertions)]
{
builder = builder.plugin(tauri_plugin_pilot::init());
}
builder.run(tauri::generate_context!()).expect("error running app");
}
Cài đặt CLI:
cargo install tauri-pilot
Chạy ứng dụng của bạn ở chế độ dev. Xong. Lệnh tauri-pilot ping bây giờ đã có thể phản hồi rồi.
Plugin này chỉ được biên dịch trong các bản debug. Nó sẽ không xuất hiện trong binary release của bạn.
Tại sao không dùng WebdriverIO?
Tôi đã thử. Đây là trải nghiệm thực tế:
Với WebdriverIO, bạn cần cài đặt các dependency của Node.js, cấu hình wdio.conf.ts, trỏ nó về binary đã build (không phải dev server), đảm bảo binary WebDriver khớp với phiên bản WebKit, viết test bằng JavaScript trong khi ứng dụng của bạn dùng Rust, và đối mặt với các selector dễ bị lỗi khi UI thay đổi.
Với tauri-pilot, bạn chạy cargo install tauri-pilot, thêm 2 dòng vào ứng dụng, và bắt đầu viết các lệnh shell. Các tham chiếu cây truy cập (@e1, @e2) ổn định qua các lần render miễn là phần tử đó còn tồn tại. Bạn cũng có thể dùng CSS selectors hoặc tọa độ pixel cho các trường hợp ngoại lệ.
Lệnh diff là thứ WebdriverIO hoàn toàn không làm được:
$ tauri-pilot diff -i
+ button "New PR" [ref=e8]
~ list "PR List" [ref=e4]: children 2 → 3
Nó so sánh trạng thái UI hiện tại với snapshot trước đó và chỉ hiển thị những gì đã thay đổi. Khi bạn đang debug một vấn đề về UI, điều này có giá trị hơn bất kỳ framework assertion nào.
Được xây dựng cho AI Agents
Ban đầu tôi xây dựng tauri-pilot để Claude Code có thể tương tác với các ứng dụng Tauri của tôi. Định dạng đầu ra được thiết kế để các LLM dễ dàng tiêu thụ: cây truy cập gọn gàng, tham chiếu ngắn, phản hồi dạng văn bản thuần.
Quy trình làm việc của một AI agent trông như sau:
- Dùng
snapshot -iđể hiểu trang hiện tại. - Chọn một ref từ đầu ra và tương tác với nó.
- Dùng
assertđể xác minh kết quả. - Dùng
diff -iđể xem những gì thay đổi mà không cần đọc lại toàn bộ cây (tiết kiệm tokens).
Cờ --json trên mọi lệnh cung cấp đầu ra có cấu trúc để sử dụng trong lập trình.
Tuy nhiên, bạn không cần một AI agent để tận dụng điều này. Tôi dùng tauri-pilot trực tiếp trong các bash script và tương tác khi đang phát triển. Nó nhanh hơn nhiều so với việc thủ công click chuột qua UI khi tôi thử lại luồng form lần thứ năm.
Có gì mới trong v0.2.0
Bản phát hành mới nhất (vừa ra mắt) thêm vào các tính năng:
- Ghi lại/phát lại (Record/replay) —
tauri-pilot record start, tương tác với ứng dụng,record stop --output test.json, sau đóreplay test.jsonđể chạy lại toàn bộ phiên. Xuất ra shell script vớireplay --export sh. - Hỗ trợ đa cửa sổ —
windowsliệt kê tất cả cửa sổ, cờ--windownhắm đến một cửa sổ cụ thể. - Dump form —
formslấy tất cả các trường form cùng lúc thay vì gọivaluetrên từng input. - Truy cập localStorage/sessionStorage —
storage get "token",storage set "key" "value",storage list,storage clear. - Kéo thả (Drag & drop) —
drag @e5 @e6cho danh sách sắp xếp,drop @e3 --file ./image.pngcho vùng tải lên file. - Theo dõi DOM —
watchchặn cho đến khi có đột biến DOM xảy ra, sau đó in tóm tắt. Hữu ích khi chờ cập nhật bất đồng bộ.
Cơ chế hoạt động bên trong gầm xe
Plugin khởi động một máy chủ Unix socket khi ứng dụng của bạn khởi chạy. CLI kết nối với socket đó và gửi tin nhắn JSON-RPC. Plugin tiêm một cầu nối JS (window.__PILOT__) vào WebView để xử lý việc kiểm tra DOM.
Sơ đồ kiến trúc tauri-pilot
Phần khó khăn là lấy giá trị trả về từ webview.eval(). Trong Tauri v2, eval() hoạt động theo kiểu "bắn và quên" (fire-and-forget). Không có cách nào lấy lại kết quả trực tiếp. Vì vậy, mọi lần chạy đánh giá JS đều bao bọc script trong try/catch, gọi ngược lại Rust qua IPC (invoke('plugin:pilot|__callback', {id, result})), và phía Rust sẽ đợi trên một kênh oneshot với thời gian chờ là 10 giây.
Nó hoạt động rất ổn định. Tôi đã chạy nó hàng ngày trong nhiều tuần.
Một số hạn chế
Hiện tại chỉ hỗ trợ Linux. Tauri dùng WebKitGTK trên Linux, WKWebView trên macOS và WebView2 trên Windows. Cách tiếp cận socket và cầu nối JS nên hoạt động trên mọi nền tảng, nhưng tôi chưa kiểm thử. Hỗ trợ macOS và Windows đang được lên kế hoạch.
Lệnh chụp màn hình dùng html-to-image được tích hợp sẵn trong cầu nối JS, nghĩa là nó chụp nội dung WebView nhưng không bao gồm các trang trí cửa sổ native hay hộp thoại hệ thống.
Và đây là bản pre-1.0. API có thể thay đổi. Tôi đang dùng semver, nên các bản nâng cấp nhỏ có thể bao gồm các thay đổi không tương thích ngược cho đến khi ra phiên bản 1.0.
Hãy thử ngay
cargo install tauri-pilot
- GitHub: github.com/mpiton/tauri-pilot
- Docs: mpiton.github.io/tauri-pilot
- crates.io: crates.io/crates/tauri-plugin-pilot
Nếu bạn đang kiểm thử một ứng dụng Tauri v2 trên Linux, hãy thử xem. Issues và PRs luôn được chào đón.



