feat(workbench): group live presentation steps into expandable categories
Generic Container CI/CD / test-build-publish (push) Successful in 1m12s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m13s

Group adjacent live display steps into category menus with merged snapshots, original-step submenus and scoped playback.

The submenu exposes the latest raw step, so explicit selections must remain pinned. Locate failed and cancelled steps using the full group sequence, and prevent an exiting menu from dismissing a trigger-owned reopen. Regression tests reproduce these paths before the fixes.

Validation: lint, typecheck, 1178 unit tests and 10 focused browser cases passed; update the presentation history guide.
This commit is contained in:
2026-11-12 12:54:09 +08:00
parent 7a304a91a8
commit 03827a2fc0
18 changed files with 1029 additions and 84 deletions
+29 -25
View File
@@ -1,6 +1,6 @@
import { INVESTIGATION_DEV_NETWORK } from "../../src/features/workbench/utils/investigation-dev-network";
import { expect, test } from "@playwright/test";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation } from "./support/mock-investigation-presentation";
import { investigationFixture, mockInvestigationPresentation, selectInvestigation, showInvestigationMap, showInvestigationTasks } from "./support/mock-investigation-presentation";
test("live investigation charts remain visible while Dev is open", async ({ page }) => {
const task = investigationFixture(1, "合成爆管展示核查");
@@ -766,36 +766,34 @@ test("live actions continue recording without interrupting a historical view and
const task = investigationFixture(1, "历史回看中的实时调查");
const mock = await mockInvestigationPresentation(page, [task]);
await selectInvestigation(page, task.title);
await showInvestigationMap(page);
const panel = page.getByLabel("调查实时展示", { exact: true });
await expect(panel.getByText("实时跟随", { exact: true })).toBeVisible();
const chart = (id: number) => ({ action: "open_chart", chart_id: "live-history", evidence_refs: ["dev:synthetic"], chart: { title: `压力快照 ${id}`, chart_type: "line", x_data: ["08:00"], series: [{ name: "压力", data: [id] }] } });
await mock.emit(task, chart(1));
await expect(panel.getByRole("group", { name: "压力快照 1", exact: true })).toBeVisible();
const switcher = panel.getByRole("combobox", { name: "切换展示动作" });
await expect(switcher).toContainText("压力快照 1");
const switcher = panel.getByRole("button", { name: "切换展示动作" });
await expect(switcher).toHaveText("当前(最新)");
const manager = page.getByRole("dialog", { name: "图表管理", exact: true });
await expect(manager).toBeVisible();
await manager.getByRole("button", { name: "关闭窗口", exact: true }).click();
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(1);
await expect(page.getByRole("option", { name: /当前(最新).*压力快照 1/ })).toBeVisible();
await expect(page.getByRole("menu", { name: "展示类别", exact: true }).locator("[data-history-group-row]")).toHaveText(["图表"]);
await page.keyboard.press("Escape");
await expect(panel.getByRole("button", { name: "上一个动作" })).toBeDisabled();
await expect(panel.getByRole("button", { name: "上一个动作" })).toBeEnabled();
await mock.emit(task, chart(2));
await expect(switcher).toContainText("压力快照 2");
await expect(switcher).toHaveText("当前(最新)");
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(2);
await page.getByRole("option", { name: "1. 打开图表 · 压力快照 1 · 完成", exact: true }).click();
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
await page.getByRole("menuitem", { name: "1. 压力快照 1", exact: true }).click();
await expect(panel).toContainText("历史回看");
for (let id = 3; id <= 12; id++) await mock.emit(task, chart(id));
await expect(switcher).toContainText("1. 打开图表");
await expect(switcher).toContainText("1. 压力快照 1");
await switcher.click();
await expect(page.getByRole("option")).toHaveText([
/当前(最新).*压力快照 12/,
...Array.from({ length: 11 }, (_, index) => `${11 - index}. 打开图表 · 压力快照 ${11 - index} · 完成`)
]);
await expect(page.getByRole("option", { name: /当前(最新).*压力快照 12/ })).toBeVisible();
await expect(page.getByRole("option", { name: /^12\. 打开图表/ })).toHaveCount(0);
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
const historyMenu = page.getByRole("menu", { name: "图表展示步骤", exact: true });
await expect(historyMenu.getByRole("menuitem", { name: /^\d+\./ })).toHaveCount(12);
await expect(historyMenu.getByRole("menuitem", { name: "12. 压力快照 12", exact: true })).toBeVisible();
await page.keyboard.press("Escape");
await expect(panel.getByRole("group", { name: "压力快照 1", exact: true })).toBeVisible();
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toHaveCount(0);
@@ -803,8 +801,8 @@ test("live actions continue recording without interrupting a historical view and
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toBeVisible();
await panel.getByRole("button", { name: "上一个动作" }).focus();
await page.keyboard.press("Enter");
await expect(switcher).toContainText("11. 打开图表");
await expect(panel.getByRole("group", { name: "压力快照 11", exact: true })).toBeVisible();
await expect(switcher).toHaveText("图表");
await expect(panel.getByRole("group", { name: "压力快照 12", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});
@@ -822,20 +820,22 @@ test("selecting an investigation restores saved actions, keeps live continuity a
saved.push({ task_id: completed.task_id, run_id: runId, event_seq: 5, execution_generation: 1, occurred_at: completed.created_at, command: command("已完成调查的压力曲线") });
const mock = await mockInvestigationPresentation(page, [task, completed], saved);
await selectInvestigation(page, task.title);
await showInvestigationMap(page);
const panel = page.getByLabel("调查实时展示", { exact: true });
const switcher = panel.getByRole("combobox", { name: "切换展示动作" });
await expect(switcher).toContainText("4 个动作");
const switcher = panel.getByRole("button", { name: "切换展示动作" });
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
await expect(panel.getByRole("group", { name: "复核压力对比", exact: true })).toBeVisible();
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toContainText("保存的影响范围");
await switcher.click();
await expect(page.getByRole("option")).toHaveCount(4);
await page.getByRole("option", { name: "1. 打开图表 · 初次压力对比 · 完成", exact: true }).click();
await expect(page.getByRole("menu", { name: "展示类别", exact: true }).locator("[data-history-group-row]")).toHaveCount(3);
await page.getByRole("menuitem", { name: "展开图表步骤", exact: true }).last().click();
await page.getByRole("menuitem", { name: "1. 初次压力对比", exact: true }).click();
await expect(panel.getByRole("group", { name: "初次压力对比", exact: true })).toBeVisible();
await expect(panel.getByRole("region", { name: "调查覆盖层" })).toHaveCount(0);
await mock.emit(task, command("当前实时压力"));
await expect(panel.getByRole("status")).toContainText("后续 4 个动作");
await panel.getByRole("button", { name: "回到当前", exact: true }).click();
await expect(switcher).toContainText("5 个动作");
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
await expect(panel.getByRole("group", { name: "当前实时压力", exact: true })).toBeVisible();
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await testInfo.attach("saved-investigation-history", { body: await panel.screenshot({ path: testInfo.outputPath("saved-investigation-history.png") }), contentType: "image/png" });
@@ -844,6 +844,7 @@ test("selecting an investigation restores saved actions, keeps live continuity a
await page.getByRole("checkbox", { name: "跟随所选调查", exact: true }).uncheck();
await expect(panel.getByText("已载入历史", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "切换地图 Dev Panel" }).click();
await showInvestigationTasks(page);
const tasks = page.getByRole("region", { name: "工况任务", exact: true });
// Completed tasks can be selected through the all-status filter.
const completedTitle = tasks.getByText(completed.title, { exact: true }).first();
@@ -852,10 +853,13 @@ test("selecting an investigation restores saved actions, keeps live continuity a
if (await all.isVisible()) await all.click();
}
await completedTitle.click();
await showInvestigationMap(page);
await expect(panel.getByRole("group", { name: "已完成调查的压力曲线", exact: true })).toBeVisible();
await expect(switcher).toContainText("1 个动作");
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "0");
await showInvestigationTasks(page);
await tasks.getByText(task.title, { exact: true }).first().click();
await expect(switcher).toContainText("5 个动作");
await showInvestigationMap(page);
await expect(panel.getByRole("slider", { name: "历史播放进度" })).toHaveAttribute("max", "2");
await expect(panel.getByRole("group", { name: "当前实时压力", exact: true })).toBeVisible();
expect(mock.mutations).toEqual([]);
});