feat(workbench): integrate operational workflows and map state
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import type {
|
||||
OperationalTask,
|
||||
OperationalTaskAction,
|
||||
OperationalTaskSummary
|
||||
} from "../../src/features/workbench/api/operational-task-client";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
|
||||
const projectId = "324ff7ee-c2d4-5184-a84b-48fb14a2933e";
|
||||
const taskId = "72000000-0000-4000-8000-000000000001";
|
||||
const appointmentTaskId = "72000000-0000-4000-8000-000000000002";
|
||||
const actionId = "73000000-0000-4000-8000-000000000001";
|
||||
|
||||
test("审批动作展示中文参数和证据,viewer 不可审批,冲突后可核对未知回执", async ({ page }, testInfo) => {
|
||||
test.setTimeout(60_000);
|
||||
await mockAgentApi(page);
|
||||
await page.route("**/runtime-config.js", (route) => route.fulfill({
|
||||
contentType: "text/javascript",
|
||||
body: `globalThis.__TJWATER_CONFIG__=${JSON.stringify({
|
||||
TJWATER_AUTH_MODE: "disabled",
|
||||
TJWATER_SERVER_API_BASE_URL: "http://server.test",
|
||||
TJWATER_MAP_URL: "http://map.test/geoserver",
|
||||
TJWATER_GEOSERVER_WORKSPACE: "tjwater_next"
|
||||
})}`
|
||||
}));
|
||||
await page.route("http://server.test/api/v1/projects**", (route) => route.fulfill({ json: {
|
||||
items: [{ project_id: projectId, gs_workspace: "tjwater_next", status: "active" }],
|
||||
total: 1, limit: 1000, offset: 0
|
||||
} }));
|
||||
await page.route("http://server.test/api/v1/scada-devices**", (route) =>
|
||||
route.fulfill({ json: { items: [], total: 0, limit: 1000, offset: 0 } })
|
||||
);
|
||||
await page.route("http://map.test/**", (route) => route.fulfill({ status: 204 }));
|
||||
|
||||
const baseAction: OperationalTaskAction = {
|
||||
action_id: actionId,
|
||||
task_id: taskId,
|
||||
version: 3,
|
||||
kind: "issue_command",
|
||||
node_id: null,
|
||||
reason: "隔离疑似漏损支路前需要调度员确认阀门目标与开度",
|
||||
payload: {
|
||||
command_type: "set_valve",
|
||||
target: { kind: "valve", id: "V-102" },
|
||||
parameters: { opening: 35 },
|
||||
impact_summary: "隔离疑似漏损支路,可能短时影响演示小区供水压力",
|
||||
preconditions: ["上游压力不低于 28m", "现场人员已到位"],
|
||||
expires_at: "2026-09-23T09:00:00Z"
|
||||
},
|
||||
evidence_refs: ["e-pressure"],
|
||||
required_approval_permission: "operational_action.command.approve",
|
||||
required_execution_permission: "operational_action.command.execute",
|
||||
approval_status: "pending",
|
||||
execution_status: "not_submitted",
|
||||
execution_request_id: null,
|
||||
proposed_by: "agent",
|
||||
proposed_at: "2026-09-23T08:25:00Z",
|
||||
decided_by: null,
|
||||
decided_at: null,
|
||||
decision_comment: null,
|
||||
executed_by: null,
|
||||
executed_at: null,
|
||||
receipt: null,
|
||||
execution_detail: null
|
||||
};
|
||||
let task: OperationalTask = {
|
||||
project_id: projectId,
|
||||
task_id: taskId,
|
||||
task_type: "event_investigation",
|
||||
workflow: "burst_report",
|
||||
lifecycle_status: "waiting_human",
|
||||
active_run: null,
|
||||
status: "waiting_human",
|
||||
priority: "urgent",
|
||||
revision: 2,
|
||||
execution_generation: 2,
|
||||
execution_request_id: "74000000-0000-4000-8000-000000000001",
|
||||
title: "阀门调节审批",
|
||||
summary: "爆管事件调查需要确认隔离阀门动作",
|
||||
source: { kind: "operator", external_id: null, external_revision: null, event_kind: "burst_report" },
|
||||
trigger: { kind: "manual", occurred_at: "2026-09-23T08:20:00Z", detail: "演示路与样板路交叉口" },
|
||||
scheduled_for: null,
|
||||
agent_session_id: "approval-session",
|
||||
agent_lease_expires_at: null,
|
||||
current_plan_revision: null,
|
||||
current_report_revision: 1,
|
||||
created_at: "2026-09-23T08:20:00Z",
|
||||
updated_at: "2026-09-23T08:25:00Z",
|
||||
latest_plan: null,
|
||||
latest_report: {
|
||||
revision: 1,
|
||||
execution_generation: 1,
|
||||
source_session_id: "approval-session",
|
||||
published_at: "2026-09-23T08:24:00Z",
|
||||
published_by: "agent",
|
||||
report: {
|
||||
title: "爆管事件调查报告",
|
||||
current_conclusion: "疑似支路漏损,阀门动作尚未执行。",
|
||||
conclusion_boundary: "等待人工确认和设备回执。",
|
||||
evidence_refs: ["e-pressure"],
|
||||
recommendations: ["确认 V-102 目标与开度后再执行"],
|
||||
pending_items: ["调度员确认阀门动作"]
|
||||
}
|
||||
},
|
||||
evidence: [{
|
||||
evidence_id: "e-pressure",
|
||||
kind: "system_data",
|
||||
summary: "V-102 下游压力在五分钟内下降 8.2m",
|
||||
source_label: "SCADA 压力监测",
|
||||
source_ref: "PRESSURE-V102",
|
||||
observed_at: "2026-09-23T08:18:00Z",
|
||||
recorded_at: "2026-09-23T08:23:00Z",
|
||||
recorded_by: "agent",
|
||||
has_snapshot: false
|
||||
}],
|
||||
actions: [baseAction],
|
||||
permissions: {
|
||||
can_publish_artifacts: false,
|
||||
can_propose_actions: false,
|
||||
can_sync_external_feedback: false,
|
||||
can_decide_work_orders: false,
|
||||
can_execute_work_orders: false,
|
||||
can_decide_commands: false,
|
||||
can_execute_commands: false
|
||||
},
|
||||
work_order_snapshot: null
|
||||
};
|
||||
|
||||
const appointmentTask: OperationalTask = {
|
||||
...task,
|
||||
task_id: appointmentTaskId,
|
||||
task_type: "external_work_order",
|
||||
workflow: "general",
|
||||
lifecycle_status: "open",
|
||||
active_run: null,
|
||||
status: "queued",
|
||||
priority: "normal",
|
||||
execution_request_id: "74000000-0000-4000-8000-000000000002",
|
||||
title: "城北阀井预约维修",
|
||||
summary: "外围工单同步的预约维修任务",
|
||||
source: {
|
||||
kind: "external_system",
|
||||
external_id: "sample-work-order-1",
|
||||
external_revision: 2,
|
||||
event_kind: "general"
|
||||
},
|
||||
trigger: {
|
||||
kind: "external_sync",
|
||||
occurred_at: "2026-09-23T08:30:00Z",
|
||||
detail: "预约 09/23 18:00 由北片区维修组处理"
|
||||
},
|
||||
scheduled_for: "2026-09-23T10:00:00Z",
|
||||
agent_session_id: null,
|
||||
current_report_revision: null,
|
||||
latest_report: null,
|
||||
evidence: [],
|
||||
actions: [],
|
||||
work_order_snapshot: {
|
||||
work_order_id: "50000000-0000-4000-8000-000000000001",
|
||||
revision: 2,
|
||||
title: "城北阀井预约维修",
|
||||
summary: "编辑后的预约维修说明",
|
||||
status: "open",
|
||||
priority: "normal",
|
||||
assignee: "测试班组乙",
|
||||
scheduled_at: "2026-09-23T10:00:00Z",
|
||||
work_order_type: "general",
|
||||
location: "联调测试点东侧",
|
||||
scenario_code: "APPOINTMENT-TEST-001",
|
||||
subtasks: [{
|
||||
id: "field-service",
|
||||
title: "阀井现场维修",
|
||||
summary: "按预约窗口完成现场维修并回填结果。",
|
||||
status: "pending",
|
||||
scheduled_at: "2026-09-23T10:00:00Z",
|
||||
location: "联调测试点东侧",
|
||||
assignee: "测试班组乙",
|
||||
duration_minutes: 60,
|
||||
reply_window_minutes: 30,
|
||||
stages: [
|
||||
{ id: "dispatch", sequence: 1, title: "派发", status: "pending", owner: "调度岗", time_label: "待安排", description: "确认预约窗口。" },
|
||||
{ id: "execution", sequence: 2, title: "执行", status: "pending", owner: "测试班组乙", time_label: "待安排", description: "完成现场维修。" },
|
||||
{ id: "reply", sequence: 3, title: "复令", status: "pending", owner: "调度岗", time_label: "待安排", description: "核对回执并闭环。" }
|
||||
],
|
||||
reply_requirements: ["回填现场处理结果"]
|
||||
}],
|
||||
origin: "synthetic",
|
||||
updated_at: "2026-09-23T08:30:00Z"
|
||||
}
|
||||
};
|
||||
|
||||
const summary = (item: OperationalTask): OperationalTaskSummary => {
|
||||
const {
|
||||
latest_plan,
|
||||
latest_report,
|
||||
evidence,
|
||||
actions,
|
||||
permissions,
|
||||
work_order_snapshot,
|
||||
...taskSummary
|
||||
} = item;
|
||||
void latest_plan; void latest_report; void evidence; void actions; void permissions; void work_order_snapshot;
|
||||
return taskSummary;
|
||||
};
|
||||
|
||||
let scheduleRequests = 0;
|
||||
|
||||
const runtimeSnapshot = {
|
||||
availability: "not_configured", source: "none",
|
||||
scenario_availability: "not_configured", artifact_availability: "not_configured",
|
||||
schedule_availability: "not_configured", work_order_availability: "not_configured",
|
||||
alert_availability: "not_configured", generated_at: "2026-09-23T08:30:00Z",
|
||||
conditions: [], alerts: [], scenarios: [], latest_analysis: null, work_orders: []
|
||||
};
|
||||
await page.route("http://server.test/api/v1/workbench/sync-snapshot", (route) =>
|
||||
route.fulfill({ json: {
|
||||
schema: "workbench-sync-snapshot@1", event_cursor: 0, runtime: runtimeSnapshot,
|
||||
tasks: [summary(task), summary(appointmentTask)], notifications: [],
|
||||
generated_at: "2026-09-23T08:30:00Z"
|
||||
} })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/workbench/events**", (route) =>
|
||||
route.fulfill({ contentType: "text/event-stream", body: ": test keepalive\n\n" })
|
||||
);
|
||||
|
||||
await page.route("http://server.test/api/v1/operational-task-notifications**", (route) =>
|
||||
route.fulfill({ json: { items: [], limit: 100, offset: 0, next_offset: null } })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/operational-tasks**", async (route) => {
|
||||
const request = route.request();
|
||||
const pathname = new URL(request.url()).pathname;
|
||||
if (pathname.endsWith("/operational-tasks/schedules")) {
|
||||
scheduleRequests += 1;
|
||||
return route.fulfill({ json: { items: [] } });
|
||||
}
|
||||
if (request.method() === "GET" && pathname.endsWith("/operational-tasks")) {
|
||||
return route.fulfill({ json: { items: [summary(task), summary(appointmentTask)], limit: 50, offset: 0, next_offset: null } });
|
||||
}
|
||||
if (request.method() === "GET" && pathname.endsWith(`/operational-tasks/${taskId}`)) {
|
||||
return route.fulfill({ json: task });
|
||||
}
|
||||
if (request.method() === "GET" && pathname.endsWith(`/operational-tasks/${appointmentTaskId}`)) {
|
||||
return route.fulfill({ json: appointmentTask });
|
||||
}
|
||||
if (request.method() === "POST" && pathname.endsWith(`/actions/${actionId}/decision`)) {
|
||||
return route.fulfill({ status: 409, json: { detail: "动作版本冲突:expected 3, actual 4" } });
|
||||
}
|
||||
if (request.method() === "POST" && pathname.endsWith(`/actions/${actionId}/reconcile`)) {
|
||||
const receiptAction = {
|
||||
...task.actions[0],
|
||||
version: 5,
|
||||
execution_status: "succeeded",
|
||||
receipt: {
|
||||
adapter: "scada-command-adapter",
|
||||
receipt_id: "SCADA-RECEIPT-20260923-01",
|
||||
submitted_at: "2026-09-23T08:28:00Z",
|
||||
completed_at: "2026-09-23T08:29:00Z",
|
||||
external_status: "completed",
|
||||
detail: "V-102 开度已调整至 35%"
|
||||
},
|
||||
execution_detail: "设备回执确认完成"
|
||||
};
|
||||
task = { ...task, actions: [receiptAction] };
|
||||
return route.fulfill({ json: receiptAction });
|
||||
}
|
||||
return route.fulfill({ status: 404, json: {} });
|
||||
});
|
||||
|
||||
const openTask = async () => {
|
||||
await page.getByRole("button", { name: "工况任务", exact: true }).click();
|
||||
const conditionPanel = page.getByRole("region", { name: "工况任务", exact: true });
|
||||
const expandButton = conditionPanel.getByRole("button", { name: "展开工况任务" });
|
||||
if (await expandButton.isVisible()) await expandButton.click();
|
||||
await conditionPanel.getByRole("button", { name: /调查任务/ }).click();
|
||||
await conditionPanel.getByText("阀门调节审批", { exact: true }).first().click();
|
||||
return conditionPanel;
|
||||
};
|
||||
|
||||
await page.goto("/");
|
||||
let conditionPanel = await openTask();
|
||||
await expect(conditionPanel.getByRole("navigation", { name: "工况任务分类" })).toBeVisible();
|
||||
await expect(conditionPanel.getByRole("button", { name: /定时任务/ })).toContainText("模拟数据");
|
||||
await expect(conditionPanel.getByRole("button", { name: /调查任务/ })).toContainText("后端");
|
||||
await expect(conditionPanel.getByRole("button", { name: /预约任务/ })).toContainText("后端");
|
||||
await expect(conditionPanel.getByRole("button", { name: /预约任务/ })).toContainText("演示");
|
||||
await conditionPanel.getByRole("button", { name: "配置定时流程" }).click();
|
||||
await conditionPanel.getByLabel("定时流程名称").fill("夜间阀门巡检");
|
||||
await conditionPanel.getByLabel("任务说明").fill("每十五分钟检查预约维修涉及的边界阀门。");
|
||||
await conditionPanel.getByRole("button", { name: "创建定时流程" }).click();
|
||||
await expect(conditionPanel.getByText("夜间阀门巡检", { exact: true }).first()).toBeVisible();
|
||||
await conditionPanel.getByRole("button", { name: /预约任务/ }).click();
|
||||
await expect(conditionPanel.getByText("城北阀井预约维修", { exact: true }).first()).toBeVisible();
|
||||
await expect(conditionPanel.getByText(/^演示工单:/).first()).toBeVisible();
|
||||
await conditionPanel.getByText("城北阀井预约维修", { exact: true }).first().click();
|
||||
await expect(conditionPanel.getByText(/阀井现场维修.*待处理/)).toBeVisible();
|
||||
await expect(conditionPanel.getByText("测试班组乙", { exact: true }).first()).toBeVisible();
|
||||
await expect(conditionPanel.getByText(/位置:联调测试点东侧/)).toBeVisible();
|
||||
await expect(conditionPanel.getByText("编辑后的预约维修说明", { exact: true })).toBeVisible();
|
||||
await expect(conditionPanel.getByText("预约 09/23 18:00 由北片区维修组处理", { exact: true })).toHaveCount(0);
|
||||
await page.waitForTimeout(3_500);
|
||||
await page.addStyleTag({ content: "[data-sonner-toaster] { display: none !important; }" });
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await page.screenshot({ path: "/tmp/tjwater-operational-ui-review/task-categories-desktop.png" });
|
||||
await conditionPanel.getByRole("button", { name: /调查任务/ }).click();
|
||||
expect(scheduleRequests).toBe(0);
|
||||
await expect(conditionPanel.getByText("上一轮报告,当前任务待人工", { exact: true })).toBeVisible();
|
||||
await expect(conditionPanel.getByText(/第\s*[12]\s*代|generation/i)).toHaveCount(0);
|
||||
const actionCard = conditionPanel.locator(`[data-action-id="${actionId}"]`);
|
||||
await expect(actionCard.getByText("命令类型", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("对象类型", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("目标对象", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("执行参数", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("影响说明", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("调节阀门", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("阀门", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("开度 35%", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText("确认依据", { exact: true })).toBeVisible();
|
||||
await expect(actionCard.getByText(/V-102 下游压力.*SCADA 压力监测/)).toBeVisible();
|
||||
await expect(actionCard.getByText(/当前账号仅可查看/)).toBeVisible();
|
||||
await expect(actionCard.getByText(/operational_action\.command\.approve/)).toHaveCount(0);
|
||||
await expect(actionCard.getByRole("button", { name: "批准下发命令" })).toHaveCount(0);
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await testInfo.attach("approval-viewer-desktop", {
|
||||
body: await actionCard.screenshot({ path: testInfo.outputPath("approval-viewer-desktop.png") }),
|
||||
contentType: "image/png"
|
||||
});
|
||||
|
||||
task = {
|
||||
...task,
|
||||
permissions: { ...task.permissions, can_decide_commands: true }
|
||||
};
|
||||
await page.reload();
|
||||
conditionPanel = await openTask();
|
||||
await conditionPanel.getByRole("button", { name: "批准下发命令" }).click();
|
||||
await expect(conditionPanel.getByRole("alert")).toContainText("动作版本冲突:expected 3, actual 4");
|
||||
const refreshButton = conditionPanel.getByRole("button", { name: "刷新后重新确认" });
|
||||
|
||||
task = {
|
||||
...task,
|
||||
actions: [{
|
||||
...task.actions[0],
|
||||
version: 4,
|
||||
approval_status: "approved",
|
||||
execution_status: "unknown",
|
||||
execution_request_id: "75000000-0000-4000-8000-000000000001",
|
||||
decided_by: "dispatcher",
|
||||
decided_at: "2026-09-23T08:27:00Z"
|
||||
}],
|
||||
permissions: { ...task.permissions, can_execute_commands: true }
|
||||
};
|
||||
await refreshButton.click();
|
||||
const reconcileButton = conditionPanel.getByRole("button", { name: "核对执行回执" });
|
||||
await expect(reconcileButton).toBeVisible();
|
||||
await reconcileButton.click();
|
||||
const receipt = conditionPanel.getByRole("region", { name: "执行回执" });
|
||||
await expect(receipt).toContainText("SCADA-RECEIPT-20260923-01");
|
||||
await expect(receipt).toContainText("已完成");
|
||||
await expect(receipt).toContainText("V-102 开度已调整至 35%");
|
||||
await testInfo.attach("approval-receipt-desktop", {
|
||||
body: await actionCard.screenshot({ path: testInfo.outputPath("approval-receipt-desktop.png") }),
|
||||
contentType: "image/png"
|
||||
});
|
||||
});
|
||||
|
||||
test("后端没有调查任务时显示空态,分类标签按压不缩放", async ({ page }, testInfo) => {
|
||||
test.setTimeout(60_000);
|
||||
await mockAgentApi(page);
|
||||
await page.route("**/runtime-config.js", (route) => route.fulfill({
|
||||
contentType: "text/javascript",
|
||||
body: `globalThis.__TJWATER_CONFIG__=${JSON.stringify({
|
||||
TJWATER_AUTH_MODE: "disabled",
|
||||
TJWATER_SERVER_API_BASE_URL: "http://server.test",
|
||||
TJWATER_MAP_URL: "http://map.test/geoserver",
|
||||
TJWATER_GEOSERVER_WORKSPACE: "tjwater_next"
|
||||
})}`
|
||||
}));
|
||||
await page.route("http://server.test/api/v1/projects**", (route) => route.fulfill({ json: {
|
||||
items: [{ project_id: projectId, gs_workspace: "tjwater_next", status: "active" }],
|
||||
total: 1, limit: 1000, offset: 0
|
||||
} }));
|
||||
await page.route("http://server.test/api/v1/scada-devices**", (route) =>
|
||||
route.fulfill({ json: { items: [], total: 0, limit: 1000, offset: 0 } })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/operational-task-notifications**", (route) =>
|
||||
route.fulfill({ json: { items: [], limit: 100, offset: 0, next_offset: null } })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/operational-tasks**", (route) =>
|
||||
route.fulfill({ json: { items: [], limit: 50, offset: 0, next_offset: null } })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/workbench/sync-snapshot", (route) =>
|
||||
route.fulfill({ json: {
|
||||
schema: "workbench-sync-snapshot@1", event_cursor: 0,
|
||||
runtime: {
|
||||
availability: "not_configured", source: "none",
|
||||
scenario_availability: "not_configured", artifact_availability: "not_configured",
|
||||
schedule_availability: "not_configured", work_order_availability: "not_configured",
|
||||
alert_availability: "not_configured", generated_at: "2026-09-23T08:30:00Z",
|
||||
conditions: [], alerts: [], scenarios: [], latest_analysis: null, work_orders: []
|
||||
},
|
||||
tasks: [], notifications: [], generated_at: "2026-09-23T08:30:00Z"
|
||||
} })
|
||||
);
|
||||
await page.route("http://server.test/api/v1/workbench/events**", (route) =>
|
||||
route.fulfill({ contentType: "text/event-stream", body: ": test keepalive\n\n" })
|
||||
);
|
||||
await page.route("http://map.test/**", (route) => route.fulfill({ status: 204 }));
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByRole("button", { name: "工况任务", exact: true }).press("Enter");
|
||||
const conditionPanel = page.getByRole("region", { name: "工况任务", exact: true });
|
||||
const expandButton = conditionPanel.getByRole("button", { name: "展开工况任务" });
|
||||
if (await expandButton.isVisible()) await expandButton.click();
|
||||
await conditionPanel.getByRole("button", { name: /调查任务/ }).click();
|
||||
await expect(conditionPanel.getByText("暂无任务", { exact: true })).toBeVisible();
|
||||
await expect(conditionPanel.getByText("新任务进入当前分类后会出现在这里。", { exact: true })).toBeVisible();
|
||||
for (const width of [1280, 375, 320]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
if (width <= 375) {
|
||||
const openMobileTasks = page.getByRole("button", { name: "打开工况任务", exact: true });
|
||||
if (await openMobileTasks.isVisible()) await openMobileTasks.click();
|
||||
const expandMobile = conditionPanel.getByRole("button", { name: "展开工况任务" });
|
||||
if (await expandMobile.isVisible()) await expandMobile.click();
|
||||
}
|
||||
const categories = conditionPanel.getByRole("navigation", { name: "工况任务分类" });
|
||||
for (const label of ["定时任务", "调查任务", "预约任务"]) {
|
||||
const tab = categories.getByRole("button", { name: new RegExp(label) });
|
||||
await tab.hover();
|
||||
const before = await tab.boundingBox();
|
||||
expect(before).not.toBeNull();
|
||||
await page.mouse.down();
|
||||
try {
|
||||
await tab.evaluate(async (element) => {
|
||||
await Promise.all(element.getAnimations().map((animation) => animation.finished));
|
||||
});
|
||||
await expect.poll(() => tab.evaluate((element) => getComputedStyle(element).scale)).toBe("none");
|
||||
const pressed = await tab.boundingBox();
|
||||
expect(pressed).not.toBeNull();
|
||||
expect(Math.abs(pressed!.width - before!.width)).toBeLessThan(0.5);
|
||||
expect(Math.abs(pressed!.height - before!.height)).toBeLessThan(0.5);
|
||||
} finally {
|
||||
await page.mouse.up();
|
||||
}
|
||||
await expect(tab).toHaveAttribute("aria-current", "page");
|
||||
}
|
||||
await categories.screenshot({ path: testInfo.outputPath(`task-tabs-${width}.png`) });
|
||||
}
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
const investigationTab = conditionPanel.getByRole("navigation", { name: "工况任务分类" })
|
||||
.getByRole("button", { name: /调查任务/ });
|
||||
await investigationTab.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(investigationTab).toHaveAttribute("aria-current", "page");
|
||||
await expect(investigationTab).toBeFocused();
|
||||
await expect.poll(() => investigationTab.evaluate((element) => getComputedStyle(element).transitionProperty)).toBe("none");
|
||||
});
|
||||
Reference in New Issue
Block a user