Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
- 报告详情采用执行轨迹与证据工作区,可按执行、节点、请求尝试次数定位历史画布,并从画布返回完整报告。流程设置、来源定位与检查器配置/输入输出/校验均连接已有定义和处理器。
- 1440×900、1280×800、1600×1000 是桌面核对视口;390px 报告采用单列证据和表格内部滚动。
- 当前实现、验证范围及必要视觉差异见 [实施记录](ui-v5/implementation.md)、[视觉差异](ui-v5/visual-deviations.md) 和 [80 项验收矩阵](ui-v5/acceptance.csv)。
- 最新审计的运行隔离、页外保存/发布、证据定位和核心场景验证见 [审计修复记录](ui-v5/audit-followup.md)。

历史 V3 设计源保留在 [`FlowTest_V3_UI_CN_HD`](../../FlowTest_V3_UI_CN_HD/README.md)。其中紫色主色、深色导航和旧面板尺寸已由 v5 替代。
24 changes: 12 additions & 12 deletions docs/design/ui-v5/acceptance.csv

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions docs/design/ui-v5/audit-followup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# v5 最新审计修复

依据[最新审计](https://chatgpt.com/share/6ac1011f-d588-83ea-bfd7-6b30b3a9b6f5),以 `main@b06af115de9151add04060d09bfc963ec2ac0700` 为基线,在 `codex/ui-v5-audit-followup` 独立工作树实施。保留已确认的冰蓝白主题、权限、固定发布版本和草稿修订检查。

## 修复范围

| 缺口 | 现在的行为 | 回归证据 |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| 旧运行迟到覆盖当前流程 | 运行开始、轮询、事件、重连和失败项重跑均绑定用户、项目、流程与运行代次;切换或卸载失效旧回调,后台任务继续执行;终态证据不被迟到节点事件覆盖 | `workflow-execution-concurrency.test.tsx`、`use-execution-events.test.ts` |
| 页外流程保存后回退、发布版本不更新 | 保存完整对象同步精确详情和已加载列表,取消旧读取;发布后读取精确详情。连续保存使用新修订号,首次发布和再次发布按新固定版本运行;保存期间的新编辑及丢弃后的新稿保留 | `workflow-save-concurrency.test.tsx`、`WorkflowsPage.test.tsx` |
| 手动切换残留旧控制实例定位 | 切换草稿或历史清除节点、请求尝试、控制记录和实例参数;直接深链接继续恢复原定位 | `WorkflowsPage.test.tsx`、`WorkflowRunInspector.test.tsx` |
| 独立断言结果有值但检查器显示未提供 | 实际浏览器验证发现此缺口。优先显示标准断言证据;明确的 `assert` 节点兼容读取冻结输出中的期望、实际和结果,保留差值与来源,不根据 HTTP 成功推断断言通过 | `AssertionEvidence.test.tsx`、`WorkflowRunInspector.test.tsx`、核心浏览器场景 |
| 连线点击受画布动画影响 | 点击前等待节点测量及视口连续稳定,保留可命中区检查与实际检查器断言;没有增加重试或放宽像素阈值 | `workflow-editor-interactions.spec.ts`、`workflow-editor-visual.spec.ts` |

## 核心场景与视觉证据

新增 `frontend/e2e/workflow-editor-core-scenarios.spec.ts`,在 **1280×800、1440×900** 两档分别验证并截图:完整编辑器、If 与 Until、循环体入口和出口、两轮真实控制记录、精确嵌套实例、HTTP 200 后独立断言失败。失败证据截图确保期望 201、实际 200 和差值 −1 在视口内同时可见;保存、报告链接和只读边界分别断言。

这些场景使用实际 API/Worker 与合成资产。截图附带源码提交、前端差异和完整源码 SHA-256、浏览器版本、平台与几何数据。新建的 **6 张 macOS Chromium 基线**覆盖完整编辑器、循环体和失败证据,普通验证保持 `maxDiffPixelRatio=0.003`。只屏蔽生成的对象标识、耗时等易变区域,断言数值、失败状态与循环边界保留。既有 **27 张 Linux 基线未重建**。

本地 Docker 新容器无法启动,改用独立数据库 `flowtest_ui_v5_audit_20261003`、Redis 10/11/12、独立存储桶和当前源码的 API/Worker/Vite 进程;基础设施沿用已有服务。测试目标为同机 Mock,项目策略只允许目标主机及 `/32` 私网地址,没有放宽生产出站策略。此验证不称为完整本地 Compose 验收。

## 验收边界

前端定向检查、全量覆盖率与构建、实际浏览器行为、平台对应的像素检查和最新提交远程 Required Gate 分别计入证据。历史 PR #127 的门禁与 62 通过/1 重试记录不能证明本轮通过。PR 评审补充了再次运行及失败项重跑被拒绝的路径:保留上一轮终态证据,直到新执行成功返回才替换;旧回调仍失效,失败对话框能够展示错误和重试。

新核心场景的 Linux 像素基线、整站文字与控件对比度、真实角色的全部特殊状态、完整键盘/屏幕阅读器审计以及同环境小/中/大图前后对比仍需独立验收。新场景在 Linux 执行真实接口与行为断言并取景,但不把 macOS 像素通过计为 Linux 通过。未把这些项目改写为全量完成。
43 changes: 42 additions & 1 deletion frontend/e2e/support/workflow-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ export async function seedEditor(
const project = await create('/projects', { name: `流程编辑验收 ${Date.now()}` })
const root = `/projects/${project.id}`
const mockTargetUrl = process.env.FLOWTEST_E2E_MOCK_TARGET_URL ?? 'http://mock-target:8080'
const mockTargetHost = new URL(mockTargetUrl).hostname
await create(
`${root}/security-policy`,
{ allowed_hosts: ['mock-target'], allowed_private_cidrs: ['172.16.0.0/12'] },
{
allowed_hosts: [mockTargetHost],
allowed_private_cidrs: [process.env.FLOWTEST_E2E_MOCK_TARGET_CIDR ?? '172.16.0.0/12'],
},
'put',
)
const environment = await create(`${root}/environments`, {
Expand Down Expand Up @@ -91,6 +95,7 @@ export async function seedEditor(
export async function clickEdge(page: Page, id = 'start-api') {
const path = page.locator(`.react-flow__edge[data-id="${id}"] .react-flow__edge-interaction`)
await path.waitFor({ state: 'attached' })
await settleCanvas(page)
const hit = await path.evaluate((element) => {
const svgPath = element as SVGPathElement
const transform = svgPath.getScreenCTM()
Expand All @@ -111,6 +116,42 @@ export async function clickEdge(page: Page, id = 'start-api') {
await page.mouse.click(hit!.x, hit!.y)
await expect(page.getByRole('heading', { name: '连接关系', exact: true })).toBeVisible()
}

export async function settleCanvas(page: Page): Promise<void> {
await expect
.poll(() =>
page.locator('.react-flow__node').evaluateAll((nodes) =>
nodes.flatMap((node) => {
const body = node.querySelector('.flow-node')
if (!body) return []
const wrapperHeight = node.getBoundingClientRect().height
const bodyHeight = body.getBoundingClientRect().height
return Math.abs(wrapperHeight - bodyHeight) < 1
? []
: [{ node: node.getAttribute('data-id'), wrapperHeight, bodyHeight }]
}),
),
)
.toEqual([])
await page.evaluate(async () => {
let previous = '',
stable = 0
const deadline = performance.now() + 5000
while (stable < 10) {
if (performance.now() > deadline) throw new Error('流程画布测量或视口动画尚未稳定')
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
const current = JSON.stringify({
viewport: document.querySelector('.react-flow__viewport')?.getAttribute('style'),
nodes: Array.from(document.querySelectorAll('.react-flow__node'), (node) => {
const { x, y, width, height } = node.getBoundingClientRect()
return { x, y, width, height }
}),
})
stable = current === previous ? stable + 1 : 0
previous = current
}
})
}
export async function dragBetween(page: Page, source: Locator, target: Locator) {
await source.waitFor({ state: 'visible' })
await target.waitFor({ state: 'visible' })
Expand Down
Loading
Loading