关于 e2e-testing
为 Langflow 前端提供 Playwright 端到端测试编写与维护指南,面向测试新功能、排查失败用例和审查覆盖率的开发者。内容包括自定义夹具、选择器、辅助函数与标签规范。
主要功能
- 01编写和筛选 Playwright E2E 测试
- 02使用夹具检测 API 与流程错误
- 03按优先级选择稳定的元素定位方式
- 04复用启动、视图和流程辅助函数
- 05为测试添加发布与领域标签
适用场景
- 01为新功能编写 E2E 测试
- 02排查失败的 Playwright 测试
- 03审查测试标签与覆盖范围
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ langflow-ai/ langflow --skill e2e-testing安装后在 Claude Code 中输入/e2e-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/fixtures.mdhelpers.mdselectors.md详细介绍
Langflow E2E 测试(Playwright)
适用场景
- 用户要求为某个功能或流程编写 E2E 测试
- 用户要求修复失败的 E2E 测试
- 用户要求审查 E2E 测试覆盖率
- 用户修改组件中的
data-testid属性(可能会导致现有测试失败) - 用户修改
src/frontend/tests/utils/中的测试工具
以下情况不要应用:
- 用户询问单元测试(使用
frontend-testing技能进行 Jest 测试) - 用户询问后端测试(使用
backend-code-review技能进行 pytest 测试)
技术栈
| 工具 | 版本 | 用途 |
|---|---|---|
| Playwright | 1.59.1 | E2E 测试运行器 + 浏览器自动化 |
| Chromium | (捆绑) | 默认浏览器(已禁用 Firefox/Safari) |
| 自定义 fixture | tests/fixtures.ts | 自动检测 API 错误和流程执行失败 |
关键命令
# Run all E2E tests
npx playwright test
# Run tests filtered by tag
npx playwright test --grep "@release"
npx playwright test --grep "@workspace"
npx playwright test --grep "@starter-projects"
# Run a specific test file
npx playwright test tests/core/features/run-flow.spec.ts
# Debug mode (headed browser + step through)
npx playwright test --debug
# Show HTML report after run
npx playwright show-report
# Update snapshots (if used)
npx playwright test --update-snapshots
配置
文件:src/frontend/playwright.config.ts
| 设置 | 值 | 原因 |
|---|---|---|
fullyParallel | true | 并行运行测试以提升速度 |
timeout | 5 分钟 | 流程构建可能较慢;防止误报超时 |
retries | 3(本地),2(CI) | 网络/渲染问题可能不稳定;重试可捕获这些问题 |
workers | 2 | 平衡速度和资源使用 |
actionTimeout | 20s | 单项操作超时(点击、填充等) |
trace | on-first-retry | 失败时捕获跟踪信息以便调试 |
baseURL | http://localhost:3000 | Vite 开发服务器 |
WebServer:Playwright 会自动启动后端(7860 上的 uvicorn)和前端(3000 上的 npm start)。
目录结构
src/frontend/tests/
├── fixtures.ts # Custom test fixture with error detection
├── globalTeardown.ts # Cleanup (removes temp DB after tests)
├── core/
│ ├── features/ # Main feature tests (run-flow, playground, etc.)
│ ├── integrations/ # Starter project / template tests
│ ├── regression/ # Bug regression tests
│ └── unit/ # Component-level Playwright tests
├── extended/
│ ├── features/ # Extended features (MCP, auto-save, etc.)
│ ├── integrations/ # Extended integrations
│ └── regression/ # Extended regressions
└── utils/ # 37+ shared helper functions
文件命名
- 使用 kebab-case 并以
.spec.ts为后缀:run-flow.spec.ts、playground.spec.ts、flow-lock.spec.ts - 模板测试可以使用空格:
Document QA.spec.ts、Social Media Agent.spec.ts - 用于并行执行的分片测试:
chatInputOutputUser-shard-0.spec.ts
注意:E2E 测试使用
.spec.ts(Playwright 约定)。单元测试使用.test.tsx(Jest 约定)。不要混用。
测试结构
基本测试
import { expect, test } from "../../fixtures";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
test(
"user should be able to run a flow successfully",
{ tag: ["@release", "@workspace"] },
async ({ page }) => {
await awaitBootstrapTest(page);
// Arrange: Create a flow
await page.getByTestId("blank-flow").click();
// Act: Add components and run
await page.getByTestId("sidebar-search-input").fill("Chat Output");
// ... setup ...
// Assert: Verify result
await expect(page.getByTestId("build-status-success")).toBeVisible({ timeout: 30000 });
},
);
使用 test.describe
test.describe("Flow Lock Feature", () => {
test(
"should lock and unlock a flow",
{ tag: ["@release", "@api"] },
async ({ page }) => {
// ...
},
);
test(
"should prevent editing when locked",
{ tag: ["@release"] },
async ({ page }) => {
// ...
},
);
});
使用串行模式(测试存在顺序依赖)
test.describe.configure({ mode: "serial" });
test("step 1: create flow", async ({ page }) => { /* ... */ });
test("step 2: edit flow", async ({ page }) => { /* ... */ });
test("step 3: delete flow", async ({ page }) => { /* ... */ });
使用事件传递模式(流式/轮询/直接)
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
withEventDeliveryModes(
"Document Q&A should work",
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
// This test runs 3 times: streaming, polling, direct
// Each mode is configured automatically via route interception
},
);
标签系统
每个测试都必须标记为 @release——发布运行会搜索此标签,因此未标记或标签错误的测试规范会悄无声息地从发布覆盖范围中移除。在 @release 之外,还要添加下表中的领域标签(一个测试可以有多个标签)。以下六个是唯一允许的标签;不要自行创建新标签。
| 标签 | 用途 | 使用时机 |
|---|---|---|
@release | 属于发布运行(每个测试规范都必须添加) | 所有测试 |
@workspace | 工作区/流程管理 | 创建、编辑、删除流程 |
@api | 依赖 API 的功能 | 调用后端端点的测试 |
@database | 数据库操作 | 涉及持久化的测试 |
@components | 组件级测试 | 单个组件的行为 |
@starter-projects | 模板/起始项目测试 | 预构建的流程模板 |
// Right: tag your test
test("my feature test", { tag: ["@release", "@workspace"] }, async ({ page }) => { ... });
// Wrong: no tags — test can't be filtered
test("my feature test", async ({ page }) => { ... });
自定义 fixture:错误检测
始终从 ../../fixtures 导入 test 和 expect,不要从 @playwright/test 导入。
// Right
import { expect, test } from "../../fixtures";
// Wrong — bypasses error detection
import { expect, test } from "@playwright/test";
原因:自定义 fixture 会自动监控所有 /api/ 响应,并在以下情况发生时使测试失败:
- 出现 HTTP 400、404、422 或 500 错误
- 流程执行在事件流中返回
error: true - 流式响应中出现 Python 异常
若要选择允许预期错误(例如,测试错误处理):
test("should show error on invalid input", { tag: ["@release"] }, async ({ page }) => {
page.allowFlowErrors(); // Allow flow errors for this test
// ... test that expects errors ...
});
选择器策略
优先级(按优先顺序)
getByTestId——最稳定,在 Langflow 中约 95% 的情况下都会使用getByRole——用于按钮、标题和表单元素getByText——用于可见文本内容waitForSelector——用于 CSS 选择器和动态元素locator——用于复杂选择器(CSS、XPath)
常见的 data-testid 模式
画布与导航:
blank-flow— 新建空白流程按钮sidebar-search-input— 组件搜索canvas_controls_dropdown— 画布控制菜单fit_view、zoom_out、zoom_in— 画布控制react-flow-id— ReactFlow 画布容器
组件字段:
popover-anchor-input-{fieldname}— 组件参数的输入字段input-chat-playground— Playground 聊天输入框div-chat-message— Playground 中的聊天消息
操作:
add-component-button-{component}— 将组件添加到画布button-send— 发送聊天消息button_run_{component}— 运行指定组件publish-button、save-flow-button— 流程操作edit-fields-button— 切换检查面板字段编辑器
模态框与面板:
modal-title— 模态框标题icon-Globe— 全局变量icon-Lock— 流程锁定切换session-selector— Playground 会话切换器
重要:全局变量与徽标
当组件字段选中了全局变量(load_from_db: true + value: "OPENAI_API_KEY")时,该字段会渲染一个徽标,而不是 <input> 元素。这意味着 getByTestId("popover-anchor-input-api_key") 找不到该元素——它不存在于 DOM 中。
预先选中了全局变量的模板:Market Research、Price Deal Finder、Research Agent。 未预先选中全局变量的模板(会渲染输入框):Instagram Copywriter。
核心辅助函数
位于 src/frontend/tests/utils/:
| 函数 | 功能 | 使用时机 |
|---|---|---|
awaitBootstrapTest(page) | 等待应用完全加载 | 每个测试的开头 |
initialGPTsetup(page) | 完整设置:adjustView → updateComponents → selectModel → addKey → adjustView → unselectNodes | 需要配置 OpenAI 的测试 |
adjustScreenView(page, opts?) | 适应视图 + 缩小 | 将组件添加到画布后 |
zoomOut(page, times) | 缩小 N 次 | 组件太小时 |
selectGptModel(page) | 为所有 Language Model 节点选择 gpt-4o-mini | 依赖 GPT 的测试 |
addOpenAiInputKey(page) | 为所有 openai_api_key 字段填入 OPENAI_API_KEY | 需要 API 密钥的测试 |
enableInspectPanel(page) | 开启检查面板 | 必须在调用 edit-fields-button 前调用 |
disableInspectPanel(page) | 关闭检查面板 | 检查完成后的清理 |
updateOldComponents(page) | 如果存在过时组件,则点击 "Update all" | 加载已保存的流程后 |
unselectNodes(page) | 点击画布空白区域以取消选中所有节点 | 节点操作后 |
renameFlow(page, { flowName }) | 重命名当前流程 | 流程管理测试 |
uploadFile(page, filename) | 从测试资源上传文件 | 文件上传测试 |
withEventDeliveryModes(...) | 运行测试 3 次:streaming、polling、direct | Starter project 测试 |
initialGPTsetup 选项
await initialGPTsetup(page); // All steps
await initialGPTsetup(page, {
skipAdjustScreenView: true,
skipUpdateOldComponents: true,
skipSelectGptModel: true,
});
检查面板模式(至关重要)
// MUST enable inspection panel FIRST
await enableInspectPanel(page);
// Click a node to select it
await page.getByTestId("title-OpenAI").click();
// Open field editor
await page.getByTestId("edit-fields-button").click();
// Toggle field visibility
await page.getByTestId("showmodel_name").click();
// Close field editor
await page.getByTestId("edit-fields-button").click();
如果跳过 enableInspectPanel(page),edit-fields-button 将不会显示。
跳过模式
// Skip test if env var missing
test.skip(!process?.env?.OPENAI_API_KEY, "OPENAI_API_KEY required to run this test");
// Skip test unconditionally with reason
test.skip(true, "Feature not yet implemented with new designs");
编写优秀的端到端测试
应该:
- 为每个测试添加
@release标签(以及适用的任何领域标签) - 从
../../fixtures导入,而不是从@playwright/test导入 - 以
awaitBootstrapTest(page)开始——务必如此 - 使用
getByTestId作为稳定的选择器 - 对异步操作的
waitForSelector和expect(...).toBeVisible()设置明确的超时时间 - 测试完整的用户流程:设置 → 操作 → 验证
- 对涉及流程执行(chat、build)的测试,使用
withEventDeliveryModes
不要:
- 不要使用
page.waitForTimeout(),除非绝对必要——优先使用waitForSelector或expect().toBeVisible() - 不要硬编码 API 密钥——从
process.env.OPENAI_API_KEY读取 - 不要无故跳过测试——始终为
test.skip()提供第二个参数 - 不要从
@playwright/test导入——使用自定义夹具 - 在访问
edit-fields-button前,别忘了调用enableInspectPanel(page) - 选择全局变量时,不要假设输入字段存在(此时会渲染徽标)
挑战测试(此处也适用)
E2E 测试还应覆盖对抗性场景:
- 无效输入:粘贴 10K 个字符、特殊字符(
<script>alert(1)</script>)、空提交 - 网络中断:如果用户在构建过程中失去连接,会发生什么?
- 权限边界:用户能否通过直接 URL 访问其他用户的流程?
- 并发操作:双击删除、快速发送聊天消息
- 错误恢复:UI 是否能从 500 错误中顺利恢复?