# 使用 Patchright 连接 CapyBrowser

Patchright 是可选的 CDP 客户端。CapyBrowser 负责创建、启动、计费和停止浏览器；Patchright 只连接已进入 `ready` 的 Session。下面示例锁定 `patchright@1.63.0`，这是已验证的一个版本，不表示所有版本都兼容。

## 安装客户端

需要 Node.js 20 或更高版本。无需下载 Patchright 自带浏览器：

```sh
mkdir capy-patchright-client && cd capy-patchright-client
npm init -y
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --ignore-scripts --save-exact patchright@1.63.0
```

不要运行 `patchright install`。也不要调用 `chromium.launch()`：浏览器已经由 CapyBrowser 启动。

## 连接已有 Session

先通过 OpenAPI `POST /openapi/v1/browser/sessions/:id/control` 或 MCP `browser_control` 获取本次连接的新 `cdp_url`，只把它放在当前进程的安全配置中。然后连接默认 context 和已有页面：

```js
import { chromium } from "patchright";

const browser = await chromium.connectOverCDP(process.env.CAPY_CDP_URL, {
  timeout: 30_000,
});

try {
  const context = browser.contexts()[0];
  if (!context) throw new Error("CapyBrowser default context not found");

  const pages = context.pages();
  const page = pages.find((candidate) => candidate.url() !== "about:blank") ?? pages[0];
  if (!page) throw new Error("CapyBrowser page not found");

  console.log(await page.title());
} finally {
  // connectOverCDP 下这只断开当前客户端；仍需用 Capy 管理 API 停止 Session。
  await browser.close();
}
```

把代码保存为 `connect.mjs`，再按当前 shell 把一次性控制地址放入进程环境。以下命令都不会回显输入，也不会把控制地址作为命令参数写入 shell 历史。

Bash：

```bash
IFS= read -r -s -p 'Capy CDP URL: ' CAPY_CDP_URL
printf '\n'
export CAPY_CDP_URL
node connect.mjs
unset CAPY_CDP_URL
```

Zsh：

```zsh
read -rs 'CAPY_CDP_URL?Capy CDP URL: '
export CAPY_CDP_URL
node connect.mjs
unset CAPY_CDP_URL
```

控制地址只能建立一次连接。断线后若原 Session 仍为 `ready`，为同一 Session 再申请一个新的控制地址，重新枚举默认 context 中的页面，并按此前记录的 Target 身份找回同一页面。不要复用旧地址、创建第二个 Session，或自动重放结果不明的提交、发送、支付等写操作。

## 按任务继续操作

在默认 context 的已有页面中完成输入、点击、iframe 或新页面操作。连接中断时，为原 Session 获取新的控制地址，重新枚举页面并按此前记录的 Target 身份找回原页面；不要依赖页面数组顺序。继续前先核对实际页面状态，不重放结果不明的写操作。

下载制品时，通过 Session 的下载接口取回文件并核对响应头、内容长度和 SHA-256。使用 Profile 启动后，仍应在页面中确认 cookie、localStorage、IndexedDB 所承载的登录态和业务状态，不能只凭 Session 已就绪推断网站状态已经恢复。

用户交还控制后，为同一 Session 重新申请控制地址并找到原页面，再核对实际输入、点击结果和目标滚动容器的位置后继续。命令已发送或客户端随后能够滚动，都不能替代对目标页面最终状态的检查。

## 兼容性注意事项

Patchright 1.63.0 的 `evaluate` 默认使用隔离世界。若代码确实需要读取网页主世界中自行定义的全局变量，该版本可显式把 `isolatedContext` 设为 `false`：

```js
const value = await page.evaluate(
  () => globalThis.exampleFromPage,
  undefined,
  undefined,
  false,
);
```

Patchright 1.63.0 的页面 console 事件也不保证与 Playwright 1.63.0 完全相同；依赖 `page.on("console")` 的采集逻辑应单独验证。网页 DOM、网络响应或业务结果仍需直接核对，不能用 console 事件数量替代。

客户端能够连接，只证明 CDP 控制链路可用。目标网站仍会按自己的访问、登录和风控规则决定是否提供内容。

## 结束与费用

断开 Patchright 不会停止 Session，也不会停止计费。当前 Agent 创建的 Session 应在 `finally` 中先取回需要的下载，再调用 `DELETE /openapi/v1/browser/sessions/:id` 或 MCP `browser_stop_session`，并查询到 `released`；费用结算再单独检查 `billing.status`。如果只是连接用户提供、由其他流程拥有的 Session，不要自动结束它，除非用户明确把结束责任交给当前 Agent。
