` 或 CI 提供的 changed-files 接口。
- 从 diff 中解析所有新增行段(hunk header `@@ -a,b +c,d @@`,新增行从 `c` 开始,长度为 `d`,或以 `+` 行前缀收集行号)。
- 结果:对每个文件 `f`,得到新增行号集合 `L_f = {l1, l2, ...}`。
2. **加载文件的覆盖率信息**(Istanbul JSON)
- coverage[filePath] 包含 `statementMap`(语句范围)与 `s`(语句执行次数)。
- `statementMap`:例如 `{ "1": { "start": {line, column}, "end": {line, column} }, ... }`
- `s`:例如 `{ "1": 0, "2": 3, ... }`。
3. **计算 impacted statements `I_f`**
- 对每个 `statementId`,取其行区间 `[start.line, end.line]`。
- 如果该区间与 `L_f` 有交集(存在 l ∈ L_f 使得 start.line ≤ l ≤ end.line),则把该 `statementId` 加入 `I_f`。
4. **判定覆盖**
- 对 `I_f` 中每个 `statementId`,若 `s[statementId] > 0` 则认为该语句被覆盖,加入 `C_f`。
5. **计算文件级和合并覆盖率**
- 文件级:`coverage_f = |C_f| / |I_f|`
- 全量:合并分子与分母求比。
6. **输出结果与字段**
- 每个文件:`{ file, impacted_statements: |I_f|, covered_statements: |C_f|, coverage: coverage_f }`
- 合并:`{ total_impacted: Σ|I_f|, total_covered: Σ|C_f|, change_coverage: Σ|C_f|/Σ|I_f| }`
---
## 五、示例(示意)
假设文件 `foo.js` 的覆盖率中有 3 个语句:
- stmt1: lines 1–3, execCount = 2
- stmt2: lines 4–6, execCount = 0
- stmt3: lines 7–10, execCount = 1
`git diff` 发现新增行集合 `L_foo = {2, 5}`,则:
- stmt1 intersects line 2 → impacted
- stmt2 intersects line 5 → impacted
- stmt3 不受影响
于是 `I_foo = {stmt1, stmt2}`,`C_foo = {stmt1}`(因为 stmt2 执行次数 0)
文件级 coverage: `coverage_foo = 1 / 2 = 50%`
若这是唯一变更文件,则 `change_coverage = 50%`
# 代码插桩
本章介绍前端测试中代码插桩的基本概念、常见插桩方式,以及在单元测试和端到端测试(E2E)中的实际应用方案。Canyon 的覆盖率能力依赖于稳定、可回溯的插桩结果,因此理解插桩机制是使用 Canyon 的前提。
---
## 什么是代码插桩
**代码插桩(Code Instrumentation)**,是指在源码或构建产物中插入“探针代码”,用于记录代码在运行过程中是否被执行、执行次数等信息。
覆盖率工具正是依赖这些探针,在测试结束后统计出:
- 哪些文件被执行过
- 哪些函数 / 语句 / 分支被执行
- 哪些代码从未被覆盖
无论是**单元测试**还是**端到端测试(E2E)**,只要需要覆盖率数据,都绕不开代码插桩。
---
## 插桩方式分类
在前端领域,主流的代码插桩方式可以分为两类:
### 1. V8 原生覆盖率插桩
- 基于 V8 引擎
- 在运行时从 **字节码执行信息** 中获取覆盖率
- 不修改源码或构建产物
- 常见于 Node.js、Chromium 浏览器环境
**特点:**
- 无需改动构建流程
- 性能开销较低
---
### 2. Istanbul 插桩(静态插桩)
- 在 **构建阶段** 对代码进行 AST 转换
- 通过插入计数器的方式记录执行情况
- 常见实现:
- `babel-plugin-istanbul`
- `vite-plugin-istanbul`
**特点:**
- 插桩发生在源码 → 构建产物阶段
- 结果稳定、可控
- 构建产物体积会变大
---
## E2E 测试中的插桩问题
端到端测试(如 Playwright)与单元测试最大的不同在于:
> **代码运行在真实浏览器内核中**
这会直接影响覆盖率的采集方式。
---
### Playwright + V8 覆盖率的局限
Playwright 支持通过 Chromium 的 V8 接口获取 JS 覆盖率,这在以下场景中是可行的:
- 原生 HTML / CSS / JS
- 未打包、未压缩的脚本
- Demo 或简单页面
但在真实生产项目中,往往存在:
- 构建工具打包(Vite / Webpack / Rspack)
- 代码压缩、混淆
- 多模块合并
- 动态加载
此时:
- 覆盖率基于 **打包后的 JS**
- 与源码结构严重偏离
- Source Map 还原成本极高
- 结果不可控、不可验证
---
## Istanbul 插桩在 E2E 中的解决方案
在现代 SPA 项目中,**推荐在构建阶段使用 Istanbul 插桩**,再在 E2E 测试中直接收集浏览器内存中的覆盖率数据。
### 核心思路
1. **构建阶段**
- 使用 `babel-plugin-istanbul` 等工具
- 在 AST 转换时插入探针
- 生成带插桩的构建产物
2. **运行阶段(Playwright)**
- 浏览器执行插桩代码
- 覆盖率数据累积在 `window.__coverage__`
3. **测试阶段**
- Playwright 从浏览器上下文中读取覆盖率对象
- 上报给 Canyon 做聚合分析
# 还原源代码覆盖率
前端应用有各种编译形式。在很多情况下,预编译的 AST 会交给 Babel 进行转换,这使得无法直接将覆盖率映射到源代码。因此,我们需要还原源代码覆盖率。
## Source Map
> 在前端开发中,JavaScript 代码通常会被压缩、混淆或使用预处理器(如 TypeScript、Babel)进行转换,以提高性能和兼容性。这些过程使得部署到生产环境的代码与原始源代码非常不同,增加了调试难度。Source Map 通过记录编译后代码与原始代码之间的映射关系来解决这个问题,允许开发者在浏览器开发者工具中查看和调试原始代码。 -- [web.dev](https://web.dev/articles/source-maps)
在大多数情况下,您不需要手动获取 sourceMap 文件,因为大多数构建工具在`将预编译的 AST 交给 Babel 进行转换`的过程中会传递 sourceMap 文件。
然而,在某些情况下,您可能需要配置 sourceMap 文件。
## 需要启用 sourceMap 选项的情况
```js filename="webpack.config.js" {4}
const path = require("path");
module.exports = {
entry: "./build/main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: ["babel-loader"],
exclude: "/node_modules/",
},
],
},
};
```
在这个例子中,webpack 的入口文件是 `./build/main.js`,这是 tsc 编译 TypeScript 文件的输出。我们需要在 tsconfig.json 中将 `sourceMap` 设置为 `true` **以确保传递 sourceMap 数据。**
```json filename="tsconfig.json" {3}
{
"compilerOptions": {
"sourceMap": true
}
}
```
## 需要手动设置 sourceMap 的情况
当您的 sourceMap 生成方式属于以下情况时,您需要手动设置 sourceMap。
| 类别 | devtool | 描述 |
| ---------------------------------- | -------------------- | ----------------------- |
| 生成 source map 文件,不显示源代码 | hidden-source-map | 文件末尾不引用 map 文件 |
| 生成 source map 文件,不显示源代码 | nosources-source-map | 文件末尾不引用 map 文件 |
### 如何手动设置 sourceMap
在这种情况下,您需要使用[分离 Hit 和 Map](/docs/core-concepts/separate-hit-and-map)。通过 Tools CLI,在构建阶段检测本地 sourceMap 文件,Canyon 会将它们与初始覆盖率数据进行匹配并上传。
> [!NOTE]
>
> 这也是 JavaScript 过于灵活的一个缺点。为了收集准确的源代码覆盖率数据,我们需要将这些 sourceMap 文件链接在一起,以便代码插桩能够正确映射到源代码。
import { Callout } from "nextra/components";
# 分离 Hit 和 Map
在 CI 中收集 Babel 编译期间生成的初始覆盖率源文件
## 原因
1. **收集完整的编译文件**:在编译过程中,通过 `@canyonjs/babel-plugin` 插件,分析并解析每个编译文件的初始覆盖率文件,并将其保存到 `.canyon_out` 文件夹中。
2. **早期收集以减轻压力**:如果不报告初始覆盖率文件,这些覆盖率文件将在 UI 自动化测试期间被报告,这会产生巨大的传输压力。早期收集可以减少超过 90% 的传输压力。
## 报告初始覆盖率数据
@canyonjs/babel-plugin 插件在编译过程中为编译后的文件生成初始覆盖率文件,并将其保存到 .canyon_output 文件夹中。我们提供了 Tools CLI 命令行工具,您可以在 CI 中使用它向 Canyon 服务器报告。
```yml filename=".gitlab-ci.yml" {19,20} copy
pages:
image: node:20
stage: deploy
script:
- npm run build
- npx @canyonjs/cli upload --dsn=https://app.canyonjs.io/api/coverage/map/init
only:
- main
```
## 准备就绪
现在检查页面上的 window.coverage 对象,如截图所示。window.coverage 不再包含 map 数据,这可以大大降低 UI 自动化覆盖率收集过程中的大量传输成本。

# 快照(Snapshot)
Snapshot 是 Canyon 对某个覆盖率视图的“冻结产物”。它会把当时的覆盖率数据与可直接打开的报告页面一起打包保存,便于后续对比、归档和共享。
---
## 一、核心价值
- **可追溯**:把某一时刻的覆盖率结果固化,不受后续分支变化影响。
- **可下载**:直接下载报告包(`artifact_zip`),本地解压即可查看 `index.html`。
- **可对比**:同一仓库可按 `commit` 或 `compare` 维度留存多份快照。
---
## 二、支持的 subject
Snapshot 支持两类 subject:
- `commit`:某个 commit 的全量覆盖率快照
- `compare`:`base...head` 对比视图的覆盖率快照
这也是前端列表中“总体覆盖率”和“变更代码覆盖率”互斥显示的原因:
- `commit`:重点看总体覆盖率
- `compare`:重点看变更代码覆盖率
---
## 三、状态流转
创建快照后,状态会按以下流程变化:
- `generating`:生成中
- `completed`:生成完成,可下载
- `failed`:生成失败
- `timeout`:超时(默认 120 秒)
系统会在查询列表/详情时对超时任务做状态修正,避免长期停留在 `generating`。
---
## 四、生成产物内容
Snapshot 不是只存一份原始 JSON,而是生成可视化报告包:
1. 拉取覆盖率 map(按 `subject/subjectID/buildTarget`)
2. 通过 Git Provider 拉取对应 commit 的源码内容
3. 生成 `report-data.js`
4. 用 `@canyonjs/report-html` 的 `dist` 产物替换 `data/report-data.js`
5. 打包为 zip,写入 `artifact_zip`
最终下载后可直接打开 `index.html` 浏览报告。
---
## 五、落库指标(用于统计/看板)
生成完成时,Snapshot 会写入以下关键指标:
- `statements_covered`
- `statements_total`
- `changestatements_covered`
- `changestatements_total`
- `duration_ms`(报告生成耗时,毫秒)
百分比可以按需自行计算:
- 总体覆盖率:`statements_covered / statements_total`
- 变更覆盖率:`changestatements_covered / changestatements_total`
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/babel-plugin
}
size={"small"}
>
Source
一个用于检测 CI 环境变量的 Babel 插件。与 istanbuljs 配合使用,完成代码插桩。
## 使用方法
安装:
```sh
npm install --save-dev @canyonjs/babel-plugin
```
在 `babel.config.js` 中添加这些配置:
```js
module.exports = {
plugins: process.env.CI_COMMIT_REF_NAME === "test-coverage" ? ["istanbul", "canyon"] : [],
// 注意插件顺序:canyon 插件应在 istanbul 插件之后
};
```
它做了两件事:
1. 检测 CI 流水线变量
2. 收集参与编译的文件覆盖率初始数据
## 配置
babel.config.js
```js
module.exports = {
plugins: [
"istanbul",
[
"canyon",
{
// #region == Step 2: CI Provider auto-detection, generally no manual configuration needed, see Support Provider documentation for details
repoID: "230614", // Repository ID
sha: "xxxxxxxxx", // Git Commit SHA
// #endregion
// #region == Step 4: Separate hit and map data (optional)
keepMap: false, // Keep coverage map, optional, default is false.
// #endregion
// #region == Step 5: Other configuration (optional)
instrumentCwd: "/path/to", // Instrumentation working directory, may need manual configuration in multi-repo mode
provider: "gitlab", // Source code provider (optional), default is gitlab
include: ["src/**/*.ts", "src/**/*.tsx"], // Include glob patterns (optional)
exclude: ["**/*.test.ts", "**/*.spec.ts"], // Exclude glob patterns (optional)
extensions: [".ts", ".tsx", ".js"], // Extensions participating in include/exclude matching (optional)
// #endregion
},
],
],
};
```
| 配置项 | 描述 | 是否必填 | 默认值 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | ------------- |
| repoID | 仓库 ID | 一般无需手动配置(自动检测 CI Provider) | None |
| sha | Git 提交 SHA | 一般无需手动配置(自动检测 CI Provider) | None |
| branch | Git 仓库分支 | 一般无需手动配置(自动检测 CI Provider) | None |
| keepMap | 保留覆盖率地图,可选,默认为 true,当为 false 时,将生成 .canyon_output 文件 | 可选 | true |
| instrumentCwd | 插桩工作目录,在多仓库模式下可能需要手动配置 | 可选 | process.cwd() |
| provider | 源代码提供商(可选),默认为 gitlab | 可选 | gitlab |
| include | 需要插桩的文件匹配规则(glob),语义与 babel-plugin-istanbul 一致 | 可选 | [] |
| exclude | 不需要插桩的文件匹配规则(glob),语义与 babel-plugin-istanbul 一致 | 可选 | [] |
| extensions | include/exclude 匹配时使用的扩展名列表 | 可选 | .js/.ts/... |
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/cli
}
size={"small"}
>
Source
Canyon CLI 是一个用于将覆盖率数据上传到 Canyon 服务器的命令行工具。它从本地 `.canyon_output` 目录读取并聚合覆盖率数据,然后将其上传到配置的 Canyon 服务器。
## 安装
通过 npm 安装:
```bash
npm install -g @canyonjs/cli
```
或者从 [GitHub Release](https://github.com/canyon-project/canyon/releases) 下载最新版本。
## 特性
- 从本地 `.canyon_output` 目录读取并聚合覆盖率数据
- 支持按路径过滤覆盖率文件
- 支持基于场景的覆盖率报告
- 自动检测 CI 环境变量(GitLab CI)
- 将多个覆盖率文件合并为单个报告
## 使用方法
### 基本用法
```bash
canyon upload --dsn=http://your-canyon-server/api/coverage/client
```
使用 `--scene` 添加场景元数据以过滤覆盖率数据:
```bash
canyon upload --dsn=$DSN \
--scene='{"source":"automation","type":"e2e","env":"test"}'
```
## 命令选项
| 选项 | 描述 | 是否必填 | 默认值 |
| ------------------ | ---------------------------- | -------- | --------------- |
| `--dsn` | Canyon 服务器端点 URL | 是 | None |
| `--repo_id` | 仓库 ID | 否\* | `CI_PROJECT_ID` |
| `--sha` | Git 提交 SHA | 否\* | `CI_COMMIT_SHA` |
| `--provider` | 源代码提供商 (gitlab/github) | 否 | `gitlab` |
| `--build_target` | 构建目标标识符 | 否 | 空字符串 |
| `--instrument_cwd` | 插桩工作目录 | 否 | `process.cwd()` |
| `--filter` | 按路径子串过滤覆盖率文件 | 否 | None |
| `--scene` | JSON 格式的场景元数据 | 否 | None |
| `--debug` | 启用调试模式 | 否 | `false` |
\* 如果未提供,将从 CI 环境变量自动检测
## 示例:GitLab CI 集成
```yaml
coverage:
stage: test
script:
- npm test
- canyon upload --dsn=$CANYON_DSN --scene='{"type":"unit","env":"ci"}'
artifacts:
paths:
- .canyon_output/
expire_in: 1 week
```
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/collect
}
size={"small"}
>
Source
`@canyonjs/collect` 是一个用于 Web 站点的覆盖率收集脚本包。它通常以脚本形式注入页面,在浏览器运行阶段按需采集覆盖率并上报到 Canyon 服务端。
## 安装
```bash
npm install @canyonjs/collect
```
## 适用场景
- 需要在真实浏览器环境采集覆盖率
- 通过页面脚本方式接入,而不是 CLI 上传
- 与 E2E 自动化流程联动,按场景上报覆盖率
## 代码位置
- 包目录:`tools/collect`
- 入口:`tools/collect/src/index.ts`
## 说明
当前该包以脚本采集场景为主,具体接入方式可结合业务页面注入方案和测试框架流程实现。
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/git-diff
}
size={"small"}
>
Source
@canyonjs/git-diff 是一个用于在 CI/CD 流水线中检测代码变更并生成 Git Diff 内容的 NPM 包
## 使用
### 在 CI/CD 流水线中使用
```bash
npx @canyonjs/git-diff
```
### 输出
在当前目录下生成diff.txt文件
```text filename="diff.txt"
diff --git a/fixtures/sum.js b/fixtures/sum.js
index 1234567..abcdefg 100644
--- a/fixtures/sum.js
+++ b/fixtures/sum.js
@@ -1,3 +1,5 @@
export function sum(a, b) {
+ // 新增的注释
return a + b;
+ // 另一个新增的注释
}
diff --git a/fixtures/add.js b/fixtures/add.js
index 1234567..abcdefg 100644
--- a/fixtures/add.js
+++ b/fixtures/add.js
@@ -1,3 +1,6 @@
export function sum(a, b) {
+ // 新增的注释行
+ console.log('计算中...');
return a + b;
+ // 结束注释
}
```
## 原理
@canyonjs/git-diff 通过检测当前流水线环境变量,确定 CI/CD 提供商,然后根据提供商的特定环境变量判断当前是 MR 还是普通 Commit,从而确定需要进行 Git Diff 比较的 commit 范围。
### 检测逻辑
1. **平台检测**:首先检测 CI/CD 平台特定的环境变量,确定当前运行环境
2. **事件类型判断**:根据平台特定的环境变量判断是 MR/PR 还是普通 Commit
3. **分支确定**:
- 对于 MR/PR:获取源分支和目标分支
- 对于普通 Commit:获取当前分支和上一个 Commit
4. **Diff 生成**:根据确定的分支或 Commit 范围生成 Git Diff 内容
### Git Diff 范围确定
| 事件类型 | 比较范围 | 命令示例 |
| ----------- | ---------------------------- | --------------------------------------- |
| MR/PR | 源分支 vs 目标分支 | `git diff origin/target..origin/source` |
| 普通 Commit | 当前 Commit vs 上一个 Commit | `git diff HEAD~1..HEAD` |
import { Button } from "antd";
# @canyonjs/playwright
}
size={"small"}
>
Source
为 Playwright 测试提供浏览器端 JavaScript 覆盖率收集。
import { GithubOutlined } from "@ant-design/icons";
### 前置条件
- 你的应用程序必须经过 istanbuljs 代码插桩
---
## 安装
```bash npm2yarn
npm install --save-dev @canyonjs/playwright
```
## 使用
扩展 Playwright 的 context 功能以支持覆盖率收集:
```js filename="playwright.config.ts"
import { test as baseTest } from "@playwright/test";
import { createCoverageContextFixture } from "@canyonjs/playwright";
export const test = baseTest.extend({
context: createCoverageContextFixture({
outputDir: ".canyon_output",
}),
});
export const expect = test.expect;
```
在你的 E2E 测试文件中使用此 `test` 导出,而不是 Playwright 的默认 `test`。
---
### 这行代码做了什么
- 从已插桩的应用程序中收集 **浏览器端 JavaScript 覆盖率**
- 在以下时机刷新覆盖率数据:
- 页面卸载时
- 测试拆卸时
- 将覆盖率文件写入指定的输出目录
- 通过扩展 Playwright fixtures 在 **用户代码层面** 完全实现
> 此集成 **不会** 修补或替换 Playwright 的测试运行器。
---
### 输出
覆盖率文件将被写入:
```text
.canyon_output/
├─ coverage-1700000000000.json
├─ coverage-1700000001234.json
└─ ...
```
这些文件可以:
- 与 `nyc` 合并
- 上传到 Canyon 平台
- 通过 Canyon CLI 工具进行后处理
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/report
}
size={"small"}
>
Source
一个覆盖率报告器,适配[istanbul-reports](https://github.com/istanbuljs/istanbuljs/tree/main/packages/istanbul-reports/lib)接口
## 安装
```bash npm2yarn
npm install --save-dev @canyonjs/report
```
## 使用
该报告器适配istanbuljs报告器接口,所以支持各种常见的测试框架的覆盖率报告生成,以vitest为例
```js {7}
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["@canyonjs/report"],
},
},
});
```
## 配置
> [!IMPORTANT]
>
> 支持传入git diff,生成变更代码覆盖率报告!默认读取diff.txt文件内容。在CI中集成请查看 [@canyonjs/git-diff](./git-diff)。
```js {7}
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: [
"json",
[
"@canyonjs/report",
{
diff: diff, // 支持git diff字符串、路径
},
],
],
},
},
});
```
## 输出
- index.html,单个html文件,易于传输/部署或直接在任何位置打开。
- data/report-data.js,对应压缩过的覆盖率数据及源码内容。
- js、css等静态资源。
## 例子
| 测试框架 | 示例链接 |
| ------------- | -------------------------------------------------------------------------------------------------------------- |
| `Vitest` | [report-html-vitest](https://github.com/canyon-project/canyon/tree/main/examples/report-html-vitest) |
| `Jest` | [report-html-jest](https://github.com/canyon-project/canyon/tree/main/examples/report-html-jest) |
| `RSTest` | [report-html-rstest](https://github.com/canyon-project/canyon/tree/main/examples/report-html-rstest) |
| `Mocha` | [report-html-mocha](https://github.com/canyon-project/canyon/tree/main/examples/report-html-mocha) |
| `Test Runner` | [report-html-test-runner](https://github.com/canyon-project/canyon/tree/main/examples/report-html-test-runner) |
import { GithubOutlined } from "@ant-design/icons";
import { Button } from "antd";
# @canyonjs/vite-plugin
}
size={"small"}
>
Source
`@canyonjs/vite-plugin` 是 Vite 场景下的 Canyon 插桩插件,内部直接调用 `@canyonjs/babel-plugin` 完成核心处理逻辑。
因此你只需要关注 Vite 接入方式;参数语义、CI 自动检测等能力与 `@canyonjs/babel-plugin` 一致。
## 安装
```sh
npm install -D @canyonjs/vite-plugin vite-plugin-istanbul
```
## 使用方法
建议仅在生产构建中启用:
```ts
import { defineConfig } from "vite";
import istanbulPlugin from "vite-plugin-istanbul";
import canyonVitePlugin from "@canyonjs/vite-plugin";
const isProduction = process.env.NODE_ENV === "production";
export default defineConfig({
plugins: [
...(isProduction
? [
istanbulPlugin({
forceBuildInstrument: true,
}),
canyonVitePlugin({
repoID: "9050",
sha: "xxxxx",
provider: "gitlab",
ci: true,
keepMap: true,
}),
]
: []),
],
});
```
## 配置项
`@canyonjs/vite-plugin` 直接透传以下配置给 `@canyonjs/babel-plugin`:
- `repoID`
- `sha`
- `provider`
- `ci`
- `instrumentCwd`
- `keepMap`
不重复展开参数说明,详情请直接查看 [@canyonjs/babel-plugin](./babel-plugin) 文档。
import { Steps, Callout, Cards } from "nextra/components";
# 快速开始
与单元测试不同,端到端测试会启动浏览器。我们的目标是在浏览器内收集覆盖率数据,因此复杂度相对较高。以下是一般流程:
- 暴露浏览器全局变量,在适当的时机将覆盖率数据写入本地
- UI自动化完成后,使用[Tools CLI](/docs/ecosystem/tools-cli)读取本地`.canyon_output`目录中的覆盖率数据并聚合上报
- 将caseID与[Report ID](/docs/core-concepts/report-id)关联,确保用例可追溯
## 框架
## 注意事项
**服务端渲染(SSR)框架的覆盖率支持范围** 以 Next.js 为代表的服务端渲染(SSR)框架,Canyon
当前仅支持客户端代码的覆盖率统计。 **说明:** SSR 框架同时包含: - 服务端代码(Node.js / Edge
Runtime) - 客户端代码(Browser Runtime) Canyon 的覆盖率采集与变更覆盖率计算: -
依赖浏览器侧执行结果 - 依赖 window.__coverage__ 等客户端覆盖率模型 **因此在 SSR 场景下:** ✅
**支持:** - Client Components - 浏览器中执行的页面逻辑、事件处理、Hooks、客户端工具函数等 ❌
**不支持(不计入覆盖率):** - getServerSideProps / getStaticProps - Server Components - API
Routes(/api/*) - Edge / Node.js 服务端逻辑
import { Steps } from "nextra/components";
import { Button } from "antd";
import { GithubOutlined } from "@ant-design/icons";
import SetupReactPlaywrightPython from "../../../components/setup-react-playwright-python.mdx";
# Playwright Python
在 [Playwright Python](https://playwright.dev/python/docs/intro)(pytest-playwright)中集成覆盖率收集,生成 `.canyon_output` 供后续报告生成或上报使用。
}
size={"small"}
>
Demo
## 使用该方案的优势
对于端到端测试而言,该方案的核心优势包括:
- **支持现代化 Web 应用**:现代化的 Web 应用都会经过 JS bundle 打包,Playwright 提供的默认方案只能对原生 JS 等简单场景使用 V8 收集覆盖率,而该方案通过 Babel 插桩可以准确收集打包后代码的覆盖率
- **确保变更代码覆盖率完整性**:`@canyonjs/babel-plugin` 会在构建时生成一份初始覆盖率数据,确保在 CI 集成时,能够收集到因懒加载而可能丢失的变更代码覆盖率
- **与 pytest-playwright 无缝集成**:通过 pytest plugin 扩展 `page` fixture,在用户代码层面完全实现,不会修补或替换 pytest 测试运行器
- **灵活的覆盖率报告**:`.canyon_output` 中的覆盖率数据可配合 [Tools CLI](/docs/ecosystem/tools-cli) 聚合上报,或用于本地生成 HTML 报告
> [!TIP]
>
> 该方案特别适合使用现代构建工具(如 Vite、Webpack)的 React、Vue 等框架项目,能够准确追踪打包后代码的覆盖率情况。若你偏好 Python 编写 E2E 测试,可选用 [canyonjs-playwright-python](https://github.com/canyon-project/canyonjs-playwright-python)。
## 以新项目的形式开始
import { Steps } from "nextra/components";
import { Button } from "antd";
import { GithubOutlined } from "@ant-design/icons";
import SetupReactPlaywright from "../../../components/setup-react-playwright.mdx";
# Playwright
在 [playwright](https://playwright.dev/docs/intro) 中集成覆盖率收集与报告生成
}
size={"small"}
>
Demo
## 使用该方案的优势
对于端到端测试而言,该方案的核心优势包括:
- **支持现代化 Web 应用**:现代化的 Web 应用都会经过 JS bundle 打包,Playwright 提供的默认方案只能对原生 JS 等简单场景使用 V8 收集覆盖率,而该方案通过 Babel 插桩可以准确收集打包后代码的覆盖率
- **确保变更代码覆盖率完整性**:`@canyonjs/babel-plugin` 会在构建时生成一份初始覆盖率数据,确保在 CI 集成时,能够收集到因懒加载而可能丢失的变更代码覆盖率
- **与 Playwright 无缝集成**:通过扩展 Playwright 的 context fixture,在用户代码层面完全实现,不会修补或替换 Playwright 的测试运行器
- **灵活的覆盖率报告**:支持本地生成 HTML 报告,也可以上传到 Canyon 服务器进行统一管理和分析
> [!TIP]
>
> 该方案特别适合使用现代构建工具(如 Vite、Webpack)的 React、Vue 等框架项目,能够准确追踪打包后代码的覆盖率情况。
## 以新项目的形式开始
import { Steps, Callout } from "nextra/components";
# 首次获取覆盖率数据
> [!NOTE]
>
> Canyon 提供了[常见框架的安装指南](/docs/installation/getting-started),帮助您快速上手。
按照以下步骤完成您的首次覆盖率数据报告:
## 新项目开始
### 安装
Babel 是前端工程化和模块化开发中不可或缺的工具。对于 Babel 项目,您只需安装两个 Babel 插件即可快速开始。
```sh npm2yarn
npm install babel-plugin-istanbul @canyonjs/babel-plugin -D
```
将 [`istanbul`](https://www.npmjs.com/package/babel-plugin-istanbul) 和 [`canyon`](https://www.npmjs.com/package/@canyonjs/babel-plugin) 插件添加到您的 [Babel 配置文件](https://babeljs.io/docs/config-files#configuration-file-types)中:
```js filename="babel.config.js" {3,4} copy
module.exports = {
plugins: [
"istanbul",
"@canyonjs",
// 注意插件顺序:canyon 插件应在 istanbul 插件之后
],
};
```
### 验证
配置完成后,启动您的项目并在控制台中打印 **window.\_\_coverage\_\_**。如果输出与下图匹配,则代码插桩成功。

### 更新 Babel 插件激活条件
**在 CI 阶段,我们需要控制插件激活条件,以防止在生产分支中进行插桩。**
```js {2,3} copy
module.exports = {
plugins:
process.env.CI && process.env.CI_COMMIT_REF_NAME !== "release" ? ["istanbul", "@canyonjs"] : [],
};
```
### CI中收集初始覆盖率数据
在 CI 环境中,您可以通过运行测试用例来收集初始覆盖率数据。这有助于建立代码覆盖率的基准线。
以下是一个 GitLab CI/CD 配置示例:
```yaml filename=".gitlab-ci.yml" copy
pages:
image: node:20
stage: deploy
script:
- npm run build
- npx @canyonjs/cli upload --dsn=https://app.canyonjs.io/api/coverage/map/init
only:
- main
```
### 报告覆盖率数据
等待 CI 完成后,将页面部署到测试环境。为了收集人工测试的覆盖率数据,您需要在 HTML 文件中引入 Canyon 收集脚本:
```html copy
```
配置完成后,随后的测试就会不断收集上报覆盖率数据到服务端。
# 介绍
## 概述
Canyon是一个 JavaScript 代码覆盖率收集平台。我们解决了开发人员和 QA 工程师在端到端测试期间收集测试用例代码覆盖率时遇到的困难。它由三个主要组件组成:
- 一系列[插件](https://github.com/canyon-project/canyon/tree/main/plugins),负责适配各种 CI 提供商并检测环境变量。
- 一个[前后端一体化应用](https://github.com/canyon-project/canyon/tree/main/app)(Node.js + React),负责覆盖率数据的采集处理、后端 API 与前端报告展示。

## 为什么选择 Canyon?
Canyon 通过在编译阶段分离 hit 和 map 数据,高效处理 UI 自动化测试生成的大量覆盖率数据。
此外,Canyon 适配常见的 CI 提供商,能够在构建阶段插入探针代码,并在 UI 自动化测试期间收集和报告覆盖率数据。
这使开发团队能够**准确地**、**实时地**了解当前 UI 自动化测试中**每个测试用例**的覆盖情况,从而更好地评估和提高代码质量。
### 特性
- [准确高效](/docs/core-concepts/separate-hit-and-map):通过分离 hit 和 map,在编译时生成初始覆盖率数据,准确高效地收集覆盖率信息
- [SourceMap](/docs/core-concepts/restore-source-code-coverage):准确还原源代码覆盖率
- [构建工具](/docs/installation/getting-started):为 Next.js、Vite、Webpack 等常用构建工具提供覆盖率解决方案
- [自动化框架](/docs/end-to-end-testing/getting-started):为常见 UI 自动化框架提供集成方案
- [CI 提供商](/docs/reference/provider):适配 GitHub Actions、GitLab Runner 等常见 CI 提供商,自动检测 CI 环境变量
### 自托管
如果您想完全控制自己的覆盖率数据和测试数据,可以在自己的基础设施上[自托管](/docs/self-host/community-edition/prerequisites) Canyon。
## 生态系统
| 项目 | 状态 | 描述 |
| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------- |
| [@canyonjs/babel-plugin] | [![@canyonjs/babel-plugin-status]][@canyonjs/babel-plugin-package] | 用于检测流水线环境变量的 Babel 插件 |
| [@canyonjs/cli] | [![@canyonjs/cli-status]][@canyonjs/cli-package] | 扫描本地 `.canyon_output` 数据并上传到服务器 |
| [@canyonjs/collect] | [![@canyonjs/collect-status]][@canyonjs/collect-package] | 用于网站自动收集覆盖率数据的脚本包 |
[@canyonjs/babel-plugin]: /docs/ecosystem/babel-plugin
[@canyonjs/cli]: /docs/ecosystem/cli
[@canyonjs/collect]: /docs/ecosystem/collect
[vite-plugin-canyon]: https://github.com/canyon-project/vite-plugin-canyon
[@canyonjs/babel-plugin-status]: https://img.shields.io/npm/v/@canyonjs/babel-plugin.svg
[@canyonjs/cli-status]: https://img.shields.io/npm/v/@canyonjs/cli.svg
[@canyonjs/collect-status]: https://img.shields.io/npm/v/@canyonjs/collect.svg
[vite-plugin-canyon-status]: https://img.shields.io/npm/v/vite-plugin-canyon.svg
[@canyonjs/babel-plugin-package]: https://npmjs.com/package/@canyonjs/babel-plugin
[@canyonjs/cli-package]: https://npmjs.com/package/@canyonjs/cli
[@canyonjs/collect-package]: https://npmjs.com/package/@canyonjs/collect
[vite-plugin-canyon-package]: https://npmjs.com/package/babel-plugin-vite
## 架构
下图显示了 Canyon 及其生态系统的一些组件的架构:
1. Babel 插件在 CI/CD 流水线中完成代码插桩。
2. 项目部署到测试环境,用户可以选择 UI 自动化或手动测试来触发插桩代码探针。
3. 生成的覆盖率数据报告到 Canyon 服务器。
4. Canyon 服务器消费数据,并结合 GitLab 提供的源代码信息生成覆盖率报告。
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#FFF7ED",
"primaryBorderColor": "#FB923C",
"lineColor": "#9CA3AF",
"fontFamily": "Inter, system-ui"
}
}}%%
flowchart LR
classDef client fill:#EEF2FF,stroke:#6366F1,color:#1E1B4B;
classDef test fill:#F1F5F9,stroke:#94A3B8,color:#020617;
classDef core fill:#FFF7ED,stroke:#FB923C,color:#7C2D12;
classDef storage fill:#ECFEFF,stroke:#06B6D4,color:#083344;
classDef infra fill:#F0FDF4,stroke:#22C55E,color:#14532D;
classDef external fill:#FAFAFA,stroke:#D1D5DB,color:#111827;
%% Clients
UI[UI Automation]
WebUI[Canyon Web UI]
API[API Client]
class UI,WebUI,API client
%% Test
Test[Test Environment]
Pipeline[CI / CD Pipeline]
class Test,Pipeline test
UI --> Test
Pipeline --> Test
%% Canyon Core
subgraph Canyon["Canyon Server"]
MQ[Message Queue]
DB[(Postgres)]
HTTP[HTTP Server]
end
class MQ,HTTP core
class DB storage
Test --> MQ
MQ --> DB
DB --> HTTP
%% Infra
subgraph Deploy["Service Deployment"]
K8s[Kubernetes]
Node[Node.js]
end
class K8s,Node infra
Canyon --> Deploy
%% External
GitLab[GitLab]
class GitLab external
Canyon -.-> GitLab
WebUI -.-> HTTP
API -.-> HTTP
```
import { Cards } from "nextra/components";
# 快速开始
如何将 Canyon 插件安装到您的应用程序中。
> [!NOTE]
>
> 当前推荐使用 `@canyonjs/babel-plugin` 与 `@canyonjs/vite-plugin`。更多插件请查看[插件目录](https://github.com/canyon-project/canyon/tree/main/plugins)。
## 框架
# React Native
import { Steps } from "nextra/components";
安装和配置 React Native。
### 创建项目
React Native [文档](https://reactnative.dev/)
### 添加 Canyon 和配置
> [!TIP]
>
> 您需要了解 [react-native-babel-preset](https://reactnative.dev/docs/javascript-environment#javascript-syntax-transformers)。
```sh npm2yarn copy
npm install babel-plugin-istanbul @canyonjs/babel-plugin -D
```
### 配置 Babel 插件
```js filename="babel.config.js" {3} copy
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: ["istanbul", "@canyonjs"],
};
```
### 完成了
继续查看[首次获取覆盖率数据 - 验证](/docs/getting-started/first-coverage#%E6%A3%80%E6%9F%A5)。
# Vite
import { Steps } from "nextra/components";
安装和配置 Vite。
### 创建项目
首先,使用以下命令创建一个新的 React 项目 `vite`。
```sh npm2yarn copy
npm create vite@latest
```
### 添加 Canyon 和配置
```sh npm2yarn copy
npm install -D @canyonjs/vite-plugin vite-plugin-istanbul
```
### 配置 Vite 插件
```ts filename="vite.config.ts" copy
import { defineConfig } from "vite";
import istanbulPlugin from "vite-plugin-istanbul";
import canyonVitePlugin from "@canyonjs/vite-plugin";
const isProduction = process.env.NODE_ENV === "production";
// https://vite.dev/config/
export default defineConfig({
plugins: isProduction
? [
istanbulPlugin({
forceBuildInstrument: true,
}),
canyonVitePlugin({
repoID: "your-repo-id",
sha: "your-commit-sha",
provider: "gitlab",
ci: true,
keepMap: true,
}),
]
: [],
});
```
### 完成了
继续查看[首次获取覆盖率数据 - 验证](/docs/getting-started/first-coverage#%E6%A3%80%E6%9F%A5)。
# 安装与构建
学习如何在本机安装并启动 Canyon 社区版(Node.js 部署方式)。
## 1. 克隆仓库并进入 app
```bash
git clone https://github.com/canyon-project/canyon
cd canyon/app
```
## 2. 安装依赖
```bash
pnpm install
```
## 3. 配置环境变量
创建 `app/.env`(可从现有 `.env` 模板复制),至少包含:
```bash
DATABASE_URL=postgres://username:password@host:5432/dbname
```
## 4. 生成并执行建表 SQL
在 `app` 目录执行:
```bash
pnpm run migrate:sql
```
该命令会生成 `app/schema.sql`。然后手动执行该 SQL(示例):
```bash
psql "postgres://username:password@host:5432/dbname" -f schema.sql
```
## 5. 启动服务
开发模式:
```bash
pnpm dev
```
生产模式(可选):
```bash
pnpm build
pnpm start
```
## 6. 访问
默认访问地址:
- 开发模式(`pnpm dev`):
- 前端页面:`http://localhost:3000`
- API:`http://localhost:3000/api`
- Swagger UI:`http://localhost:3000/api/ui`
- OpenAPI JSON:`http://localhost:3000/api/doc`
- 生产模式(`pnpm start`):
- 前端页面:`http://localhost:8080`
- API:`http://localhost:8080/api`
- Swagger UI:`http://localhost:8080/api/ui`
- OpenAPI JSON:`http://localhost:8080/api/doc`
# 前置条件
本文介绍以 **Node.js + PostgreSQL** 方式部署 Canyon 社区版所需的前置条件(不使用 Docker)。
## 运行环境
- 操作系统:Linux / macOS / Windows(可正常运行 Node.js 与 PostgreSQL)
- Node.js:建议 `20+`
- pnpm:建议 `9+`
- PostgreSQL:建议 `14+`
## 安装 Node.js 与 pnpm
先安装 Node.js,再安装 pnpm。
- [Node.js 下载地址](https://nodejs.org/en/download)
- [pnpm 安装指南](https://pnpm.io/installation)
安装完成后可验证:
```bash
node -v
pnpm -v
```
## 准备 PostgreSQL
Canyon 使用 PostgreSQL 存储数据,请准备好可访问的数据库并记录连接串:
```text
postgres://username:password@host:5432/dbname
```
后续在 `app/.env` 中通过 `DATABASE_URL` 使用该连接串。
# 配置与访问
学习如何配置和访问 Canyon 社区版。
## 1. 初始化 Git 提供商配置(GitLab)
如果你使用 GitLab,请先导入 `app/prisma/import-infra.sql` 中的配置到数据库。
该文件默认包含两项:
- `GITLAB_BASE_URL`
- `GITLAB_PRIVATE_TOKEN`
执行前请先将 `GITLAB_PRIVATE_TOKEN` 修改为你自己的 Token。
示例:
```bash
cd app
psql "postgres://username:password@host:5432/dbname" -f prisma/import-infra.sql
```
## 2. 启动并访问系统
启动服务后,打开:
- 开发模式:`http://localhost:3000`
- 生产模式:`http://localhost:8080`
API 文档入口:
- Swagger UI:`/api/ui`
- OpenAPI JSON:`/api/doc`
可在页面中查看仓库、commit、compare 及覆盖率报告能力。
## 3. 重要说明
- 当前社区版文档优先覆盖 GitLab 场景。
- 如果你使用自建 GitLab,请将 `GITLAB_BASE_URL` 改为你的实例地址。
- 如果 Token 权限不足,会影响 compare、源码拉取、快照生成等能力。
CANYON
更准确的 JavaScript 代码覆盖率数据收集
Canyon 通过简单的 Babel 配置实现 JavaScript 代码覆盖率收集和实时报告生成,解决了端到端测试中的覆盖率收集难题。
[快速开始](/docs/getting-started/introduction) · [GitHub
仓库](https://github.com/canyon-project/canyon)
## 特性
Canyon 专为 JavaScript 端到端测试覆盖率收集而设计,具有以下特性:
- [准确高效](/docs/core-concepts/separate-hit-and-map):通过分离 hit 和 map 数据,在编译时生成初始覆盖率数据,准确高效地收集覆盖率信息
- [SourceMap](/docs/core-concepts/restore-source-code-coverage):准确还原源代码覆盖率
- [构建工具](/docs/installation/getting-started):为 Next.js、Vite、Webpack 等常用构建工具提供覆盖率解决方案
- [自动化框架](/docs/end-to-end-testing/getting-started):为常见 UI 自动化框架提供集成方案
- [CI 提供商](/docs/reference/provider):适配 GitHub Actions、GitLab Runner 等常见 CI 提供商,自动检测 CI 环境变量
## 社区
Canyon 由 [travzhang](https://github.com/travzhang) 创建。
关注 [@wr_zhang25](https://twitter.com/wr_zhang25) 获取项目最新动态。
欢迎加入 [GitHub Discussions](https://github.com/canyon-project/canyon/discussions)!
# License
Canyon is licensed under the MIT License.
```text
MIT License
Copyright (c) Canyon Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```