` 或 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%`
# Compare、快照与变更覆盖率报告
通过「创建 Compare」得到的覆盖率视图具有 **[覆盖率累积](https://cdn.jsdelivr.net/gh/canyon-project/assets/docs/core-concepts/accumulative-coverage)** 特性:在 Base → Head 这一区间内,只要某段代码在任一已上报覆盖率的 commit 对应的测试里被执行过,就会在该对比结果中计为已覆盖。下文说明在控制台中的典型操作流程。
---
## 一、创建 Compare
- **Base**:作为对比起点的历史版本 **Commit SHA**(通常选分支公共祖先或合并目标)。
- **Head**:在 Commit 列表中 **已经上报过覆盖率** 的 **Commit SHA**。
填写并创建 Compare 后,系统会基于该区间聚合覆盖率数据。

---
## 二、创建快照
Compare 创建成功后,打开 **查看覆盖率报告** 即可看到基于当前区间的实时覆盖率。
当本轮测试结束后,可通过 **创建快照** 将当前视图固化为 **离线报告包**,便于归档、分享或与后续版本对照。快照概念详见 [快照(Snapshot)](https://cdn.jsdelivr.net/gh/canyon-project/assets/docs/core-concepts/snapshot)。

---
## 三、查看覆盖率报告
在快照列表中进入 **查看快照记录**,下载并解压报告包后打开其中的报告页面。
在报告中可通过 **Change Statements** 查看当前 Compare 下 **变更语句** 的覆盖情况(哪些变更行曾被测试命中)。

---
## 四、快速定位未覆盖代码块
在报告界面中结合变更视图与未覆盖高亮,可以快速缩小 **尚未被测试触达的代码块** 范围,用于补充用例或评审风险。

# 代码插桩
本章介绍前端测试中代码插桩的基本概念、常见插桩方式,以及在单元测试和端到端测试(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 等框架项目,能够准确追踪打包后代码的覆盖率情况。
## 以新项目的形式开始
# 最常问的问题
## 问题一:为什么 Compare 中的变更代码文件数量和 GitLab 不一致?
Compare 中统计的是 **Babel 参与编译并进入覆盖率计算链路** 的文件,例如 `js`、`jsx`、`ts`、`tsx` 等;而 GitLab 展示的是 Git diff 的文件变化结果。两者统计口径不同,因此数量可能不一致。
例如:
- 你在一次提交中改了 10 个文件,其中包含:
- 6 个 `ts/tsx` 业务文件
- 2 个 `md` 文档
- 1 个 `json` 配置
- 1 张 `png` 图片
- GitLab 的 compare 会显示 10 个变更文件。
- Compare 页面通常只会聚焦可编译并参与覆盖率计算的代码文件(如上面的 6 个 `ts/tsx`),所以看到的文件数可能是 6。
再比如:
- GitLab 中某些文件虽然发生了变更,但属于样式资源、静态资源、文档或未纳入 Babel 处理范围的文件。
- 这些文件不会进入覆盖率计算,因此不会计入 Compare 的变更代码文件统计。
如果你想快速判断“为什么少了几个文件”,可以按下面思路排查:
1. 先在 GitLab compare 中列出全部变更文件类型。
2. 过滤出 `js`、`jsx`、`ts`、`tsx` 等代码文件。
3. 再对照项目的 Babel/插桩配置,确认这些代码文件是否在处理范围内。
4. 对不在范围内的文件(如文档、图片、部分配置文件)视为口径差异导致的正常现象。
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)。
# istanbuljs 覆盖率数据字典
## 概述
istanbuljs 是一个JavaScript代码覆盖率的黄金标准,本数据字典描述了覆盖率数据的主要结构和字段。
## 主要数据结构
### 1. Coverage Object (覆盖率对象)
顶级覆盖率数据结构,包含所有文件的覆盖率信息。
```json
{
"path/to/file.js": {
// FileCoverage 对象
}
}
```
### 2. FileCoverage (文件覆盖率)
单个文件的完整覆盖率数据。
| 字段 | 类型 | 描述 |
|------|------|------|
| `path` | string | 文件的绝对路径 |
| `statementMap` | Object | 语句映射表 |
| `fnMap` | Object | 函数映射表 |
| `branchMap` | Object | 分支映射表 |
| `s` | Object | 语句执行计数 |
| `f` | Object | 函数执行计数 |
| `b` | Object | 分支执行计数 |
### 3. StatementMap (语句映射)
描述代码中每个语句的位置信息。
| 字段 | 类型 | 描述 |
|------|------|------|
| `start` | Location | 语句开始位置 |
| `end` | Location | 语句结束位置 |
### 4. FunctionMap (函数映射)
描述代码中每个函数的信息。
| 字段 | 类型 | 描述 |
|------|------|------|
| `name` | string | 函数名称(匿名函数为"(anonymous_N)") |
| `decl` | Location | 函数声明位置 |
| `loc` | Location | 函数体位置 |
| `line` | number | 函数声明行号 |
### 5. BranchMap (分支映射)
描述代码中每个分支的信息。
| 字段 | 类型 | 描述 |
|------|------|------|
| `loc` | Location | 分支位置 |
| `type` | string | 分支类型(if, switch, cond-expr, binary-expr) |
| `locations` | Array<Location> | 分支条件的所有可能位置 |
### 6. Location (位置信息)
描述源代码中的位置。
| 字段 | 类型 | 描述 |
|------|------|------|
| `start` | Position | 开始位置 |
| `end` | Position | 结束位置 |
### 7. Position (坐标位置)
源代码中的具体坐标。
| 字段 | 类型 | 描述 |
|------|------|------|
| `line` | number | 行号(从1开始) |
| `column` | number | 列号(从0开始) |
## 计数器对象
### 8. Statement Counters (语句计数器)
记录每个语句的执行次数。
```json
{
"0": 5, // 语句ID 0 执行了5次
"1": 0, // 语句ID 1 未执行
"2": 3 // 语句ID 2 执行了3次
}
```
### 9. Function Counters (函数计数器)
记录每个函数的调用次数。
```json
{
"0": 2, // 函数ID 0 被调用了2次
"1": 0, // 函数ID 1 未被调用
"2": 1 // 函数ID 2 被调用了1次
}
```
### 10. Branch Counters (分支计数器)
记录每个分支条件的执行次数。
```json
{
"0": [5, 2], // 分支ID 0: true分支5次,false分支2次
"1": [0, 3], // 分支ID 1: true分支0次,false分支3次
"2": [1, 1] // 分支ID 2: 两个分支各执行1次
}
```
## 分支类型说明
### Branch Types (分支类型)
| 类型 | 描述 | 示例 |
|------|------|------|
| `if` | if语句 | `if (condition) { ... }` |
| `switch` | switch语句 | `switch (value) { case 1: ... }` |
| `cond-expr` | 三元运算符 | `condition ? value1 : value2` |
| `binary-expr` | 逻辑运算符 | `a && b`, `x \|\| y` |
## 报告摘要数据
### 11. Summary (摘要信息)
每种覆盖率类型的统计摘要。
| 字段 | 类型 | 描述 |
|------|------|------|
| `total` | number | 总数量 |
| `covered` | number | 已覆盖数量 |
| `skipped` | number | 跳过数量 |
| `pct` | number | 覆盖率百分比 |
### 12. Coverage Summary (覆盖率摘要)
文件或整体的覆盖率摘要。
| 字段 | 类型 | 描述 |
|------|------|------|
| `lines` | Summary | 行覆盖率摘要 |
| `functions` | Summary | 函数覆盖率摘要 |
| `statements` | Summary | 语句覆盖率摘要 |
| `branches` | Summary | 分支覆盖率摘要 |
## 实际示例
### math.js 文件内容
```javascript
function add(a, b) {
return a + b;
}
function subtract(a, b) {
if (a > b) {
return a - b;
} else {
return b - a;
}
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
module.exports = { add, subtract, multiply, divide };
```
### 完整的FileCoverage示例
```json
{
"path": "/project/src/math.js",
"statementMap": {
"0": {"start": {"line": 1, "column": 0}, "end": {"line": 1, "column": 25}},
"1": {"start": {"line": 2, "column": 2}, "end": {"line": 2, "column": 13}},
"2": {"start": {"line": 5, "column": 0}, "end": {"line": 5, "column": 30}},
"3": {"start": {"line": 6, "column": 2}, "end": {"line": 6, "column": 25}},
"4": {"start": {"line": 8, "column": 4}, "end": {"line": 8, "column": 15}},
"5": {"start": {"line": 10, "column": 4}, "end": {"line": 10, "column": 15}},
"6": {"start": {"line": 13, "column": 0}, "end": {"line": 13, "column": 25}},
"7": {"start": {"line": 14, "column": 2}, "end": {"line": 14, "column": 13}},
"8": {"start": {"line": 17, "column": 0}, "end": {"line": 17, "column": 30}},
"9": {"start": {"line": 18, "column": 2}, "end": {"line": 18, "column": 25}},
"10": {"start": {"line": 20, "column": 4}, "end": {"line": 20, "column": 15}},
"11": {"start": {"line": 22, "column": 4}, "end": {"line": 22, "column": 15}},
"12": {"start": {"line": 25, "column": 0}, "end": {"line": 25, "column": 25}}
},
"fnMap": {
"0": {
"name": "add",
"decl": {"start": {"line": 1, "column": 9}, "end": {"line": 1, "column": 12}},
"loc": {"start": {"line": 1, "column": 20}, "end": {"line": 3, "column": 1}},
"line": 1
},
"1": {
"name": "subtract",
"decl": {"start": {"line": 5, "column": 9}, "end": {"line": 5, "column": 17}},
"loc": {"start": {"line": 5, "column": 25}, "end": {"line": 12, "column": 1}},
"line": 5
},
"2": {
"name": "multiply",
"decl": {"start": {"line": 13, "column": 9}, "end": {"line": 13, "column": 16}},
"loc": {"start": {"line": 13, "column": 24}, "end": {"line": 15, "column": 1}},
"line": 13
},
"3": {
"name": "divide",
"decl": {"start": {"line": 17, "column": 9}, "end": {"line": 17, "column": 15}},
"loc": {"start": {"line": 17, "column": 23}, "end": {"line": 24, "column": 1}},
"line": 17
}
},
"branchMap": {
"0": {
"loc": {"start": {"line": 6, "column": 2}, "end": {"line": 6, "column": 25}},
"type": "if",
"locations": [
{"start": {"line": 6, "column": 2}, "end": {"line": 6, "column": 25}},
{"start": {"line": 6, "column": 2}, "end": {"line": 6, "column": 25}}
]
},
"1": {
"loc": {"start": {"line": 18, "column": 2}, "end": {"line": 18, "column": 25}},
"type": "if",
"locations": [
{"start": {"line": 18, "column": 2}, "end": {"line": 18, "column": 25}},
{"start": {"line": 18, "column": 2}, "end": {"line": 18, "column": 25}}
]
}
},
"s": {"0": 1, "1": 1, "2": 1, "3": 1, "4": 3, "5": 2, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1},
"f": {"0": 5, "1": 3, "2": 2, "3": 1},
"b": {"0": [3, 2], "1": [1, 0]}
}
```
# 安装与构建
学习如何在本机安装并启动 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)!