> ## Documentation Index
> Fetch the complete documentation index at: https://extension.js.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 扩展的 Playwright E2E 测试

> 使用 Playwright 编写可重复的浏览器扩展端到端测试。覆盖扩展流程、UI 渲染以及与外部系统的集成，适用于本地和 CI。

用可重复的端到端测试在多个浏览器中验证扩展行为。

Extension.js 项目可以使用 Playwright 来测试扩展流程、UI 渲染与集成行为，适用于持续集成 (CI) 与本地环境。

## Playwright 测试能力

| 能力        | 给你带来什么                                       |
| --------- | -------------------------------------------- |
| 跨浏览器运行时检查 | 在 Chromium 与 Firefox 引擎上验证核心流程               |
| UI 与交互覆盖  | 在真实浏览器上下文中测试 popup/options/content-script 行为 |
| 适合 CI 的报告 | 为失败的测试捕获 trace/截图/录像                         |
| 回归安全      | 捕获单元测试通常错过的集成 bug                            |

## 为什么使用

* 捕获单元测试错过的运行时回归。
* 在真实浏览器引擎上验证扩展行为。
* 在发布前验证多浏览器变更。

## 典型设置

在项目中安装 Playwright 测试依赖：

<PackageManagerTabs command="install -D @playwright/test" />

创建 `playwright.config.ts`，定义浏览器项目与报告方式。

## 推荐基线

```ts theme={null}
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "e2e",
  retries: process.env.CI ? 2 : 0,
  reporter: [["html"], ["list"]],
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
  ],
});
```

## 自动化契约(推荐)

为了确定性自动化，不要解析终端文本。请使用 Extension.js 生成的元数据文件：

* `dist/extension-js/<browser>/ready.json`
* `dist/extension-js/<browser>/events.ndjson`(watch/重建事件的换行分隔 JSON)

`ready.json` 包含为脚本/Agent 设计的稳定字段：

* `status`：`starting` | `ready` | `error`
* `command`：`dev` | `start` | `preview`
* `browser`
* `distPath`
* `manifestPath`
* `port`
* `pid`
* `runId`
* `startedAt`
* `compiledAt`
* `errors`

把这份契约作为就绪和失败的真相来源。

## 运行测试

<CodeGroup>
  ```bash npm theme={null}
  npx playwright test
  ```

  ```bash pnpm theme={null}
  pnpm playwright test
  ```

  ```bash yarn theme={null}
  yarn playwright test
  ```

  ```bash bun theme={null}
  bunx playwright test
  ```

  ```bash bun theme={null}
  bunx playwright test
  ```
</CodeGroup>

## 标准 Playwright 流程(对 AI 友好)

1. 以 no-browser 模式启动 Extension.js。
2. 等到 `ready.json` 报告 `status: "ready"`。
3. 用 `distPath` 中的扩展产物启动 Playwright。
4. 运行测试并关闭。

### 开发模式 vs 测试模式

* `dev` 用于 watch 模式迭代(`extension dev --no-browser` + `extension dev --wait`)。
* `start` 用于生产风格的检查(`extension start --no-browser` + `extension start --wait`)。

### 重要区分：运行模式 vs 就绪门

* `--no-browser` 是**运行模式**：在不启动浏览器的情况下启动扩展流水线。
* 就绪门(`extension dev --wait --browser=<browser>`) 是**同步步骤**，告诉 Playwright 扩展何时就绪。

`--no-browser` 生成构建产物。wait 步骤在测试继续前确认就绪。如果你的环境无法运行第二个 CLI 进程，请直接轮询 `ready.json`。

使用如下双进程模式：

1. 进程 A：`extension dev --no-browser`
2. 进程 B：`extension dev --wait --browser=<browser> --wait-format=json`
3. 仅在 wait 步骤成功退出后再启动 Playwright

面向生产的变体：

1. 进程 A：`extension start --no-browser`
2. 进程 B：`extension start --wait --browser=<browser> --wait-format=json`
3. 仅在 wait 步骤成功退出后再启动 Playwright

```ts theme={null}
import { spawn } from "node:child_process";
import { chromium } from "@playwright/test";

const browserName = "chromium";

const child = spawn(
  "pnpm",
  ["extension", "dev", "--no-browser", `--browser=${browserName}`],
  {
    stdio: "inherit",
    env: process.env,
  },
);

async function waitForReady() {
  return await new Promise<any>((resolve, reject) => {
    let stdout = "";
    let stderr = "";
    const wait = spawn(
      "pnpm",
      [
        "extension",
        "dev",
        "--wait",
        `--browser=${browserName}`,
        "--wait-timeout=60000",
        "--wait-format=json",
      ],
      { stdio: ["ignore", "pipe", "pipe"], env: process.env },
    );
    wait.stdout.on("data", (chunk) => (stdout += chunk.toString()));
    wait.stderr.on("data", (chunk) => (stderr += chunk.toString()));
    wait.on("error", reject);
    wait.on("close", (code) => {
      if ((code ?? 1) !== 0) {
        reject(
          new Error(stderr || `wait command failed with code ${String(code)}`),
        );
        return;
      }
      const payload = JSON.parse(stdout.trim());
      resolve(payload.results[0]);
    });
  });
}

const ready = await waitForReady();

const context = await chromium.launchPersistentContext("", {
  headless: false,
  args: [
    `--disable-extensions-except=${ready.distPath}`,
    `--load-extension=${ready.distPath}`,
  ],
});

// run tests using context/pages...

await context.close();
child.kill("SIGTERM");
```

## 针对扩展的实用建议

* 让测试夹具保持确定性；扩展启动可能对 profile 状态敏感。
* 优先在扩展 UI 状态上做显式等待，而不是固定超时。
* 在 CI 中同时跑 Chromium 与 Firefox 项目，获得跨引擎信心。
* 在失败时捕获 trace/截图/录像，方便加速调试。
* 在机器可靠性上，优先使用 `ready.json`/`events.ndjson`，而不是解析 stdout。

## 常见陷阱

* 用固定超时取代基于状态的等待
* 在 CI 中只跑一个浏览器目标
* 失败运行未上传产物
* 让测试耦合于本地 profile 或环境的假设

## 仓库参考

你的仓库中包含一份 Playwright 配置：

* `playwright.config.ts`

## 下一步

* 配置 [CI 模板](/docs/workflows/ci-templates)。
* 让命令工作流与 [dev](/docs/commands/dev) 和 [build](/docs/commands/build) 保持一致。
