Dev Browser 技能
瀏覽器自動化,可在腳本執行之間保持頁面狀態。編寫小而專注的腳本,逐步完成任務。一旦驗證了工作流程的一部分,並且有重複的工作要做,就可以編寫一個腳本,在單次執行中完成重複的工作。
選擇你的方法
- 本地/原始碼可取得的網站:先閱讀原始碼,直接編寫選擇器
- 未知的頁面佈局:使用
getAISnapshot()探索元素,並使用selectSnapshotRef()與它們互動 - 視覺回饋:截圖以查看使用者所見內容
設定
有兩種模式可供選擇。如果不確定使用哪種,請詢問使用者。
獨立模式(預設)
啟動一個新的 Chromium 瀏覽器,用於全新的自動化工作階段。
./skills/dev-browser/server.sh &
如果使用者要求,請加上 --headless 標誌。在執行腳本前,請等待看到 Ready 訊息。
擴充功能模式
連接到使用者現有的 Chrome 瀏覽器。在以下情況使用此模式:
- 使用者已登入網站,而你想要在已驗證的體驗背後執行操作,且這不是本地開發環境。
- 使用者要求你使用擴充功能
重要提示:核心流程仍然相同。你可以在他們的瀏覽器中建立具名頁面。
啟動中繼伺服器:
cd skills/dev-browser && npm i && npm run start-extension &
等待主控台出現 Waiting for extension to connect... 接著出現 Extension connected。這表示客戶端已連線,瀏覽器已準備好被控制。
工作流程:
- 腳本呼叫
client.page("name"),就像正常模式一樣,以建立新頁面或連接到現有頁面。 - 自動化在使用者的實際瀏覽器工作階段中執行
如果擴充功能尚未連線,請告知使用者啟動並啟用它。下載連結:https://github.com/SawyerHood/dev-browser/releases
編寫腳本
所有腳本都從
skills/dev-browser/目錄執行。@/匯入別名需要此目錄的配置。
使用 heredocs 內聯執行腳本:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect, waitForPageLoad } from "@/client.js";
const client = await connect();
// 建立具有自訂視窗大小的頁面(可選)
const page = await client.page("example", { viewport: { width: 1920, height: 1080 } });
await page.goto("https://example.com");
await waitForPageLoad(page);
console.log({ title: await page.title(), url: page.url() });
await client.disconnect();
EOF
僅在以下情況才寫入 tmp/ 檔案:腳本需要重複使用、很複雜,或使用者明確要求。
關鍵原則
- 小型腳本:每個腳本只做一件事(導航、點擊、填寫、檢查)
- 評估狀態:在腳本結尾記錄/回傳狀態,以決定下一步
- 描述性頁面名稱:使用
"checkout"、"login",而不是"main" - 斷開連線以退出:
await client.disconnect()– 頁面會保留在伺服器上 - 在 evaluate 中使用純 JavaScript:
page.evaluate()在瀏覽器中執行 – 不支援 TypeScript 語法
工作流程迴圈
對於複雜任務,請遵循以下模式:
- 編寫一個腳本來執行一個動作
- 執行它並觀察輸出
- 評估 – 它成功了嗎?目前狀態是什麼?
- 決定 – 任務完成了嗎?還是需要另一個腳本?
- 重複直到任務完成
瀏覽器環境中無 TypeScript
傳遞給 page.evaluate() 的程式碼在瀏覽器中執行,瀏覽器不認識 TypeScript:
// ✅ 正確:純 JavaScript
const text = await page.evaluate(() => {
return document.body.innerText;
});
// ❌ 錯誤:TypeScript 語法會在執行時失敗
const text = await page.evaluate(() => {
const el: HTMLElement = document.body; // 型別註解在瀏覽器中會出錯!
return el.innerText;
});
爬取資料
對於爬取大型資料集,請攔截並重放網路請求,而不是滾動 DOM。請參閱 references/scraping.md 以取得完整的指南,涵蓋請求捕獲、綱要發現和分頁 API 重放。
客戶端 API
const client = await connect();
// 取得或建立具名頁面(viewport 僅適用於新頁面)
const page = await client.page("name");
const pageWithSize = await client.page("name", { viewport: { width: 1920, height: 1080 } });
const pages = await client.list(); // 列出所有頁面名稱
await client.close("name"); // 關閉一個頁面
await client.disconnect(); // 斷開連線(頁面會保留)
// ARIA 快照方法
const snapshot = await client.getAISnapshot("name"); // 取得無障礙樹
const element = await client.selectSnapshotRef("name", "e5"); // 透過參考取得元素
page 物件是一個標準的 Playwright Page。
等待
import { waitForPageLoad } from "@/client.js";
await waitForPageLoad(page); // 導航後
await page.waitForSelector(".results"); // 等待特定元素
await page.waitForURL("**/success"); // 等待特定 URL
檢查頁面狀態
螢幕截圖
await page.screenshot({ path: "tmp/screenshot.png" });
await page.screenshot({ path: "tmp/full.png", fullPage: true });
ARIA 快照(元素探索)
使用 getAISnapshot() 探索頁面元素。回傳 YAML 格式的無障礙樹:
- banner:
- link "Hacker News" [ref=e1]
- navigation:
- link "new" [ref=e2]
- main:
- list:
- listitem:
- link "Article Title" [ref=e8]
- link "328 comments" [ref=e9]
- contentinfo:
- textbox [ref=e10]
- /placeholder: "Search"
解讀 ref:
[ref=eN]– 用於互動的元素參考(僅限可見、可點擊的元素)[checked]、[disabled]、[expanded]– 元素狀態[level=N]– 標題層級/url:、/placeholder:– 元素屬性
與 ref 互動:
const snapshot = await client.getAISnapshot("hackernews");
console.log(snapshot); // 找到你需要的 ref
const element = await client.selectSnapshotRef("hackernews", "e2");
await element.click();
錯誤復原
頁面狀態在失敗後會持續保留。使用以下腳本進行除錯:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("hackernews");
await page.screenshot({ path: "tmp/debug.png" });
console.log({
url: page.url(),
title: await page.title(),
bodyText: await page.textContent("body").then((t) => t?.slice(0, 200)),
});
await client.disconnect();
EOF