
Dify E2E 测试体系详解Cucumber Playwright 的仓库级端到端测试如何编排、运行与保障契约【免费下载链接】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本文以 Dify 仓库的 e2e/AGENTS.md 为主体完整还原该包定义的命令体系、运行时编排职责、标签语义、会话/清理契约与浏览器-API 边界并结合 e2e/scripts/run-cucumber.ts、e2e/scripts/setup.ts、e2e/features/support/world.ts 等源码说明每个约定背后的真实实现。读完本文你可以独立搭建并运行 Dify 的仓库级 E2E 套件理解其确定性执行策略与空选标签不可通过的行为门禁并能在贡献新场景时遵守该包的编写与评审规范。1. e2e 包的定位Cucumber 场景 Playwright 浏览器层e2e/AGENTS.md 开篇即定义了本包的角色它是 Dify 的仓库级 Cucumber 场景集合以 Playwright 作为浏览器层。该文件本身是唯一事实来源canonical documentation管辖当前包的架构、运行时、会话与标签语义、种子seed、协议与清理契约场景编写与评审方法论归仓库本地技能所有而功能特性级的事实则就近写在各特性目录的AGENTS.md中例如 e2e/features/agent-v2/AGENTS.md。从目录结构看整个包的职责划分非常清晰e2e/features/Gherkin 场景.feature与按能力域组织的步骤定义step-definitions/覆盖accessibilityWCAG 扫描、键盘导航、agent-v2Agent v2 运行时、apps各类应用创建/发布/分享、auth会话刷新、重定向安全、smoke认证/未认证入口等域e2e/scripts/运行时编排run-cucumber.ts、setup.ts、seed-runner.ts、run-prepared.ts、run-external-runtime.ts、run-post-merge.tse2e/support/API 客户端、清理、命名、测试素材等共享能力e2e/fixtures/test-materials/确定性上传素材包括 voice-input.wav麦克风场景使用的假音频、含中文与特殊字符的文件名素材 agent-special-filename-中文 #$%.txt 等。e2e/README.md 仅有一行指针声明规范文档位于AGENTS.md这也印证了文档一个包、一份权威契约的组织方式。2. 命令体系从单次运行到完整确定性执行2.1 前置条件与并发约束所有命令都在仓库根目录执行。依赖与浏览器只需一次性安装pnpm install与pnpm -C e2e e2e:install后者对应 e2e/package.json 中的playwright install --with-deps chromium webkit。文档特别强调一条硬性约束同一时间只能运行一个本地pnpm -C e2e e2e*进程。原因是各 runner 共享端口、认证状态与日志路径。这一点在 e2e/scripts/setup.ts 中有直接证据startApi启动前会检测127.0.0.1:5001是否已被占用若被占用会直接抛出包含端口监听者描述的错误Agent backend 的 5050 端口与 shellctl 沙箱的 5004 端口同理从机制上防止了多进程互相踩踏。2.2 完整命令清单以下命令完整继承自 e2e/AGENTS.md 的 Commands 一节并与 e2e/package.json 的 scripts 定义一一对应场景命令在已初始化的实例上运行 E2Epnpm -C e2e e2e独立自动化 WCAG A 级扫描pnpm -C e2e e2e:accessibility:a独立自动化 WCAG AA 级扫描pnpm -C e2e e2e:accessibility:aa单页 WCAG 自动化扫描pnpm -C e2e exec tsx ./scripts/run-cucumber.ts --full -- --tags axe and wcag-a and wcag-page-studio按需替换级别与页面标签重置、初始化并运行确定性场景pnpm -C e2e e2e:full准备并运行依赖共享 fixture 的场景E2E_START_AGENT_BACKEND1 pnpm -C e2e e2e:prepared运行标签子集pnpm -C e2e e2e -- --tags smoke有头headed调试pnpm -C e2e e2e:headed -- --tags smoke准备并运行外部运行时场景E2E_START_AGENT_BACKEND1 pnpm -C e2e e2e:external仅对已有中间件做种子注入、不跑 Cucumberpnpm -C e2e seed -- --profile prepared\|external-runtime\|post-merge重置持久化的 E2E 状态pnpm -C e2e e2e:reset仅构建生产 Web 产物、不启动服务pnpm -C e2e e2e:web:build中间件生命周期pnpm -C e2e e2e:middleware:up/pnpm -C e2e e2e:middleware:down作用域静态检查vp check e2e此外还有两个文档提到、e2e/package.json 中同样存在的派生命令e2e:prepared:prepare/e2e:external:prepare/e2e:post-merge:prepare仅执行--seed-only的种子阶段与e2e:post-mergepost-merge 全量运行。2.3 常用环境变量文档还给出了若干调试与覆盖开关均可在源码中验证E2E_FORCE_WEB_BUILD1强制重建前端。e2e/scripts/setup.ts 的ensureWebBuild默认复用web/.next/BUILD_ID——它会基于 git 工作树HEAD、工作区/暂存区 diff、未跟踪文件与环境配置计算 SHA-256 构建戳与web/.next/e2e-web-build.sha256比对戳一致则直接复用产物不一致才执行pnpm run build。该机制保证复用不导致测试跑在过期产物上而E2E_FORCE_WEB_BUILD1可绕过戳校验强制重建。E2E_BROWSERwebkit聚焦跨浏览器运行。e2e/features/support/hooks.ts 的Before钩子中浏览器类型由e2eBrowser webkit ? webkit : chromium决定且microphone场景在 WebKit 下会直接抛错麦克风场景要求E2E_BROWSERchromium。E2E_SLOW_MO500配合 headed 命令做本地操作调试慢放浏览器动作。3. 运行时所有权谁启动什么顺序如何3.1 职责分配表e2e/AGENTS.md 的 Runtime Ownership 一节给出了精确的职责边界每一条都能在源码中定位职责归属文件重置、中间件、后端、前端启动e2e/scripts/setup.ts唯一 E2E 运行时编排器服务生命周期、可选 seed、Cucumber 调用、teardowne2e/scripts/run-cucumber.tsfixture 创建与验证只对接已运行的运行时绝不启动服务e2e/scripts/seed-runner.ts前端复用、就绪探测与关闭e2e/support/web-server.ts共享认证 bootstrap、场景生命周期与诊断e2e/features/support/hooks.tsDifyWorld每场景BrowserContext、认证态 setup 与清理客户端e2e/features/support/world.ts能力导向的步骤定义胶水common/仅放真正跨能力的步骤e2e/features/step-definitions/其中两条值得强调的架构决策浏览器与 API 身份严格分离。DifyWorld同时持有浏览器的BrowserContext和独立的consoleRequestContextAPI 请求上下文——文档解释其动机是让未认证与登出旅程无法破坏 fixture 的所有权。在 e2e/features/support/world.ts 的startSession中可以看到浏览侧 context 在unauthenticated时不加载storageState而 API 侧始终复用认证态两者互不干扰。跨 Actor 场景保持隔离。多角色场景为每个 actor 建立独立的BrowserContext与类型化的DifyWorld状态确保诊断与清理覆盖到每一个 actor诊断钩子会为多个页面逐一截图见 e2e/features/support/hooks.ts 的diagnosticPages列表。步骤定义的写法约束访问 World 状态的步骤定义必须写成async function (this: DifyWorld, ...)因为箭头函数无法接收 Cucumber 绑定的 World 实例。这是一个容易被忽视的 TypeScript 细节。3.2run-cucumber.ts的完整编排链阅读 e2e/scripts/run-cucumber.ts 的main函数实际执行顺序为可选重置与中间件启动--full时先resetState()再startMiddleware()可选 Agent backend 托管栈shouldStartManagedAgentBackend()为真时先起 shellctl 沙箱等待http://127.0.0.1:5004/healthz就绪再起 agent backend等待http://127.0.0.1:5050/openapi.json就绪API 服务tsx ./scripts/setup.ts api就绪判据是${apiURL}/health180 秒超时Celery workerseed 模式下队列显式为dataset,priority_dataset,workflow_based_app_execution源码常量seedCeleryQueuesWeb 服务通过startWebServer启动支持复用既有服务reuseExistingServer超时 300 秒可选 seedrunSeed(seed)Cucumber 调用npx tsx ./node_modules/cucumber/cucumber/bin/cucumber.js --config ./cucumber.config.ts透传--tags等参数--full且未自定义标签时注入排除表达式not axe and not prepared and not external-model and not external-tool行为门禁退出码为 0 时还会读取cucumber-report/report.ndjson断言至少出现一条testCaseStarted消息——即空选标签不能通过teardownfinally中按顺序停止 web、celery、api、agent backend、shellctl 沙箱与中间件任何 teardown 错误都会使进程失败SIGINT/SIGTERM 同样触发同一清理路径。e2e/scripts/setup.ts 的resetState定义了重置的确切含义停止中间件容器、清空docker/volumes下的 db/plugin_daemon/redis/weaviate 数据目录、删除.auth、cucumber-report*、.logs*、playwright-report、seed-report、test-results等 E2E 本地状态。而startMiddleware通过 docker compose--profile postgresql --profile weaviate拉起db_postgres、redis、weaviate、sandbox、ssrf_proxy、plugin_daemon六个服务并依次等待 PostgreSQL/Redis 健康检查、Weaviate ready 端点、sandbox 健康端点、plugin daemon 5002 端口可达。3.3 认证是惰性完成的确定性由 setup 证明文档明确未初始化的实例会被惰性地安装并认证已初始化的实例则直接登录并复用认证态完整的 reset 与 bootstrap 能力由 setup 流程本身证明而不是靠某个 Gherkin 场景去证明。源码印证hooks.ts的Before钩子中浏览器首次启动时执行ensureAuthenticatedState(browser, baseURL)会话缓存 bootstrap后续场景直接复用e2e/scripts/seed-runner.ts 在 seed 前也会独立完成一次ensureAuthenticatedState然后建立 standalone console session 创建 fixture且 seed 遇到 blocked 任务会失败除非显式--allow-blocked。4. 标签语义选择集、外部运行时与特殊通道4.1 标签总表标签语义默认使用共享认证 storage stateunauthenticated创建干净的未认证 contextauthenticated仅表意与选择用途不改变行为axe标记独立自动化 WCAG 扫描被默认功能套件与普通 CI 命令排除wcag-a/wcag-aa限定独立级别扫描选择任一级别的命令必须同时选择axewcag-page-slug页面选择器挂在 e2e/features/accessibility/ 对应 Examples 块上prepared需要 prepared fixturepost-merge seed 档案包含这些 fixtureexternal-model/external-tool调用真实外部运行时确定性命令排除这些标签external 命令显式 opt-inmicrophone使用签入的假音频素材与隔离 Chromium contextbrowser-smoke在 Chromium 与 WebKit CI 通道中运行聚焦的键盘/导航覆盖skip从所有 runner 档案中临时排除产品行为恢复后应立即移除禁止用于永久性或环境性屏蔽agent-backend-runtimeAgent v2 运行时场景专用要求显式的运行时可用性步骤4.2 标签如何在代码层生效e2e/cucumber.config.ts 是标签策略的落点默认标签表达式为(not axe and not prepared and not external-model and not external-tool) and not skip可由环境变量E2E_CUCUMBER_TAGS或命令行--tags覆盖报告格式固定为progress-bar、summary、html:./cucumber-report/report.html与message:./cucumber-report/report.ndjson后者正是至少一条testCaseStarted门禁的数据来源场景默认超时 60 秒。e2e/features/support/hooks.ts 则实现了unauthenticated与microphone的行为差异microphone场景使用单独的 Chromium 实例启动参数为--use-fake-device-for-media-stream、--use-fake-ui-for-media-stream与--use-file-for-fake-audio-capturevoice-input.wav%noloop并对 origin 授予microphone权限。4.3 生命周期不可复制运行时开关互斥文档对 CI 有一条强约束seed 与 Cucumber 必须共享同一个运行时生命周期组合命令e2e:prepared、e2e:external等拥有 reset、中间件、服务、seed、Cucumber 与 teardown 的完整生命周期CI 不得在 workflow YAML 里复刻这套生命周期。E2E_START_AGENT_BACKEND1会在 API 之前启动托管本地 backend且与显式提供 Agent backend URLE2E_AGENT_BACKEND_URL/AGENT_BACKEND_BASE_URL互斥——e2e/scripts/setup.ts 的getAgentBackendBaseUrl正是按显式 URL 优先、开关兜底的优先级解析的。文档同时告诫不要滥用运行时标签去暗示无关服务也不要在必需 fixture 缺失时静默跳过行为。5. 浏览器、API 与契约边界这是 e2e/AGENTS.md 中最具工程价值观的一节核心原则是被测动作归属浏览器API 只能用于准备 fixture、轮询持久化结果、清理不能替代用户的When动作除非被测契约就是持久化后端状态否则优先断言用户可见的浏览器结果。对普通 Console JSON 与可表示的 multipart 操作使用场景级或进程级的生成式 oRPC 客户端并开启请求与响应校验直接调用生成操作不得手写端点 URL、复制 DTO/schema、写响应强转、一对一转发包装、跨场景可变客户端或 TanStack Query 缓存。助手helper只在拥有fixture 构造、多操作编排、清理注册表、不变量、最终一致性轮询、收窄测试视图或协议适配器时才允许存在SSE、二进制下载、纯重定向流程、外部服务、基础设施就绪检查可以集中到真实归属者名下的适配器。校验失败即契约失败应追踪到后端 schema 归属者按需更新 api/controllers/API_SCHEMA_GUIDE.md 中的契约并重新生成dify/contracts位于 packages/contracts保持场景与产品真实状态归属者对齐不得关闭校验或添加回退 schema 来让 E2E 通过。e2e/support/api/console-client.ts 是这条规则的教科书式实现它从dify/contracts导入生成路由契约consoleRouterContract通过OpenAPILink挂上RequestValidationPlugin与ResponseValidationPlugin两个插件请求经由 Playwright 的APIRequestContext发出并自动从 storageState 提取csrf_tokenCookie 注入X-CSRF-Token头——CSRF 缺失直接抛错从机制上保证未认证态的 API 调用不会静默发生。6. 种子、清理与诊断可复现性的最后防线6.1 命名与素材一次性资源名必须经 e2e/support/naming.ts 的createE2EResourceName生成格式为E2E [qualifier] resource nonce同文件还提供assertE2EResourceName任何不以E2E开头的资源名会直接断言失败——这让测试资源在全库可辨识、可批量清理。确定性上传素材保留在 e2e/fixtures/test-materials/统一经 e2e/support/test-materials.ts 解析路径。6.2 清理契约谁创建、谁负责文档规定seed 脚本拥有共享的长生命周期 fixture场景拥有自己创建的一次性资源并必须注册清理。实现上分为两层见 e2e/features/support/hooks.ts类型化清理队列DifyWorld的createdAppIds、createdAgentIds、createdDatasetIds、createdAgentConfigFiles、createdAgentConfigSkills、createdBuiltinToolCredentials等字段在Clean up scenario resources钩子中以toReversed()LIFO顺序删除——先删子资源与被引用资源再删所有者注册式清理对类型化字段之外的生命周期归属者用registerCleanup(...)注册回调注册回调在类型化队列之后按 LIFO 执行e2e/features/support/world.ts。清理顺序遵循 Cucumber 的 After 钩子逆注册序诊断 → 清理 → 关闭会话。清理失败不允许被吞掉错误会被attach到报告且对已通过的场景若清理出错同样会使场景失败shouldFailForCleanupErrors。6.3 诊断与报告布局失败场景FAILED/AMBIGUOUS/PENDING/UNDEFINED/UNKNOWN会产出全页截图与 HTML 捕获落在cucumber-report/artifacts/文件名带时间戳与场景名HTML 报告与 Cucumber Messagesreport.ndjson位于cucumber-report/后端与前端启动日志位于.logs/API、Celery、web、agent backend、shellctl 各自独立日志文件各 CI 通道保留自己的报告与日志目录如cucumber-report-non-external、.logs-webkit与 e2e/scripts/setup.ts 中e2eStatePaths的定义一致console错误与weberror页面异常会被收集并在诊断阶段附加到报告。7. 实践路径如何从仓库状态到一次可信的 E2E 运行结合 e2e/AGENTS.md 与上述源码一条可复现的本地实践路径如下均为文档给出的运行方式不涉及修改仓库内容一次性准备pnpm installpnpm -C e2e e2e:install安装 chromium 与 webkit日常快跑已初始化实例pnpm -C e2e e2e -- --tags smoke有头调试加e2e:headed慢放加E2E_SLOW_MO500完整确定性验证pnpm -C e2e e2e:full——它先清数据卷与 E2E 状态再拉起 postgres/redis/weaviate/sandbox/ssrf_proxy/plugin_daemon随后启动 API5001、Celery、Web3000跑完标签为not axe and not prepared and not external-model and not external-tool的确定性场景并执行至少一条testCaseStarted门禁共享 fixture 场景E2E_START_AGENT_BACKEND1 pnpm -C e2e e2e:prepared只想准备数据不跑场景时用pnpm -C e2e seed -- --profile prepared外部运行时场景E2E_START_AGENT_BACKEND1 pnpm -C e2e e2e:external或提供E2E_AGENT_BACKEND_URL/AGENT_BACKEND_BASE_URL指向既有运行时无障碍审计按级别跑e2e:accessibility:a/e2e:accessibility:aa或按页面标签精确到单页变更审计工作流、页面矩阵或就绪契约时PR 作者应在合并前跑一遍 AA/全量路径文档定位为 opt-in 人工审计而非回归门禁。8. 小结Dify 的 e2e 包展示了仓库级端到端测试的一种严谨形态一份 AGENTS.md 作为包级契约命令、标签、清理、诊断、契约边界全部成文每个约定在 e2e/scripts/run-cucumber.ts、e2e/scripts/setup.ts、e2e/features/support/world.ts、e2e/features/support/hooks.ts 中都有可验证的落地实现行为门禁Cucumber 退出码 非空testCaseStarted断言与契约校验oRPC 请求/响应双向校验、CSRF 强制共同保证了通过二字的含金量。对于需要在大型产品中建设 E2E 体系的团队这套编排器唯一、职责分片、身份分离、LIFO 清理、空跑不通过的设计是值得直接对标的范本。【免费下载链接】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),仅供参考