ZITADEL 功能型 UI 端到端测试实战:用 Nx + Cypress 驱动 Management Console 用户旅程验证
【免费下载链接】zitadelZITADEL - Identity infrastructure, simplified for you.项目地址: https://gitcode.com/GitHub_Trending/zi/zitadel
本篇基于 ZITADEL 仓库中 tests/functional-ui/AGENTS.md 的官方 Agent 指南展开,介绍如何在这个 Nx monorepo 中运行针对 Management Console(管理控制台)的 Cypress 端到端测试。读完后你能掌握:五个已验证的 Nx 目标(打开交互式 Runner、跑测试套件、单独起测试库、单独起测试 API、停止测试基建)的完整用法,以及底层编排细节——临时 Postgres、API 构建启动链路、系统令牌签发机制和测试辅助层的实现原理,从而能够在本地可靠地复现与调试 Console 用户旅程测试。
一、functional-ui 测试的定位
tests/functional-ui 目录承载的是基于 Cypress 的端到端测试,聚焦于 Management Console 的用户流程(user journeys),其运行前提是一个“正在运行的 ZITADEL API”——即测试不是对着静态页面跑,而是对着一个完整的 Zitadel 后端(内嵌 Console 静态资源)进行真实交互验证。
指南中特别强调了两条工作流约束(Workflow Notes):
- 它是 Console 的主要测试路径:由于
@zitadel/console项目本身没有定义 Nx 的test目标,functional-ui 套件就是验证 Console 用户旅程的第一(也是目前唯一的)E2E 测试通道; - 强依赖 API 的构建/运行编排:这些测试依赖 API 的 build/run 编排,在没有同步更新本套件的前提下,应避免改动 API 的启动假设(startup assumptions)。
这一点从 tests/functional-ui/project.json 的依赖声明可以得到印证:run-api目标声明了dependsOn: ["@zitadel/api:build"],test目标则同时依赖run-db、run-api与@zitadel/api:build,且implicitDependencies指向了@zitadel/api和@zitadel/console两个项目。
二、已验证的 Nx 目标速查
指南中列出了五个已经过验证(Verified)的 Nx 目标,这是复现整个测试环境的核心操作面:
| 目标 | 命令 | 作用 |
|---|---|---|
| 打开交互式 Cypress Runner | pnpm nx run @zitadel/functional-ui:open | 启动 Cypress Test Runner 交互界面,便于逐个调试用例 |
| 运行测试套件 | pnpm nx run @zitadel/functional-ui:test | 完整跑一次 functional UI(Console)测试 |
| 仅启动测试数据库 | pnpm nx run @zitadel/functional-ui:run-db | 启动一个本地临时(ephemeral)Postgres |
| 仅启动测试 API | pnpm nx run @zitadel/functional-ui:run-api | 构建并运行内嵌 Management Console 的 Zitadel API |
| 停止测试基建 | pnpm nx run @zitadel/functional-ui:stop | 停掉测试用的本地 Postgres |
test目标支持通过参数指定浏览器,例如--browser electron;指南指出electron浏览器应当始终可用(Cypress 内置)。
编排细节:各目标到底做了什么
结合 tests/functional-ui/project.json 的源码,可以看清每个目标背后的实际动作:
run-db:执行nx run @zitadel/devcontainer:compose up --force-recreate --renew-anon-volumes db-functional-ui,即借助 devcontainer 项目的 compose 编排,强制重建并刷新匿名卷,拉起一个仅服务于 functional-ui 的临时 Postgres 容器。run-api:先依赖@zitadel/api:build编译 Go 后端二进制,再执行nx run @zitadel/api:prod:test-functional-ui --excludeTaskDependencies。标记为continuous: true,作为长驻进程运行。open:声明依赖@zitadel/console:dev、run-db、run-api,即同时拉起 Console 开发服务、数据库与 API 后再打开交互式 Cypress Runner。test:以nx:run-commands顺序执行三条命令:cypress install——确保 Cypress 二进制就位;wait-on --verbose --interval 2000 --simultaneous 1 --timeout 30m "${CYPRESS_BACKEND_URL}/debug/ready"——每 2 秒轮询一次 API 的 ready 端点,最长等待 30 分钟,确保后端完全就绪后再跑浏览器;DISPLAY='' cypress run——以无显示环境(CI 风格)执行测试。
该目标开启
cache: true,其缓存输入除了默认项外,还显式纳入了 API 构建产物.artifacts/bin/*/*/zitadel.local——即 API 二进制一变,测试缓存即失效重跑。stop:执行nx run @zitadel/devcontainer:compose down --volumes db-functional-ui,带卷清理地拆除临时 Postgres。
API 是如何被拉起来的
apps/api/project.json 显示prod目标通过nx:run-commands启动二进制:
./.artifacts/bin/$(go env GOOS)/$(go env GOARCH)/${ZITADEL_BINARY:-zitadel.local} \ start-from-init --config ${API_CONFIG_FILE} --steps ${API_CONFIG_FILE} \ --masterkey MasterkeyNeedsToHave32Characters其中test-functional-ui配置(configuration)把API_CONFIG_FILE指向 apps/api/test-functional-ui.yaml。也就是说,functional-ui 测试用的是start-from-init子命令加一份专用 YAML——“init + start”一步完成,数据库从这份配置里现场初始化。
三、测试环境配置:test-functional-ui.yaml 解读
apps/api/test-functional-ui.yaml 是为 UI 端到端测试专门准备的 API 配置,关键项如下:
- 数据库:
Database.postgres指向库名zitadel,应用账号zitadel/zitadel、管理账号postgres/postgres,SSL 均设为disable,并配置AwaitInitialConn: 5m、MaxOpenConns: 15、MaxIdleConns: 10。注释说明该配置可通过/etc/hosts条目复用到 Docker 之外的 Zitadel 进程,提升了环境复用性。 - TLS:
Enabled: false,测试环境走明文 HTTP,与默认baseUrl的http://前缀对应。 - Quotas:
Access.Enabled: true且Debounce.MinFrequency: 0s、MaxBulkSize: 0——配额功能在测试中是开启且即时生效的。这解释了测试辅助层为何需要显式清理配额(见下文context())。 - SystemAPIUsers:声明了一个名为
cypress的系统 API 用户,KeyData是一段 Base64 编码的 RSA 公钥(-----BEGIN PUBLIC KEY-----...)。这正是 Cypress 侧签发系统令牌的验签公钥,两端由此配对(见下一节)。 - DefaultInstance:
MfaInitSkipLifetime: "0"、Features.LoginV2.Required: false——为测试流程移除 MFA 等强制项的干扰。
四、Cypress 配置:baseUrl、系统令牌与 Webhook 桩
tests/functional-ui/cypress.config.ts 是整个测试基建的枢纽,值得逐项理解:
环境变量与默认值
| 变量 | 默认值 | 说明 |
|---|---|---|
CYPRESS_BASE_URL | http://localhost:8083/ui/console | Console 页面入口 |
CYPRESS_BACKEND_URL | 由 baseUrl 去掉/ui/console后缀推导 | API 后端地址 |
CYPRESS_WEBHOOK_HANDLER_PORT | 8900 | 内置 Webhook 接收桩端口 |
CYPRESS_WEBHOOK_HANDLER_HOST | localhost | Webhook 桩监听主机 |
CYPRESS_ORGANIZATION | zitadel | 测试组织标识 |
其他关键参数:defaultCommandTimeout: 10000、pageLoadTimeout: 180000(页面加载最长 3 分钟,适配冷启动)、video: true、测试报告输出到cypress/results(HTML + JSON)、trashAssetsBeforeRuns: false(保留历史产物便于回溯)。
系统令牌(system token)的签发链路
这是理解本套件鉴权模型的关键。setupNodeEvents中注册了若干cy.task:
systemToken():使用配置中内置的 RSA 私钥,以RS256算法现场签发一个 JWT,claims 为iss=cypress、sub=cypress、aud=${CYPRESS_BACKEND_URL}、exp设为约 999 年后。由于 test-functional-ui.yaml 中SystemAPIUsers声明了与私钥配对的cypress公钥,该令牌即被 Zitadel 识别为合法的系统 API 令牌,可访问/system/v1。safetoken({key, token})/loadtoken({key}):一个跨 spec 的令牌内存缓存(Map),避免同一会话重复登录。- Webhook 事件桩:
startWebhookEventHandler()在 Node 侧启动一个 HTTP 服务(默认 8900 端口),把收到的每个 Webhook 请求的 payload 与响应状态码记录到webhookEvents数组;resetWebhookEvents()、handledWebhookEvents()、failWebhookEvents(count)三个任务允许测试用例读取事件、并让前 N 个请求故意返回 500——用于验证通知/Webhook 投递与失败重试行为。
测试辅助层(support)
support/api/apiauth.ts 封装了两条鉴权路径:
apiAuth():以 IAM Admin 用户密码登录(Password1!),返回一组版本化 API 基地址(/management/v1、/admin/v1、/auth/v1、/oidc/v1、/saml/v2、/v2/features等),供 UI 之外的直接 API 断言使用;systemAuth():通过cy.task('systemToken')获取系统令牌,封装/system/v1调用。
custom 命令层 进一步提供了cy.context():依次完成systemAuth()→instanceUnderTest()→ 清除AuthenticatedRequests与ExecutionSeconds两类配额(呼应 YAML 中 Quotas 的开启)→apiAuth(),最终返回{ system, api, instanceId }三元组,让每个 spec 在统一的“干净上下文”中执行。注意 support/api/instances.ts 中instanceUnderTest会断言“API 上恰好只有一个 instance”——这与start-from-init全新初始化的环境假设严格匹配,也是“不要改动 API 启动假设”这条工作流约束的具体落点。
此外还有shouldNotExist()(等待某选择器元素数归零,用于验证删除后的 UI 状态)、shouldConfirmSuccess()(断言.data-e2e-success出现且.data-e2e-failure不存在)等通用断言命令。cypress/support/api/下还按领域拆分了orgs.ts、projects.ts、members.ts、grants.ts、policies.ts、oidc-settings.ts、smtp.ts、sms.ts、search.ts等 helper,覆盖组织、项目、成员、授权、策略与外部服务的预置操作。
五、测试覆盖面
从 tests/functional-ui/cypress/e2e 目录结构看,spec 按 Console 功能域组织:
organization/organizations.cy.ts、projects/projects.cy.ts、permissions/permissions.cy.ts——组织、项目与权限管理;humans/humans.cy.ts、machines/machines.cy.ts——人类用户与机器用户(Service Account 一类身份)管理;applications/applications.cy.ts——应用管理;instance/settings/(notifications.cy.ts、secret-generator.cy.ts)与settings/(login-policy.cy.ts、password-complexity.cy.ts、oidc-settings.cy.ts、features.cy.ts、private-labeling.cy.ts、external-links-settings.cy.ts)——实例级设置;events/events.cy.ts、i18n/api.cy.ts——事件审计与国际化。
结合 tests/functional-ui/package.json,套件依赖cypress ^15.14.2、cypress-wait-until(自定义等待)、jsonwebtoken(配合 Node 侧签 token)与uuid(生成测试数据标识)。
六、实操步骤与注意事项
一次完整的功能型 UI 测试流程可以归纳为:
- 在仓库根目录确认 pnpm 依赖已安装(工作区根
package.json定义了 Nx workspace); pnpm nx run @zitadel/functional-ui:test(或交互调试时...:open)——该命令会自动拉起临时 Postgres、构建并启动内嵌 Console 的 Zitadel API、等待${CYPRESS_BACKEND_URL}/debug/ready就绪后再跑 Cypress;- 测试完成后执行
pnpm nx run @zitadel/functional-ui:stop清理临时数据库。
需要注意的适用前提与限制:
- 测试环境默认 HTTP(TLS 关闭)、localhost 地址与固定端口(Console 8083、Webhook 桩 8900),如自定义端口需通过
CYPRESS_BASE_URL、CYPRESS_WEBHOOK_HANDLER_PORT等环境变量传入; instanceUnderTest要求“API 上恰好一个实例”,依赖start-from-init的全新初始化语义;若更换启动方式,必须同步更新本套件(AGENTS.md 的明确警告);- 浏览器选择通过
test目标参数传入(如--browser electron),electron始终可用; - 由于 Console 项目自身无 Nx
test目标,任何针对 Console 用户旅程的回归验证都应走本套件。
小结
tests/functional-ui是 ZITADEL 仓库中验证 Management Console 用户旅程的主通道:以 AGENTS.md 的五个已验证 Nx 目标为操作入口,以 project.json 的依赖编排、test-functional-ui.yaml 的全新初始化配置、cypress.config.ts 的系统令牌与 Webhook 桩机制为底层支撑,构成了一条从临时数据库到浏览器断言的完整、可复现的端到端测试链路。理解这条链路,也就掌握了在本地复现、调试和扩展 ZITADEL Console 功能测试所需的全部关键事实。
【免费下载链接】zitadelZITADEL - Identity infrastructure, simplified for you.项目地址: https://gitcode.com/GitHub_Trending/zi/zitadel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考