尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Dify CLI E2E 测试套件实战:用真实 difyctl 二进制验证社区版与企业版全链路

Dify CLI E2E 测试套件实战:用真实 difyctl 二进制验证社区版与企业版全链路 Dify CLI E2E 测试套件实战用真实 difyctl 二进制验证社区版与企业版全链路【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/difyDify 仓库中的 cli/test/e2e 目录是difyctl命令行工具langgenius/difyctl的端到端测试套件它不 mock 任何 HTTP 流量而是直接调用真实的difyctl开发入口针对一个存活的 Dify staging 服务器执行登录、应用发现、DSL 导入与run运行等全链路验证。读完本篇你可以掌握该套件的环境变量契约DIFY_E2E_*、CE/EE 双版本自动适配机制、global-setup 的账号与 token 引导流程以及如何按 [P0]、EE、本地离线三种模式执行测试并理解其背后的隔离与幂等设计。一、测试目标与总体设计该套件的核心定位是验证真实的difyctl二进制与真实 Dify 服务器之间的集成行为。根据 cli/test/e2e/README.md 的说明每个测试都使用一个隔离的临时配置目录isolated temporary config directory确保会话状态不会在测试文件之间泄漏。从源码结构看这套设计落在几个关键实现上测试通过bun bin/dev.js启动 CLI因此无需先执行pnpm build见 cli.ts 中BIN常量run(argv, opts)是核心原语统一注入CI1抑制交互提示与 spinner、NO_COLOR1去除 ANSI 转义码、DIFY_E2E_NO_KEYRING1强制文件型 token 存储避免 macOS keychain 的 UI 弹窗阻塞子进程并在提供configDir时设置DIFY_CONFIG_DIR指向隔离目录见 run()超时策略默认 60 秒超时先发SIGINT2 秒后仍无响应再SIGKILL退出码记为 124。配套的 Vitest 运行器配置在 vitest.e2e.config.tstest: { environment: node, globalSetup: [test/e2e/setup/global-setup.ts], setupFiles: [], // E2E 不复用单测的 setup.ts —— 真实二进制自行初始化全局对象 testTimeout: 120_000, // 调用真实 staging 服务器放宽超时 hookTimeout: 30_000, retry: Number(process.env.VITEST_RETRY ?? 0), // 本地默认 0flaky 网络由 withRetry() 精确处理 pool: forks, fileParallelism: false, // 顺序执行避免 staging 上的 workspace 级冲突 reporters: [verbose], }值得注意的一点配置加载阶段会尝试把cli/.env.e2e逐行解析进process.env跳过注释与空行且已存在的环境变量优先CI 中则直接通过环境变量注入见 vitest.e2e.config.ts。二、目录结构与各模块职责README 给出的目录布局并已在仓库中核实存在test/e2e/ ├── setup/ │ ├── env.ts — 加载并校验 DIFY_E2E_* 环境变量CE EE │ ├── global-setup.ts — CE/EE 感知的引导建号、铸 token、发现 workspace、导入 DSL │ └── global-teardown.ts — 删除运行期间创建的会话 │ ├── helpers/ │ ├── cli.ts — run()、withAuthFixture()、mintFreshToken()、injectAuth()、spawn_background() │ ├── assert.ts — assertExitCode、assertJson、assertErrorEnvelope、assertNoAnsi 等 │ ├── cleanup-registry.ts — registerConversation() / cleanupRegisteredConversations() │ ├── retry.ts — withRetry(fn, { attempts, delayMs }) │ └── skip.ts — optionalIt()、enterpriseOnlyIt()、enterpriseOnlyDescribe()、isEE() │ └── suites/ ├── auth/ — login、status、use、whoami、devices、logout ├── config/ — config path/get/set/unset/view 与环境变量覆盖 ├── discovery/ — get app list / 单应用 / describe / 跨 workspace[EE] ├── dsl/ — export studio-app ├── error-handling/ — 错误消息与退出码规范 ├── framework/ — help、全局 flag ├── output/ — table / json / yaml 输出 ├── run/ — run 基础、streaming、file、reasoning、HITL └── agent/ — agent skill workflow对应的实际文件包括 setup/env.ts、setup/global-setup.ts、helpers/cli.ts、helpers/skip.ts、helpers/retry.ts 以及 fixtures/apps 下的 10 个 DSL 夹具echo-chat.yml、echo-workflow.yml、file-upload.yml、file-chat.yml、4 个hitl-*.yml、reasoning-chat.yml、ws2-workflow.yml。三、CE/EE 双版本支持机制difyctl支持两种 Dify 版本测试套件通过DIFY_E2E_EDITION自动适配版本DIFY_E2E_EDITIONWorkspace 数量EE 专属用例社区版 (CE)ce默认1自动跳过企业版 (EE)ee2激活版本判定逻辑非常薄全部集中在 env.tsexport function isEnterpriseEdition(): boolean { return (process.env.DIFY_E2E_EDITION ?? ce).toLowerCase() ee }[EE] 标签与声明式跳过需要企业版特性跨独立 workspace 切换、跨 workspace 应用查询等的用例命名中带[EE]标签并用 skip.ts 中的包装器声明式跳过// helpers/skip.ts 用法 const eeIt enterpriseOnlyIt(caps) eeIt([EE][P0] cross-workspace query returns apps from all workspaces, async () { // test body })export function enterpriseOnlyIt(caps: E2ECapabilities): TestAPI { return optionalIt(caps.edition ee) // EE 模式返回 itCE 模式返回 it.skip }这里没有运行时断言跳过是声明式的[EE]标签同时保证被跳过的用例在报告中可见并支持--testNamePattern \[EE\]过滤。另外isEE(caps)用于普通it块内的内联守卫if (!isEE(caps)) return。caps即E2ECapabilities由 global-setup 通过project.provide(e2eCapabilities, …)注入到每个测试文件其结构定义见 env.ts除edition外还包含主 token、以及为logout/devices这两个“破坏性”套件单独铸发的logoutToken/devicesToken——撤销它们不会影响主 token。四、环境变量契约与配置文件凭证模板复制模板并填入真实值模板文件位于 cli/test/e2e/.env.e2e.example.env.e2e已被 git 忽略cp cli/test/e2e/.env.e2e.example cli/.env.e2e # 用真实凭证编辑 cli/.env.e2e社区版CE—— 最少 3 个变量变量说明DIFY_E2E_HOST服务器 base URL如http://localhostDIFY_E2E_EMAIL账号邮箱由 global-setup 自动创建DIFY_E2E_PASSWORD账号密码发送前 Base64 编码企业版EE—— 必选变量变量说明DIFY_E2E_EDITION必须为eeDIFY_E2E_HOSTConsole/API base URLDIFY_E2E_EMAIL成员账号邮箱DIFY_E2E_PASSWORD成员账号密码模板注释.env.e2e.example明确了 EE 的 workspace 契约登录账号下必须已存在名为auto_test0主 workspace承载 8 个 fixture 应用和auto_test1次 workspace承载ws2-workflow.yml一个应用的两个 workspace。任一缺失时 global-setup 不做 DSL 导入应用按精确名称查找存在则跳过导入。可选覆盖变量两个版本通用变量说明DIFY_E2E_TOKEN预铸 bearer token跳过 device-flow 铸 tokenDIFY_E2E_SSO_TOKEN外部 SSO bearer tokendfoe_前缀DIFY_E2E_CONSOLE_URLConsole URL 与DIFY_E2E_HOST不同时使用DIFY_E2E_WORKSPACE_ID/DIFY_E2E_WORKSPACE_NAME覆盖主 workspace ID / 名称DIFY_E2E_WS2_ID/DIFY_E2E_WS2_APP_ID覆盖次 workspaceEEID / 应用 IDDIFY_E2E_CHAT_APP_IDecho-chat 应用 IDDIFY_E2E_WORKFLOW_APP_IDecho-workflow 应用 IDDIFY_E2E_FILE_APP_ID/DIFY_E2E_FILE_CHAT_APP_ID文件上传 / 文件对话应用 IDDIFY_E2E_HITL_APP_ID及其_EXTERNAL_、_SINGLE_ACTION_、_MULTI_NODE_变体HITL 各形态应用 IDDIFY_E2E_REASONING_APP_IDseparated-reasoning chatflow 应用 IDopt-inDIFY_E2E_REASONING_PROVISION1→ 自动导入reasoning-chat.yml夹具DIFY_E2E_MODElocal本地模式只跑离线安全用例global-setup 直接提前返回这些变量在 loadE2EEnv() 中被统一读取、缓存缺失必选变量时抛出带清单的错误。resolveEnv(caps)则把 global-setup 解析出的 capabilities 叠加到环境变量之上——capabilities 永远优先环境变量仅作回退。分离模式推理套件opt-inrun-app-reasoning.e2e.ts验证reasoning_chunk带外通道--think会把思维链以think…/think_end标签形式输出到 stderr答案保持干净-o json则将其持久化到metadata.reasoning。该套件在DIFY_E2E_REASONING_APP_ID未解析时自动跳过因为它要跑真实的 LLM 节点前提条件有二一个 LLM 节点使用reasoning_format: separated的 chatflowworkspace 已配置默认对话模型。可以把DIFY_E2E_REASONING_APP_ID指向已有应用或设置DIFY_E2E_REASONING_PROVISION1自动导入reasoning-chat.yml夹具其系统提示强制生成think块因此任意对话模型都会走 separated 路径无需专用推理模型。在 global-setup 中这个 opt-in 开关直接控制reasoning-chat.yml是否进入导入清单见 global-setup.ts。五、global-setup 引导流程详解global-setup.ts 是整个套件的地基DIFY_E2E_MODElocal时直接返回离线模式。真实模式下流程如下1. 账号引导CE 路径CE: 1. 幂等注册账号先试 /console/api/init再退 /console/api/register409 视为成功 2. 登录获取 session cookie 3. 通过 device flow 铸造主 bearer token 4. 校验 token 5. 发现唯一 workspace回退到第一个可用 workspace 6. 铸造 logout / devices 两套专属 token 7. 导入全部 DSL 夹具并发布、设置 access_mode → publicCE 的 workspace 发现策略优先取名称包含auto的 workspace按字典序找不到则回退到账号下第一个 workspace见 discoverWorkspaces()。EE 路径则由运维预先建好auto_test0/auto_test1两个 workspaceglobal-setup 按精确名称发现它们global-setup.ts并额外调用企业 admin API 完成成员账号与应用的发布及 access_mode 设置。2. Token 铸造device flow 与本地缓存主 token 的获取优先级为DIFY_E2E_TOKEN环境变量 → 本地缓存文件.token-cache.json与.env.e2e同目录、git-ignoredhost 变化即失效→ 现场铸造。缓存命中后会先调用GET /openapi/v1/account/sessions校验有效性见 global-setup.ts。铸造过程走标准三步 device flowmintTokenWithSession()POST /openapi/v1/oauth/device/code → device_code user_code POST /openapi/v1/oauth/device/approve → 批准429/5xx 时按 2s×attempt 退避重试最多 5 次 POST /openapi/v1/oauth/device/token → dfoa_ tokenlogout与devices-revoke两个套件会撤销真实服务端子会话因此 global-setup 为它们各铸一个一次性 token且这两个 token 刻意不缓存每次都必须是新鲜的。同样测试内部也可用 mintFreshToken() 按需铸发一次性dfoa_token避免消耗共享 token。3. DSL 夹具供给幂等 并发安全provisionApps() 对每个 fixture 依次执行切换到目标 workspace → 按名称搜索应用存在则复用→ 不存在则通过difyctl import studio-app --from-file yml --workspace wsId导入 → 启用 Service API → 发布workflow/advanced-chat/agent-chat模式→ 设置access_mode → publicCE 服务器返回 404 时非致命跳过。针对 CI 并行 job 的竞态问题代码有一个显式短路如果全部 9 个DIFY_E2E_*_APP_ID环境变量已预置例如来自 CI 的 provision job则直接跳过provisionApps否则多个并行 job 会各自查到“not found”并重复导入同一应用见 global-setup.ts 的注释。4. 会话清理registerConversation()注册运行期间创建的会话global-teardown 阶段统一删除防止 staging 上残留测试数据。六、运行测试在cli/目录下package.json 定义了vp test --config vitest.e2e.config.ts系列脚本当前版本1.17.0目标 Node^24.20.0cd cli # 社区版默认 bun run test:e2e # 企业版 DIFY_E2E_EDITIONee bun run test:e2e # 只跑 [P0] 冒烟用例 bun run test:e2e:smoke # 只跑 EE 标签用例P0 冒烟 DIFY_E2E_EDITIONee bun run test:e2e:smoke --testNamePattern \[EE\] # 只跑离线安全的用例无需网络实际为 help agent 套件见 DIFY_E2E_MODElocal 分支 bun run test:e2e:local # 只跑单个文件 bun vitest --config vitest.e2e.config.ts test/e2e/suites/auth/status.e2e.ts脚本与源码的对应关系test:e2e:smoke即--testNamePattern \[P0\]test:e2e:local即DIFY_E2E_MODElocal vp test …见 package.json。此外DIFY_E2E_INCLUDE支持逗号分隔的 glob 列表来进一步收敛用例兼容旧变量DIFY_E2E_SINGLE_FILE例如DIFY_E2E_INCLUDEtest/e2e/suites/run/**/*.e2e.ts见 vitest.e2e.config.ts。七、测试执行顺序文件按顺序执行fileParallelism: false在 vitest.e2e.config.ts 中硬编码为auth(login → status → use → whoami) → framework(help) → output → error-handling → framework(其余) → discovery → dsl → run(basic / streaming / file / reasoning / HITL) → agent → devices → logout顺序背后的两条原则auth 最先其余多数测试都依赖有效会话devices 与 logout 最后它们会撤销真实服务器上的会话 token放在最后可避免“误伤”后续用例。八、关键设计决策对照表README 总结的设计决策及其源码佐证决策理由源码佐证CE/EE 版本开关DIFY_E2E_EDITIONce/ee决定 global-setup 引导路径并激活/跳过[EE]用例isEnterpriseEdition()[EE]标签约定测试名带[EE]让被跳过的用例在报告中可见并支持--testNamePattern \[EE\]过滤enterpriseOnlyIt(caps)EE 模式返回itCE 模式返回it.skip——跳过是声明式的无运行时断言skip.ts无 mock所有 HTTP 流量打到真实服务器捕获真实集成回归隔离配置目录每个测试新建withTempConfig()临时目录会话状态互不泄漏withTempConfig()withAuthFixture()合并withTempConfiginjectAuth为一个 fixture减少 beforeEach 样板withAuthFixture()injectAuth()绕过 Device Flow非 auth 测试写入预制hosts.ymltokens.ymltoken_storage: file跳过浏览器步骤只有auth/套件走真实流程injectAuth()mintFreshToken()logout与devices-revoke通过 device flow API 铸发一次性dfoa_token撤销时不伤主 token全局retry: 0全局重试会掩盖非幂等失败已知 flaky 网络调用改用局部的withRetry()精确控制默认 3 次尝试、1s 间隔、可附shouldRetry谓词见 retry.tsCI 可用VITEST_RETRY显式打开全局重试会话清理registerConversation() global-teardown 在运行结束后删除 staging 会话一个实用的withRetry用法示例摘自源码注释const result await withRetry(() run([get, app, -o, json]))九、小结这套 E2E 测试的价值在于把difyctl与真实 Dify 服务之间的完整契约——device flow 认证、workspace 发现与切换、DSL 导入发布、run的流式/HITL/推理通道、token 撤销——全部纳入可重复执行的回归验证。其工程取舍顺序执行、token 本地缓存、capabilities 优先于环境变量、破坏性套件专用 token、声明式 EE 跳过都直接服务于两个目标幂等可重跑与并行 CI 安全。对维护者而言入口文件只有四个env.ts契约、global-setup.ts引导、cli.ts执行原语、vitest.e2e.config.ts编排修改任一环节前应先通读这四处。【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表