How to monitor Cypress tests with Grafana Cloud

TL;DR · AI 摘要
通过Prometheus指标与Grafana Cloud集成,可实现Cypress测试的长期趋势监控,免费层级即可完成部署。
核心要点
- 使用Cypress插件钩子生成Prometheus指标并推送到Pushgateway
- Alloy工具负责从Pushgateway采集数据并写入Grafana Cloud Metrics
- 无需付费即可利用Grafana Cloud免费层级实现完整监控流水线
结构提纲
按章节快速跳转。
- §引言
说明Cypress测试监控的挑战及现有解决方案的局限性。
- ·技术架构
介绍通过Prometheus指标和Pushgateway实现数据持久化的机制。
- ›实施步骤
分步讲解如何配置Cypress插件生成指标并推送到监控系统。
- ·工具链
详细说明Alloy在数据采集和转发中的核心作用。
- ›部署方案
展示如何利用Grafana Cloud免费层级完成完整监控流水线。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- Cypress测试监控方案
- 数据采集
- Cypress插件钩子生成指标
- Prometheus Pushgateway存储
- 数据传输
- Alloy采集器转发
- 远程写入Grafana Cloud
- 可视化
- Grafana Cloud Metrics仪表盘
金句 / Highlights
值得收藏与分享的关键句。
Cypress测试失败或变慢时,单次作业难以发现趋势,需长期数据监控。
通过Prometheus Pushgateway解决Cypress短生命周期作业的数据持久化问题。
Alloy作为开源采集器,可自动抓取Pushgateway并写入Grafana Cloud Metrics。
Grafana Cloud免费层级包含远程写入端点和Prometheus服务。
如果您的 Cypress 测试套件中存在频繁失败或运行缓慢的测试,您会知道从单个作业中很难发现其中的规律。可能是某个测试用例变慢了,或者某个测试失败了,又或者整个套件整体变慢了。根本原因可能是应用程序中的缺陷、测试不稳定,或者其他因素。终端输出和 CI 日志可以告诉您单次运行发生了什么,但这无法帮助您发现更宏观的趋势——尤其是作业完成后这些数据就会丢失。
幸运的是,Cypress 是一个专为 Web 应用程序构建的前端自动化测试框架,它已经通过插件钩子暴露了您需要的所有信息。每个测试用例执行完成后,Cypress 会提供一个包含通过/失败数量、单个测试耗时和状态的结果对象。您只需将这些数据转换为指标并发送到持久化存储即可。
在本文中,您将学习如何通过在 Cypress 钩子中将这些结果转换为 Prometheus 指标,推送到 Prometheus Pushgateway,然后让 Alloy 从网关抓取数据并转发到 Grafana Cloud Metrics ——全程仅使用免费层级即可实现。
最终您将拥有一个按照以下架构运行的监控流水线:
%3Aquality(90)%2F&w=3840&q=75)
所需准备
本教程将与您现有的 Cypress 项目一起运行。开始之前,请确保您具备以下条件:
- 一个 Cypress 项目(本示例使用 Cypress 14.x)以及一个可编辑的 cypress.config.js 文件
- 一个 Prometheus Pushgateway。由于 Cypress 运行是短暂的批处理作业,无法直接被抓取——Pushgateway 会在运行之间保存指标,以便抓取器获取。在您的基础设施中设置 Prometheus Pushgateway
- Alloy,我们的开源采集器,用于抓取 Pushgateway 并远程写入到 Grafana Cloud
- 一个 Grafana Cloud 账户。免费层级包含 Grafana Cloud Metrics 和 Prometheus 远程写入端点。如果没有账户,可以在此处 here 注册
- 您的 Grafana Cloud 远程写入 URL、数字用户 ID 以及具有
metrics:write权限范围的访问策略令牌
从 Cypress 钩子中发出指标
1a. 实现功能的关键钩子
Cypress 插件在 Node.js 中运行,可以在 setupNodeEvents 中订阅生命周期事件。两个钩子为我们提供了所有必要的信息:
before:run在任何测试用例运行前触发一次。我们使用它为整个测试套件生成一个唯一的run_id,并清除上一次运行留下的状态。在这个示例中,run_id是测试套件启动时间的 Unix 时间戳。
after:spec在每个测试文件运行结束后触发,并接收该测试文件的结果对象。测试的数量、状态和持续时间都存储在这里,因此我们在此处构建并推送指标。
// cypress.config.js
module.exports = defineConfig({
e2e: {
setupNodeEvents(on) {
on("before:run", () => {
runEpoch = String(Date.now());
specMetrics.clear();
});
on("after:spec", async (spec, results) => {
try {
await pushSpecToPrometheus(spec, results);
} catch (err) {
console.error(
`Prometheus push failed for ${spec.relative || spec.name}:`,
err.message
);
}
});
},
},
});推送操作被包裹在 try/catch 中,这样监控服务的中断不会导致原本通过的测试变成失败——遥测数据是副作用,不应成为测试通过的条件。其次,run_id 在 before:run 中仅设置一次,所有测试用例都会复用这个 ID,因此同一套件执行的所有测试用例共享一个标识符,后续可以按运行批次进行分组。
1b. 将 Cypress 结果对象转换为 Prometheus 指标
Cypress 传递给 after:spec 的结果对象包含一个统计块(通过数、失败数、待定数、跳过数、总测试数和持续时间毫秒数),以及一个测试数组,每个测试包含标题、状态和持续时间。我们将这些信息映射到一组指标中,使用标签按测试用例、运行批次和单个测试进行分类:
function metricLinesForSpec(specName, runId, githubRunId, results) {
const stats = results.stats || {};
const passed = stats.passes ?? 0;
const failed = stats.failures ?? 0;
const pending = stats.pending ?? 0;
const skipped = stats.skipped ?? 0;
const total = stats.tests ?? passed + failed + pending + skipped;
const durationSec = (stats.duration ?? 0) / 1000;
const labels = [
`spec="${escapeLabelValue(specName)}"`,
`run_id="${escapeLabelValue(runId)}"`,
`github_run_id="${escapeLabelValue(githubRunId)}"`,
].join(",");
const lines = [
`cypress_tests_total{${labels},result="passed"} ${passed}`,
`cypress_tests_total{${labels},result="failed"} ${failed}`,
`cypress_tests_total{${labels},result="pending"} ${pending}`,
`cypress_tests_total{${labels},result="skipped"} ${skipped}`,
`cypress_tests_run_total{${labels}} ${total}`,
`cypress_spec_duration_seconds{${labels}} ${durationSec}`,
`cypress_spec_success{${labels}} ${failed === 0 ? 1 : 0}`,
];
for (const test of results.tests || []) {
const testName = (test.title || []).join(" -- ") || "unknown";
const testLabels = `${labels},test="${escapeLabelValue(testName)}"`;
const state = test.state || "unknown";
const testDurationSec = (test.duration ?? 0) / 1000;
lines.push(
`cypress_test_success{${testLabels}} ${state === "passed" ? 1 : 0}`,
`cypress_test_duration_seconds{${testLabels},result="${escapeLabelValue(state)}"} ${testDurationSec}`
);
}
return lines.join("\n");
}每个系列都携带一组通用的标签 spec(哪个文件)、run_id(在 before:run 中生成的纪元时间戳)和 ci_run_id(当存在时的 CI 运行标识)——这样你可以将仪表板筛选到某个特定的 spec 或某个 CI 运行。最终会得到一组紧凑的指标:
cypress_tests_total:按 spec 统计测试结果(通过、失败、待定、跳过)的数量
cypress_tests_run_total:每个 spec 执行的总测试数
cypress_spec_duration_seconds:每个 spec 执行耗时
cypress_spec_success:如果 spec 没有失败则为 1,否则为 0
cypress_test_success和cypress_test_duration_seconds:在单个测试级别实现相同功能,使你能够跟踪某个易变测试随时间的变化趋势
1c. 推送到 Pushgateway
标准的 Prometheus 设置会定期抓取长期运行的服务。而 Cypress 运行正好相反。它是一个短暂运行的批处理任务,在任何抓取器到达之前就会终止。Prometheus Pushgateway 正是为此场景而设计:你的任务将指标推送到网关,网关保存这些指标,Prometheus(或 Alloy)按照自己的计划从网关抓取数据。
推送本身只是将 Prometheus 文本格式的指标以普通 HTTP POST 方式发送:
// cypress.config.js — 向 Pushgateway 推送一个累积的指标体async function pushSpecToPrometheus(spec, results) {
const baseUrl = process.env.PROMETHEUS_PUSHGATEWAY_URL;
if (!baseUrl) {
console.log(
"跳过 Prometheus 推送(设置 PROMETHEUS_PUSHGATEWAY_URL 以启用)。"
);
return;
}
const job = process.env.PROMETHEUS_JOB || "cypress";
const instance = process.env.PROMETHEUS_INSTANCE || "local";
const runId = runEpoch || (runEpoch = String(Date.now()));
const githubRunId = process.env.GITHUB_RUN_ID || "localrun";
const specName = spec.relative || spec.name || "unknown";
specMetrics.set(
specName,
metricLinesForSpec(specName, runId, githubRunId, results)
);
const url = new URL(
`/metrics/job/${encodeURIComponent(job)}/instance/${encodeURIComponent(instance)}`,
baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`
);
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "text/plain; charset=utf-8",
"ngrok-skip-browser-warning": "true",
},
body: buildBody(),
});
if (!res.ok) {
const detail = await res.text().catch(() => "");
throw new Error(
`Pushgateway 返回 ${res.status} ${res.statusText} for ${url.href}${detail ? `: ${detail}` : ""}`
);
}
console.log(
`已将 spec "${specName}" 的指标(run_id="${runId}", github_run_id="${githubRunId}")推送到 Pushgateway`
);
}路径中的 job 和 instance 构成了 Pushgateway 的分组键(此处为 job="cypress" 和 instance="local";可以根据运行套件的项目或模块分配任意值)。
由于我们把每个 spec 的指标累积到 specMetrics 映射中,并在每个 spec 之后重新 POST 完整的指标体,网关始终保存着套件的完整最新快照。而通过在 before:run 中清空该映射,可以确保每次新运行都从干净状态开始,而不是携带旧的 spec 数据。
整个集成都在 cypress.config.js 中实现,仅当设置了 PROMETHEUS_PUSHGATEWAY_URL 时才会激活,因此本地 Cypress 运行不会受到影响,除非你主动启用该功能:
# package.json scriptPROMETHEUS_PUSHGATEWAY_URL=http://localhost:9091 cypress run
Ship to Grafana Cloud with Alloy
现在指标已存储到 Pushgateway,但它是本地缓冲区,并非长期存储。Alloy 桥接了这两者:它从网关抓取数据,并远程写入到 Grafana Cloud Metrics。
// config.alloy — 抓取 Pushgateway 并发送到 Grafana Cloud Metricsprometheus.scrape "pushgateway" { targets = [ { "__address__" = coalesce(sys.env("PUSHGATEWAY_ADDRESS"), "localhost:9091") }, ] honor_labels = true metrics_path = "/metrics" scrape_interval = "60s" forward_to = [prometheus.remote_write.grafana_cloud.receiver]}prometheus.remote_write "grafana_cloud" { endpoint { url = sys.env("GRAFANA_CLOUD_RW_URL") basic_auth { username = sys.env("GRAFANA_CLOUD_USER") password = sys.env("GRAFANA_CLOUD_TOKEN") } } external_labels = { source = "cypress-pushgateway", }}
将 Grafana Cloud 凭据作为环境变量提供并启动 Alloy:
export GRAFANA_CLOUD_RW_URL=https://prometheus-prod-XX-XXX.grafana.net/api/prom/pushexport GRAFANA_CLOUD_USER=<your-user-id>export GRAFANA_CLOUD_TOKEN=<your-access-policy-token>alloy run config.alloy
Run tests and verify
启动 Pushgateway 和 Alloy,然后运行指向网关的测试套件:
# 1. Pushgateway(在运行之间保存指标)docker run -d -p 9091:9091 prom/pushgateway# 2. Alloy(抓取网关,发送到 Grafana Cloud)——参见第 2 部分alloy run config.alloy# 3. 启用推送的 Cypressnpm run test:prometheus
每个测试规范完成后,Cypress 会向 Pushgateway 记录类似 Pushed metrics for spec "login.cy.js" (run_id="1753100000000") 的日志。在抓取间隔内,这些时间序列会出现在 Grafana Cloud 中。
%3Aquality(90)%2F&w=3840&q=75)
从这里你可以构建一个仪表板,用于识别通过和失败次数的趋势、规范持续时间以及每个测试的成功率。你还可以设置 Grafana Alerting,当套件成功率下降或关键规范开始失败时通知你。
%3Aquality(90)%2F&w=3840&q=75)
基于收集的指标,我们可以可视化每个规范的执行时间以及单个测试的执行时间。
%3Aquality(90)%2F&w=3840&q=75)
在 CI 中运行
在 CI 环境中运行时,确保将环境变量注入到构建流程中,并验证 Docker 容器和 Alloy 的启动是否符合预期。对于持续集成系统(如 GitHub Actions、GitLab CI 或 Jenkins),可以将环境变量定义在配置文件中,并确保 Alloy 配置文件路径正确无误。
该机制在 CI 环境中同样适用,唯一不同的是指标的来源。
例如,在 GitHub Actions 工作流中,将 PROMETHEUS_PUSHGATEWAY_URL 设置为 runner 可访问的 Pushgateway,其余工作由 Cypress 完成:
# .github/workflows/main.yml (节选)
env:
PROMETHEUS_PUSHGATEWAY_URL: ${{ secrets.PROMETHEUS_PUSHGATEWAY_URL }}
# GITHUB_RUN_ID 由系统自动提供,并用作 ci_run_id 标签由于代码在检测到 GITHUB_RUN_ID 时会自动读取,每个 CI 运行都会被标记上对应的 ci_run_id,因此你可以直接从指标突变跳转到产生该指标的工作流运行记录。
%3Aquality(90)%2F&w=3840&q=75)
以下建议可帮助你节省时间:
- 使用 Pushgateway 而非抓取目标。Cypress 运行在几秒内就会完成;抓取工具永远无法捕获到它们。Pushgateway 是使短期任务可观测的缓冲区。
- 永远不要让遥测数据导致运行失败。用 try/catch 包裹推送操作。监控系统故障不应让原本通过的测试套件变成红色。
- 转义标签值。测试标题是自由文本,可能包含引号和换行符,会破坏 Prometheus 的文本格式。在写入指标前请先进行转义。
- 为每个测试套件设置唯一的
run_id。 在 before:run 阶段设置该值并在所有测试用例中复用,这是后续按运行批次进行分组和对比的基础。
Grafana Assistant 是在 Grafana Cloud 上开始使用指标、日志、追踪、仪表板等功能最简单的方式。我们提供慷慨的永久免费层级,并为所有使用场景准备了相应的方案。立即免费注册!
标签