news 2026/9/7 19:57:54

Dify E2E 测试体系:Cucumber + Playwright 仓库级端到端场景的运行、编排与契约实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dify E2E 测试体系:Cucumber + Playwright 仓库级端到端场景的运行、编排与契约实践

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/README.md 为入口展开——该文件声明本包的规范化文档位于 e2e/AGENTS.md,因此后者构成本文的主体骨架。读完本文,你可以掌握 Dify E2E 包的全部运行命令与语义标签体系,理解"单一编排器拥有服务生命周期"的运行时所有权模型,并了解浏览器操作与 oRPC 生成契约如何划分 Browser/API 边界,以及种子数据、清理注册与失败诊断的完整约定。

1. 定位与核心结论:README 指向的规范化文档

e2e/README.md 全文只有一行有效信息:

Canonical documentation for this package lives in [AGENTS.md].

这确立了 Dify 仓库的一个文档约定:包级 README 只做导航,真正的架构、运行时、会话、标签语义、种子、协议与清理契约由 e2e/AGENTS.md 承载。该文档开篇即给出包的定位:

This package contains Dify's repository-level Cucumber scenarios with Playwright as the browser layer.

即:e2e/是 Dify 的仓库级 E2E 测试包,采用Cucumber(Gherkin 场景描述)+ Playwright(浏览器驱动)的组合。文档同时划清了职责边界:本文件(AGENTS.md)拥有当前包的架构、运行时/会话/标签语义、seed、协议与清理契约;编写与评审方法论由仓库本地的e2e-cucumber-playwrightskill 负责;而各功能特性的具体事实则放在各自 feature 最近的AGENTS.md中。

从源码结构看,e2e/目录与该描述完全吻合:Gherkin 特性文件位于 features/ 下的accessibility/agent-v2/apps/auth/smoke/等子目录;步骤定义(glue code)位于 features/step-definitions/,按能力域组织;共享能力位于 support/ 与 scripts/。

2. 运行命令全集:从一次性安装到按标签子集执行

所有命令必须从仓库根目录执行。先执行一次性安装:pnpm install(安装依赖)与pnpm -C e2e e2e:install(安装浏览器及其系统依赖)。注意:由于各 runner 共享端口、认证状态与日志路径,同一时间只能运行一个本地pnpm -C e2e e2e*进程

AGENTS.md 给出的完整命令表如下(均已对照 e2e/package.json 中的 scripts 逐一核实存在):

场景命令
对已初始化的实例执行场景pnpm -C e2e e2e
独立的 WCAG Level A 无障碍扫描pnpm -C e2e e2e:accessibility:a
独立的 WCAG Level 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_BACKEND=1 pnpm -C e2e e2e:prepared
按标签运行子集pnpm -C e2e e2e -- --tags @smoke
有头模式调试pnpm -C e2e e2e:headed -- --tags @smoke
准备并运行外部运行时场景E2E_START_AGENT_BACKEND=1 pnpm -C e2e e2e:external
仅对现有中间件做 seed,不跑 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:uppnpm -C e2e e2e:middleware:down
限定范围的静态检查vp check e2e

对照 e2e/package.json,可以看到这些命令的真实映射:e2ee2e:full都调用tsx ./scripts/run-cucumber.ts(后者附加--full);e2e:prepared调用run-prepared.tse2e:external调用run-external-runtime.tse2e:middleware:up/downe2e:resete2e:web:build全部收敛到tsx ./scripts/setup.ts <subcommand>;而e2e:install实际执行playwright install --with-deps chromium webkit,CI 变体e2e:install:ci使用--only-shell进一步缩减体积。此外还有文档未列出的e2e:post-merge入口(run-post-merge.ts),用于合并后的场景准备。

文档还给出三个关键环境变量:

  • E2E_FORCE_WEB_BUILD=1:runner 默认复用web/.next/BUILD_ID(即复用已有的 Next.js 构建产物),设置该变量可强制重新构建前端;
  • E2E_BROWSER=webkit:针对特定浏览器做聚焦的跨浏览器运行;
  • E2E_SLOW_MO=500:与有头命令配合,用于本地动作级调试的减速。

2.1 标签过滤的默认语义:cucumber.config.ts

Cucumber 的配置文件 e2e/cucumber.config.ts 值得完整解读,它定义了"没有显式--tags时到底跑什么":

const hasCliTags = process.argv.some((arg) => arg === '--tags' || arg.startsWith('--tags=')) const defaultNonExternalTags = 'not @axe and not @prepared and not @external-model and not @external-tool' const selectedTags = process.env.E2E_CUCUMBER_TAGS || (hasCliTags ? undefined : defaultNonExternalTags) const tags = selectedTags ? `(${selectedTags}) and not @skip` : 'not @skip' const config = { format: [ 'progress-bar', 'summary', 'html:./cucumber-report/report.html', 'message:./cucumber-report/report.ndjson', ], import: ['./tsx-register.js', 'features/**/*.ts'], paths: ['features/**/*.feature'], tags, timeout: 60_000, }

其语义是:标签选择存在三级优先级——环境变量E2E_CUCUMBER_TAGS> 命令行--tags> 默认的defaultNonExternalTags(排除@axe@prepared@external-model@external-tool四类外部/可选标签);无论哪级来源,最终都会再叠加and not @skip,保证被@skip标记的场景在所有 runner 配置下都被排除。配置还固定了:特性文件路径为features/**/*.feature;场景超时 60 秒;报告双写为 HTML(cucumber-report/report.html)与 Cucumber Messages NDJSON(cucumber-report/report.ndjson)。

3. 运行时所有权模型:每个脚本只拥有一件事

AGENTS.md 的 "Runtime Ownership" 一节是本包架构的核心:

文件所有权职责
scripts/setup.tsreset、中间件、后端、前端的启动
scripts/run-cucumber.ts唯一的 E2E 运行时编排器:服务生命周期、可选的 seed 执行、Cucumber 调用、teardown
scripts/seed-runner.ts针对已运行中的运行时创建并验证 fixture;永不启动服务
support/web-server.ts前端复用、就绪探测与关闭
features/support/hooks.ts共享的认证引导、场景生命周期与诊断
features/support/world.tsDifyWorld:每场景的行为级BrowserContext及其带认证的 setup/cleanup 客户端
features/step-definitions/按能力域组织的 glue;common/仅留给真正跨能力的步骤

阅读 run-cucumber.ts 可以印证上述每一条。其main()流程按序完成:

  1. --full时先resetState()startMiddleware()(run-cucumber.ts#L122-L127);
  2. shouldStartManagedAgentBackend()为真,先拉起 shellctl sandbox(默认端口E2E_SHELLCTL_PORT || 5004,健康检查/healthz),再拉起 agent backend(默认端口E2E_AGENT_BACKEND_PORT || 5050,就绪探针/openapi.json),均以startLoggedProcess记录日志到.logs/(run-cucumber.ts#L132-L161);
  3. 启动 API server(就绪探针${apiURL}/health,超时 180 秒)与 Celery worker——seed 场景下额外限定队列dataset,priority_dataset,workflow_based_app_execution(run-cucumber.ts#L20);
  4. startWebServer启动/复用前端(超时 300 秒);
  5. 执行runSeed(seed)(若请求);
  6. npx tsx ./node_modules/@cucumber/cucumber/bin/cucumber.js --config ./cucumber.config.ts调用 Cucumber,--headed通过CUCUMBER_HEADLESS=0/1传递;
  7. finally中按注册顺序执行清理:停 Web、停 Celery、停 API、停 agent backend、停 shellctl sandbox、停中间件(run-cucumber.ts#L88-L106)。

两个值得注意的实现细节:

  • 未预期进程退出即快速失败waitForManagedProcesswaitForUrl与进程exit事件竞速,进程未就绪先退出时,会抛出附带日志尾部 20 行的错误(run-cucumber.ts#L28-L74),避免"服务挂了却干等超时";
  • 双通道终止处理:注册SIGINT/SIGTERM触发清理并以非零码退出,保证 Ctrl-C 也能完成 teardown(run-cucumber.ts#L108-L119)。

3.1 认证引导、行为门禁与步骤定义约定

文档还规定了几条行为层面的契约:

  • 惰性初始化与认证复用:未初始化的实例在 setup 阶段被惰性安装并认证;已初始化的实例直接登录并复用认证状态。全量运行(--full)通过 setup 阶段本身证明 reset 与 bootstrap 的正确性,而不是写一个 Gherkin 场景去证明。
  • 行为门禁:Cucumber 的退出码是行为门禁;同时 runner 还要求报告里至少出现一条testCaseStarted消息——run-cucumber.ts#L223-L226 在退出码为 0 后调用assertCucumberScenariosStarted(messages),防止"标签选择为空导致 0 个场景却通过"。文档明确禁止用"场景数基线"或"跳过场景白名单"替代这一门禁。
  • World 隔离:浏览器身份与 API 身份保持分离,使未认证/登出旅程不会破坏 fixture 的所有权;跨 actor 场景为每个 actor 使用独立的BrowserContext与类型化DifyWorld状态,确保诊断与清理覆盖所有 actor。
  • TypeScript 写法约束:访问 World 状态的步骤定义必须写成async function (this: DifyWorld, ...),因为箭头函数拿不到 Cucumber 绑定的 World 实例

4. 标签体系与外部运行时:哪些场景属于哪条流水线

AGENTS.md 的 "Tags And External Runtime" 一节定义了整个 E2E 的选择语义,逐条归纳如下:

标签语义
(默认)场景使用共享的已认证存储状态(storage state)
@unauthenticated创建干净的上下文,用于未认证旅程
@authenticated仅作意图与选择用,不改变运行时行为
@axe独立自动化 WCAG 扫描;被默认功能套件与常规 CI 命令排除
@wcag-a/@wcag-aa限定独立的级别化扫描;选择任一级别的命令必须同时选择@axe
@wcag-page-<slug>页面选择器,挂在 features/accessibility/ 下对应 Examples 块
@prepared需要 prepared fixtures;post-merge seed profile 包含它们
@external-model/@external-tool场景会调用真实外部运行时;确定性命令排除这些标签,external 命令是显式选择(opt-in)
@microphone使用签入仓库的假音频 fixture 与隔离的 Chromium 上下文
@browser-smoke在 Chromium 与 WebKit 两条 CI 通道运行聚焦的键盘与导航覆盖
@skip将场景临时排除出所有 runner 配置;产品行为恢复后应尽快移除,禁止用于永久或环境依赖性的屏蔽
@agent-backend-runtimeAgent v2 运行时场景;要求显式的运行时可用性步骤

无障碍工作流的定位值得强调:文档将其定义为opt-in 的人工审计而非回归门禁。当修改审计工作流、页面矩阵或就绪契约时,PR 作者应在合并前自行运行 AA/all 路径——这与package.jsone2e:accessibility默认转发到e2e:accessibility:aa的实现一致。

外部运行时的规则(对照 run-cucumber.ts 中的 agent backend 启动逻辑):

  • Seed 与 Cucumber 必须共享同一个运行时生命周期。组合命令拥有 reset、中间件、服务、seed、Cucumber 与 teardown 全流程;CI 不允许在 workflow YAML 中自行复刻这套生命周期
  • E2E_START_AGENT_BACKEND=1会在 API 之前拉起受管的本机 agent backend,且与显式的E2E_AGENT_BACKEND_URL/AGENT_BACKEND_BASE_URL互斥;feature 自带的服务使用自己的标签,agent v2 运行时场景使用@agent-backend-runtime并要求显式的运行时可用性步骤。
  • 文档末尾是一条纪律性约束:不得用运行时标签暗示无关服务,也不得在必需 fixture 缺失时静默跳过行为。

5. 浏览器、API 与契约边界:什么动作必须由浏览器完成

"Browser, API, And Contract Boundaries" 一节回答了 E2E 中最常见的设计问题:用户动作必须由被测浏览器执行,API 只能准备 fixture、轮询持久化结果与清理,不能替代用户的When动作;结果断言优先选择用户在浏览器中可观测的结果,除非持久化后端状态本身就是被测契约。

API 侧的调用规范同样具体:

  • 对普通 Console JSON 与可表达的 multipart 操作,使用场景级或进程级的生成式 oRPC 客户端,并开启请求与响应校验;直接调用生成的操作。e2e/package.json 的依赖可以印证这条约束的落点:@orpc/client@orpc/contract@orpc/openapi-client与 workspace 内的@dify/contracts包正是"生成契约"的来源。
  • 禁止清单:手写端点 URL、复制 DTO/schema、响应强转(cast)、一对一转发包装器、可变跨场景客户端、TanStack Query 缓存。
  • 仅在 helper 真正拥有某项职责时才保留:fixture 构建、多操作编排、清理注册表、不变量、最终一致性轮询、收窄的测试视图或协议适配器;SSE、二进制下载、仅重定向流程、外部服务与基础设施就绪检查可以集中到真实 owner 下的适配器。
  • 校验失败即契约失败:应回溯到后端 schema 的 owner,必要时更新 api/controllers/API_SCHEMA_GUIDE.md 中的契约、重新生成@dify/contracts,并让场景对齐产品真正的状态 owner;禁止关闭校验或添加兜底 schema 来让 E2E 通过

6. 种子数据、清理与诊断:可复现与可归因的工程约定

最后一节 "Seeds, Cleanup, And Diagnostics" 给出五组约定,前两组可直接落到具体文件:

  • 命名:通过 support/naming.ts 生成带E2E前缀的一次性资源名;确定性上传素材放在fixtures/test-materials/,经 support/test-materials.ts 解析——二者在目录中均已确认存在。
  • 所有权分层:seed 脚本拥有共享的长生命周期 fixture;场景拥有其创建的一次性资源,且必须注册清理
  • 清理机制:已知资源类型使用类型化的DifyWorld清理字段,其他生命周期 owner 通过registerCleanup(...)注册;注册的回调在类型化清理队列之后按LIFO顺序执行,对应实现位于 support/cleanup.ts(编排器的 teardown 即调用其中的runCleanupTasks,见 run-cucumber.ts#L91-L98)。
  • 顺序与归因:先删子资源与被引用资源,再删 owner;清理失败要附到报告上,不得吞掉
  • 诊断产物:失败场景在cucumber-report/artifacts/下生成截图与 HTML 捕获;HTML 报告与 Cucumber Messages 报告统一放在cucumber-report/;后端与前端的启动日志在.logs/;额外 CI 通道各自保留自己的报告与日志目录。这些路径与cucumber.config.tshtml:./cucumber-report/report.htmlmessage:./cucumber-report/report.ndjson的输出配置,以及 run-cucumber.ts#L79-L80 中cucumberReportDir/logDir的定义完全对应。

7. 小结:这套 E2E 体系回答了什么问题

把 e2e/AGENTS.md 的契约与仓库实现对照起来看,Dify 的仓库级 E2E 体系围绕四个问题给出了一致的答案:

  1. 谁来拥有服务生命周期?唯一编排器 scripts/run-cucumber.ts,组合命令拥有从 reset 到 teardown 的全流程,CI 不复刻生命周期;
  2. 哪些场景在默认流水线上跑?cucumber.config.ts 的三级标签优先级 +not @skip,默认排除@axe/@prepared/@external-*四类可选与外部依赖标签;
  3. 浏览器测试与 API 的边界在哪?When动作必须发生在浏览器,API 只做 fixture 准备、轮询与清理,且必须走带校验的生成式 oRPC 客户端,校验失败按契约失败处理;
  4. 状态如何可复现、失败如何可归因?E2E前缀的一次性命名、LIFO 清理注册、cucumber-report/.logs/的固定诊断产物,加上"至少一条testCaseStarted"的空跑门禁。

对维护者的实际意义是:新增一个 E2E 场景时,先决定它的标签(是否需要@preparedfixture、是否触碰真实外部运行时、是否属于@unauthenticated旅程),再决定When动作走浏览器还是 API,最后确认其创建的资源都注册了清理——这三步走完后,场景自然落入既有的运行时生命周期与报告契约之中。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 19:55:20

深入解析 Java GC 调优:减少 Minor GC 频率,优化系统吞吐

目录 一、问题描述 (一)GC 频率与影响 1. GC 频率统计 2. GC 对请求延迟的影响 2.1 Minor GC 影响的请求数 2.2 Major GC 影响的请求数 3. TP90/TP99 的影响 (二)主要问题 1. Minor GC 过于频繁 2. Major GC 触发频率偏高 二、分析 GC 机制 (一)Java 内存回收…

作者头像 李华
网站建设 2026/9/7 19:51:44

光伏设计数据不同源?一体化设计软件让排布电气结构清单同步

做分布式光伏设计的朋友应该都有过这种经历&#xff1a;CAD摊开画排布&#xff0c;Excel开着算电气&#xff0c;结构校核还得再切到另一个工具&#xff0c;最后汇总清单时发现&#xff0c;图纸上画了186块组件&#xff0c;BOM表里却变成192块。我之前复核一个朋友的工商业屋顶项…

作者头像 李华
网站建设 2026/9/7 19:51:39

办公自动化|HR 表单重复录入怎么办?AI 自动填充 Word 文档实践

一、业务痛点 HR 工作中存在大量文档表单工作&#xff1a;新员工入职登记表、信息采集表、社保公积金配套文档。不同文档大量字段复用&#xff0c;姓名、身份证、紧急联系人等信息反复复制粘贴。 事务性录入占用大量工时&#xff0c;挤压员工沟通、培训、人才发展等高价值工作时…

作者头像 李华
网站建设 2026/9/7 19:51:07

【单片机课程设计/毕业设计】基于单片机 DHT11 的环境温湿度智能加湿平台设计 集成语音识别的 STM32/51 单片机室内加湿智能终端设计(024906)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华