# SDK 能力清单

`BrowserClient` 有两套入口:
1. **直接方法**(`browser.xxx()`)— 不走 operate,见下表一。
2. **operate(action)**(`browser.operate({ pageHandle, action, params })`)— 见下表二。

> 自动生成。operate(action) 来自 `browser-op/backend/browserd.cjs` 的 `@action` 标注;
> 直接方法来自 `src/client.ts`(脚本内维护,改公开方法时同步)。始终权威。

## 约定

- **pageHandle**:operate(action) 都需要,由 `newTab()` / `listTabs()` 返回的 `tab.pageHandle` 提供。
- **selector 语法**:CSS / `aria-ref=eN`(来自 snapshot 的 ref)/ `.cls>>nth(n)` / `.cls>>last`。
- **ext 列**:action 在双路由(CDP / WebPlater 扩展)的可用性。
  - ✓ = CDP 与扩展均支持
  - ✗ = 仅 CDP(扩展路不支持,如 `waitForFunction`,因扩展路 CSP 拦截 eval)
  - 仅 ext = 仅扩展路(如 `setDialogHandler`,依赖扩展 API)

## 一、BrowserClient 直接方法(不走 operate)

### 生命周期 / tab

| 方法 | 签名 | 说明 |
| --- | --- | --- |
| `health` | health(timeoutMs?) | 探测 browserd 是否就绪 + browser 是否 open。返回 null = 连不上 |
| `open` | open({ routeMode: "cdp"\|"extension", headless? }) | 打开/复用浏览器(幂等)。routeMode 必填(无 auto) |
| `close` | close() | graceful 关浏览器(保留登录态)。用户满意前不要 close |
| `newTab` | newTab({ url?, force? }) | 新建 tab(或复用同 url 的 tab)。返回含 pageHandle |
| `listTabs` | listTabs() | 列所有 tab(每项含 pageHandle) |
| `reuseTab` | reuseTab({ url?, urlMatch? }) | 按 url/urlMatch 找已有 tab 复用;没有才 newTab。高危平台频繁重开触发风控,已开的 tab 应复用 |

### cookie

| 方法 | 签名 | 说明 |
| --- | --- | --- |
| `getCookies` | getCookies({ url?, domain? }) | 读 cookie(按 url 或 domain 过滤) |
| `setCookies` | setCookies(cookies[]) | 写 cookie |
| `clearCookies` | clearCookies({ url? \| domain? }) | 清 cookie(按 url 或 domain;不传清该域全部) |

### 下载

| 方法 | 签名 | 说明 |
| --- | --- | --- |
| `listDownloads` | listDownloads(limit?) | 列最近下载(默认 20)。extension 读 Chrome 历史；CDP 读本次 browserd 会话记录 |
| `downloadUrl` | downloadUrl({ url, filename?, conflictAction?, saveAs? }) | 用 Chrome 下载管理器发起真实浏览器下载, 返回 download id |
| `downloadUrlAndWait` | downloadUrlAndWait({ url, filename?, conflictAction?, saveAs?, timeoutMs?, intervalMs? }) | 已有真实下载 URL 时直接交给 Chrome 下载管理器并等待完成；页面按钮用 window.open 且扩展合成点击被拦截时使用 |
| `downloadWithActions` | downloadWithActions({ pageHandle, steps, filenameRegex?, timeoutMs?, intervalMs?, sinceMs? }) | 执行页面动作序列并等待动作触发的下载记录 |
| `getDownload` | getDownload(id) | 查单个下载状态 |
| `waitForDownload` | waitForDownload({ id?, filenameRegex?, sinceMs?, timeoutMs?, intervalMs? }) | 轮询等下载完成(reason=complete 成功,timeout 超时)。可按 download id 或 filenameRegex 等待 |
| `allowAutomaticDownloads` | allowAutomaticDownloads(primaryPattern \| { primaryPattern, secondaryPattern?, scope? }) | 允许指定站点自动下载多个文件(Chrome automaticDownloads content setting; extension 路) |
| `getAutomaticDownloadsSetting` | getAutomaticDownloadsSetting(primaryUrl \| { primaryUrl, secondaryUrl?, incognito? }) | 查询指定站点 automaticDownloads 当前设置(extension 路) |
| `resetAutomaticDownloadsSetting` | resetAutomaticDownloadsSetting(primaryPattern \| { primaryPattern, secondaryPattern?, scope? }) | 清除指定站点 automaticDownloads 例外设置(extension 路) |

### 通用等待

| 方法 | 签名 | 说明 |
| --- | --- | --- |
| `waitFor` | waitFor({ tab?, selector?, text?, tabTarget?, frameTarget?, condition?, pollTimeoutMs?, intervalMs?, maxRefresh?, refreshSettleMs?, frameId?, failFastOnError? }) | 统一等待 DOM、Tab、Frame 或组合状态。Tab/Frame 可等出现或消失；condition 返回同次观测证据。状态机等待默认不刷新 |

### Python Browser 直接方法映射

| JS/TS BrowserClient | Python Browser |
|---|---|
| `health()` | `health()` |
| `open({...})` | `open(route_mode=..., headless=...)` |
| `close()` | `close()` |
| `newTab({url, force})` | `new_tab(url, force)` |
| `listTabs()` | `list_tabs()` |
| `reuseTab({url, urlMatch})` | `reuse_tab(url, url_match)` |
| `getCookies(...)` | `get_cookies(...)` |
| `setCookies(cookies)` | `set_cookies(cookies)` |
| `clearCookies(...)` | `clear_cookies(...)` |
| `listDownloads(limit)` | `list_downloads(limit)` |
| `downloadUrl(...)` | `download_url(...)` |
| `downloadUrlAndWait(...)` | `download_url_and_wait(...)` |
| `getDownload(id)` | `get_download(id)` |
| `waitForDownload(...)` | `wait_for_download(...)` |
| `allowAutomaticDownloads(...)` | `allow_automatic_downloads(...)` |
| `getAutomaticDownloadsSetting(...)` | `get_automatic_downloads_setting(...)` |
| `resetAutomaticDownloadsSetting(...)` | `reset_automatic_downloads_setting(...)` |

Python 页面动作：`browser.operate(page_handle, "actionName", **params)`。
JS/TS 页面动作：`browser.operate({ pageHandle, action: "actionName", params })`。

## 二、operate(action) 清单

### 交互

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `clickAt` | `x`:number, `y`:number, `button`?:string=left, `clickCount`?:number=1 | {ok,x,y,route} | 坐标点击(绕过selector,CDP路真实鼠标/扩展路合成事件) | ✓ |
| `clickByText` | `text`:string, `exact`?:boolean=false, `index`?:number=0, `within`?:string, `offsetX`?:number=0, `offsetY`?:number=0 | {ok,text,x,y,match,route} | 定位可见文本后立即点击(原子,防DOM重渲染)。within 约束容器消歧; 点可点击祖先(React事件代理友好)。 | ✓ |
| `clickIfState` | `text`:string, `activeSelector`:string, `exact`?:boolean=false, `index`?:number=0, `within`?:string | {ok,alreadyActive,clicked,x,y} | 条件点击:文本元素(或祖先)已匹配 activeSelector 则跳过,未匹配才点。 | ✓ |
| `click` | `selector`:string, `x`?:number, `y`?:number | {ok,selector} | 点击元素(或坐标x,y) | ✓ |
| `fill` | `selector`:string, `value`:string | {ok,selector,value} | 在selector元素填入文本(先清空) | ✓ |
| `type` | `selector`:string, `value`:string, `delay`?:number=0 | {ok,selector} | 逐字输入(带延迟,模拟键盘) | ✓ |
| `press` | `selector`:string, `key`:string | {ok,selector,key} | 在元素上按键 | ✓ |
| `hover` | `selector`:string, `x`?:number, `y`?:number | {ok,selector} | 悬停元素(或坐标) | ✓ |
| `focus` | `selector`:string | {ok,selector} | 聚焦元素 | ✓ |
| `check` | `selector`:string | {ok,selector} | 勾选checkbox/radio | ✓ |
| `uncheck` | `selector`:string | {ok,selector} | 取消勾选 | ✓ |
| `selectOption` | `selector`:string, `value`:string | {ok,selector,value} | 选择option | ✓ |
| `dblclick` | `selector`:string | {ok,selector} | 双击元素 | ✓ |
| `mouseMove` | `x`:number, `y`:number | {ok,x,y} | 移动鼠标到坐标 | ✓ |
| `dragTo` | `source`:string, `target`:string | {ok,source,target} | 拖拽source到target(双兼容mouse+HTML5) | ✓ |

### 读取

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `locateVisibleText` | `text`:string, `exact`?:boolean=false, `index`?:number=0, `within`?:string | {ok,matches:[{text,x,y,width,height,centerX,centerY,visible,clickableTag,clickableX,clickableY}]} | 定位可见文本节点返回bbox(不返回DOM handle)。within 约束容器消歧。 | ✓ |
| `readTable` | `selector`?:string, `headerText`?:string, `column`?:string | {ok,headers,rows,rowCount,column?,values?} | 按selector或表头文本定位表格返回整表或单列(纯读原始文本,不解析数字) | ✓ |
| `readCard` | `anchorText`:string, `requireKeywords`?:array, `maxDepth`?:number=10 | {ok,texts:string[]} | 按锚文本定位容器返回容器内叶子文本序列(业务解析留给step) | ✓ |
| `innerHTML` | `selector`?:string, `text`?:string, `exact`?:boolean=false, `within`?:string | {ok,selector,value} | 读元素innerHTML。支持文本定位(text/exact/within)替代selector | ✓ |
| `innerText` | `selector`?:string, `text`?:string, `exact`?:boolean=false, `within`?:string | {ok,selector,value} | 读元素innerText。支持文本定位(text/exact/within)替代selector | ✓ |
| `textContent` | `selector`?:string, `text`?:string, `exact`?:boolean=false, `within`?:string | {ok,selector,value} | 读元素textContent。支持文本定位(text/exact/within)替代selector | ✓ |
| `getAttribute` | `selector`?:string, `text`?:string, `exact`?:boolean=false, `within`?:string, `name`:string | {ok,selector,name,value} | 读元素属性。支持文本定位(text/exact/within):命中文本后读其可点击祖先的属性(如tr的data-row-key) | ✓ |
| `inputValue` | `selector`?:string, `text`?:string, `exact`?:boolean=false, `within`?:string, `timeout`?:number=10000 | {ok,selector,value} | 读input/select当前值。支持文本定位(text/exact/within) | ✓ |
| `boundingBox` | `selector`:string | {ok,selector,x,y,width,height} | 读元素包围盒(坐标+尺寸) | ✓ |
| `count` | `selector`:string | {ok,selector,count} | 统计selector匹配数 | ✓ |
| `snapshot` | `depth`?:number, `timeout`?:number=15000 | {ok,yaml,totalChars} | aria无障碍快照(返回yaml) | ✓ |

### 进阶

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `operateSequence` | `steps`:array | {ok,results} | 原子序列执行(locate/click/wait在同一页面上下文串行) | ✓ |
| `fetchInPage` | `url`:string, `method`?:string=GET, `body`?:string | {ok,status,statusText,contentType,contentDisposition,body,bodyLength} | 页面上下文fetch(带cookie,绕CORS) | ✓ |
| `setDialogHandler` | `handler`:string | {ok} | 设置JS对话框处理 | 仅 ext |
| `evaluate` | `source`:string, `args`?:any | 由函数返回值决定 | 执行页面JS函数(返回其结果) | ✓ |
| `setInputFiles` | `selector`:string, `files`:array, `timeout`?:number=10000 | {ok,selector,count} | 上传文件到file input | ✓ |

### 导航

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `goto` | `url`:string | {ok,url} | 导航到URL,等待DOMContentLoaded | ✓ |
| `goBack` | `timeout`?:number=15000 | {ok,url} | 后退一页 | ✓ |
| `goForward` | `timeout`?:number=15000 | {ok,url} | 前进一页 | ✓ |
| `reload` | `timeout`?:number=15000 | {ok,url} | 刷新当前页 | ✓ |
| `status` | — | {ok,url,title,readyState,textLength} | 读当前页状态(url/title/readyState/textLength) | ✓ |

### 等待

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `waitForLoadState` | `state`?:string=load, `timeout`?:number=30000 | {ok} | 等待指定加载状态 | ✓ |
| `waitForSelector` | `selector`:string, `timeout`?:number=30000 | {ok,selector,visible,inViewport} | 等待selector元素可见 | ✓ |
| `waitForText` | `text`:string, `exact`?:boolean=false, `within`?:string, `timeout`?:number=30000, `interval`?:number=500 | {ok,text,matchCount} | 等文本出现(等价waitForSelector但按文本)。轮询locateVisibleText,match>0即就绪。 | ✓ |
| `waitForFunction` | `source`:string, `timeout`?:number=30000 | 由函数返回值决定 | 等待页面函数返回truthy | ✗ (extension路CSP拦截eval) |
| `waitForURL` | `url`:string, `timeout`?:number=30000 | {ok,url} | 等待URL匹配 | ✓ |
| `waitForTimeout` | `ms`?:number=1000 | {ok,ms} | 固定等待 | ✓ |

### 标签页

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `closeTab` | — | {ok} | 关闭当前page | ✓ |
| `activate` | — | {ok} | 切到最前(仅可视) | ✓ |

### 截图

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `screenshot` | — | {ok,format,dataUrl} | 截整页PNG(返回dataUrl) | ✓ |

### 存储

| action | params | returns | description | ext |
| --- | --- | --- | --- | --- |
| `getLocalStorage` | `keys`?:array | {ok,storage} | 读localStorage(不传keys读全部) | ✓ |
| `setLocalStorage` | `items`:object | {ok,count} | 写localStorage | ✓ |
| `removeLocalStorage` | `keys`?:array | {ok,count} | 删localStorage(不传keys删全部) | ✓ |
| `clearLocalStorage` | — | {ok} | 清空localStorage | ✓ |
