Files
SystemSimulationApp/frontend/tests/e2e/fixtures.ts
T
ljz df1d676131 完善AMESim可配置参数交互
补充气动孔口、动态管路、UD00 与弹性接触组件的离散选项、条件显示和折叠分组。

增加目录协议、非法选项和前端参数表交互回归测试,不修改现有求解公式。
2026-08-04 01:27:17 +08:00

1015 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, type Page } from "@playwright/test";
const physicalPort = (
name: string,
side: "left" | "right",
nominalRole: "inlet" | "outlet" | "bidirectional",
) => ({
name,
kind: "physical",
domain: "pneumatic",
nominalRole,
positiveFlowDirection: "intoComponent",
side,
order: side === "left" ? 0 : 1,
});
const ud00StageParameters = Array.from({ length: 8 }, (_, offset) => offset + 1)
.flatMap((stage) => {
const visibleWhen =
stage === 1
? {}
: {
visibleWhen: [
{
parameter: "nstages",
values: Array.from(
{ length: 9 - stage },
(_, offset) => stage + offset,
),
},
],
};
return [
{
name: `start${stage}`,
label: `第 ${stage} 段起点`,
quantity: "dimensionless",
unit: "",
default: stage === 1 ? 0 : 1,
minimumExclusive: false,
description: `第 ${stage} 段开始时的输出值。`,
...visibleWhen,
},
{
name: `end${stage}`,
label: `第 ${stage} 段终点`,
quantity: "dimensionless",
unit: "",
default: 1,
minimumExclusive: false,
description: `第 ${stage} 段结束时的输出值。`,
...visibleWhen,
},
{
name: `t${stage}`,
label: `第 ${stage} 段时长`,
quantity: "time",
unit: "s",
default: stage === 1 ? 1 : 0,
minimum: 0,
minimumExclusive: false,
description: `第 ${stage} 段的持续时间。`,
...visibleWhen,
},
];
});
const ud00StageParameterNames = Array.from(
{ length: 8 },
(_, offset) => offset + 1,
).flatMap((stage) => [`start${stage}`, `end${stage}`, `t${stage}`]);
export const componentCatalog = {
schemaVersion: 1,
libraries: [
{
id: "e2e-library",
label: "自动化测试组件库",
version: "1.0.0",
sourcePackage: "tests.e2e",
temporary: true,
order: 1,
components: [
{
type: "amesim_ideal_air_medium",
label: "空气介质定义",
modelType: "amesim_ideal_air_medium",
modelVersion: "0.2.0",
symbol: "amesim_ideal_air_medium",
order: 1,
category: {
id: "media",
label: "介质物性",
order: 1,
},
role: "amesimGasMediumDefinition",
ports: [],
parameters: [
{
name: "gi",
label: "介质定义索引(gi)",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 99,
minimumExclusive: false,
description: "介质定义索引由画布自动分配;索引 0 保留给内置理想空气。",
},
{
name: "property_model",
label: "物性计算模型",
quantity: "dimensionless",
unit: "",
default: 0,
minimum: 0,
maximum: 0,
minimumExclusive: false,
editor: "amesimGasPropertyModel",
options: [{ value: 0, label: "理想气体" }],
description: "选择空气介质采用的物性计算模型。",
},
],
},
{
type: "amesim_helium_medium",
label: "氦气介质定义",
modelType: "amesim_helium_medium",
modelVersion: "0.1.0",
symbol: "amesim_helium_medium",
order: 2,
category: {
id: "media",
label: "介质物性",
order: 1,
},
role: "amesimGasMediumDefinition",
ports: [],
parameters: [
{
name: "gi",
label: "介质定义索引(gi)",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 99,
minimumExclusive: false,
description: "介质定义索引由画布自动分配;索引 0 保留给内置理想空气。",
},
{
name: "property_model",
label: "物性计算模型",
quantity: "dimensionless",
unit: "",
default: 0,
minimum: 0,
maximum: 0,
minimumExclusive: false,
editor: "amesimGasPropertyModel",
options: [{ value: 0, label: "Peng–Robinson" }],
description: "氦气当前使用 Peng–Robinson 状态方程计算物性。",
},
],
},
{
type: "amesim_pneumatic_test",
label: "AMESim 气动测试元件",
modelType: "amesim_pneumatic_test",
modelVersion: "1.0.0",
symbol: "symbol-not-registered",
order: 3,
category: {
id: "pneumatic",
label: "气动元件",
order: 2,
},
ports: [
physicalPort("port_a", "left", "bidirectional"),
physicalPort("port_b", "right", "bidirectional"),
],
parameters: [
{
name: "gi",
label: "介质物性模型(gi)",
quantity: "dimensionless",
unit: "",
default: 0,
minimum: 0,
maximum: 99,
minimumExclusive: false,
editor: "amesimGasReference",
description: "选择该气动元件引用的画布介质定义索引;0 表示内置理想空气。",
},
{
name: "reference_pressure",
label: "参考压力",
quantity: "pressure",
unit: "Pa",
default: 100000,
minimum: 0,
minimumExclusive: true,
description: "用于验证参数表单位换算和 XML 基础单位输出的参考压力。",
},
{
name: "reference_temperature",
label: "参考温度",
quantity: "temperature",
unit: "K",
default: 300,
minimum: 0,
minimumExclusive: false,
description: "用于验证带偏移量单位的数值换算和显示精度。",
},
],
},
{
type: "generic_sensor",
label: "通用测试元件",
modelType: "generic_sensor",
modelVersion: "1.0.0",
symbol: "symbol-not-registered",
order: 4,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [
physicalPort("port_a", "left", "bidirectional"),
physicalPort("port_b", "right", "bidirectional"),
],
parameters: [
{
name: "gain",
label: "增益",
quantity: "dimensionless",
unit: "",
default: 1,
minimumExclusive: false,
description: "传感器输入值的无量纲放大倍数。",
},
],
},
{
type: "choice_visibility_test",
label: "离散参数测试元件",
modelType: "choice_visibility_test",
modelVersion: "1.0.0",
symbol: "symbol-not-registered",
order: 5,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameters: [
{
name: "mode",
label: "工作模式",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 3,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "基础模式" },
{ value: 2, label: "高级模式" },
{ value: 3, label: "专家模式" },
],
},
{
name: "variant",
label: "高级变体",
quantity: "dimensionless",
unit: "",
default: 10,
minimum: 10,
maximum: 20,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 10, label: "变体 A" },
{ value: 20, label: "变体 B" },
],
},
{
name: "basic_value",
label: "基础参数",
quantity: "dimensionless",
unit: "",
default: 1,
minimumExclusive: false,
visibleWhen: [{ parameter: "mode", values: [1] }],
},
{
name: "advanced_value",
label: "高级参数",
quantity: "dimensionless",
unit: "",
default: 1,
minimumExclusive: false,
visibleWhen: [{ parameter: "mode", values: [2, 3] }],
},
{
name: "combined_value",
label: "组合参数",
quantity: "dimensionless",
unit: "",
default: 3,
minimumExclusive: false,
visibleWhen: [
{ parameter: "mode", values: [2, 3] },
{ parameter: "variant", values: [20] },
],
},
],
},
{
type: "amesim_mecmas21",
label: "MECMAS21 编码测试元件",
modelType: "amesim_mecmas21",
modelVersion: "1.0.0",
symbol: "symbol-not-registered",
order: 6,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameterGroups: [
{
id: "friction",
label: "摩擦",
order: 10,
parameters: ["frictionType", "strib", "rvisc"],
defaultExpanded: false,
},
{
id: "endstops",
label: "限位",
order: 20,
parameters: ["discContactOption", "xmin"],
defaultExpanded: false,
},
],
parameters: [
{
name: "mass",
label: "质量",
quantity: "mass",
unit: "kg",
default: 1,
minimum: 0,
minimumExclusive: true,
},
{
name: "rvisc",
label: "黏性摩擦系数",
quantity: "translational_damping",
unit: "N/(m/s)",
default: 0,
minimum: 0,
minimumExclusive: false,
description: "用于验证折叠摩擦参数仍支持详情提示和值保留。",
visibleWhen: [{ parameter: "useFriction", values: [2] }],
},
{
name: "xmin",
label: "下位移限位",
quantity: "length",
unit: "m",
default: -1,
minimumExclusive: false,
visibleWhen: [{ parameter: "stoptype", values: [1, 2, 3] }],
},
{
name: "useFriction",
label: "启用摩擦",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "禁用" },
{ value: 2, label: "启用" },
],
},
{
name: "stoptype",
label: "限位类型",
quantity: "dimensionless",
unit: "",
default: 4,
minimum: 1,
maximum: 4,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "理想限位" },
{ value: 2, label: "弹性限位" },
{ value: 3, label: "恢复碰撞" },
{ value: 4, label: "无限位" },
],
},
{
name: "discContactOption",
label: "允许负接触力",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "允许负接触力" },
{ value: 2, label: "不允许负接触力" },
],
visibleWhen: [{ parameter: "stoptype", values: [2] }],
},
{
name: "strib",
label: "Stribeck 选项",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "禁用" },
{ value: 2, label: "启用" },
],
visibleWhen: [
{ parameter: "useFriction", values: [2] },
{ parameter: "stoptype", values: [1, 2, 4] },
{ parameter: "frictionType", values: [2] },
],
},
{
name: "frictionType",
label: "摩擦类型",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "简单" },
{ value: 2, label: "高级" },
],
visibleWhen: [
{ parameter: "useFriction", values: [2] },
{ parameter: "stoptype", values: [1, 2, 4] },
],
},
{
name: "unrelatedCode",
label: "无关编码",
quantity: "dimensionless",
unit: "",
default: 7,
minimumExclusive: false,
},
],
},
{
type: "amesim_pnor001",
label: "PNOR001 常系数气动孔口",
modelType: "amesim_pnor001",
modelVersion: "0.2.0",
symbol: "symbol-not-registered",
order: 7,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameterGroups: [
{
id: "flow_coefficient",
label: "流量系数",
order: 10,
parameters: ["cq", "area", "Cv", "Kv"],
defaultExpanded: false,
},
],
parameters: [
{
name: "cq",
label: "流量系数 Cq",
quantity: "dimensionless",
unit: "",
default: 0.72,
minimum: 1e-10,
maximum: 1,
minimumExclusive: false,
description: "孔口实际质量流量相对于理想可压缩流量的无量纲修正系数。",
visibleWhen: [{ parameter: "flowset", values: [1] }],
},
{
name: "area",
label: "孔口面积",
quantity: "area",
unit: "m2",
default: 5e-6,
minimum: 0,
maximum: 1,
minimumExclusive: false,
description: "选择 Cq/面积方式时用于流量计算的有效孔口面积。",
visibleWhen: [{ parameter: "flowset", values: [1] }],
},
{
name: "Cv",
label: "流量系数 Cv",
quantity: "dimensionless",
unit: "",
default: 0.5,
minimum: 0,
minimumExclusive: false,
description: "选择 Cv 方式时使用的英制流量系数。",
visibleWhen: [{ parameter: "flowset", values: [2] }],
},
{
name: "Kv",
label: "流量系数 Kv",
quantity: "dimensionless",
unit: "",
default: 0.4,
minimum: 0,
minimumExclusive: false,
description: "选择 Kv 方式时使用的公制流量系数。",
visibleWhen: [{ parameter: "flowset", values: [3] }],
},
{
name: "flowset",
label: "流量系数设置",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 3,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "Cq" },
{ value: 2, label: "Cv" },
{ value: 3, label: "Kv" },
],
description: "流量参数方式:1 使用 Cq 和面积,2 使用 Cv,3 使用 Kv。",
},
],
},
{
type: "amesim_pnl0001",
label: "PNL0001 C-R 动态管路",
modelType: "amesim_pnl0001",
modelVersion: "0.2.0",
symbol: "symbol-not-registered",
order: 8,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameterGroups: [
{
id: "thermodynamics",
label: "热力学",
order: 10,
parameters: ["k", "kth", "extemp"],
defaultExpanded: false,
},
],
parameters: [
{
name: "diam",
label: "管径",
quantity: "length",
unit: "m",
default: 0.01,
minimum: 0,
minimumExclusive: true,
description: "管路的有效内径,用于计算流通面积、储气容积和摩擦压降。",
},
{
name: "k",
label: "多方指数",
quantity: "dimensionless",
unit: "",
default: 1.35,
minimum: 0,
maximum: 2,
minimumExclusive: true,
visibleWhen: [{ parameter: "mode", values: [1] }],
},
{
name: "kth",
label: "换热系数",
quantity: "heat_transfer_coefficient",
unit: "W/(m2*K)",
default: 0,
minimum: 0,
minimumExclusive: false,
visibleWhen: [{ parameter: "mode", values: [2] }],
},
{
name: "extemp",
label: "外部温度",
quantity: "temperature",
unit: "K",
default: 293.15,
minimum: 0,
minimumExclusive: true,
visibleWhen: [{ parameter: "mode", values: [2] }],
},
{
name: "mode",
label: "热模型",
quantity: "dimensionless",
unit: "",
default: 2,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "多方过程" },
{ value: 2, label: "带换热" },
],
},
],
},
{
type: "amesim_ud00",
label: "UD00 分段线性信号",
modelType: "amesim_ud00",
modelVersion: "0.2.0",
symbol: "symbol-not-registered",
order: 9,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameterGroups: [
{
id: "stages",
label: "分段参数",
order: 10,
parameters: ud00StageParameterNames,
defaultExpanded: false,
},
],
parameters: [
{
name: "tstart",
label: "启动时间",
quantity: "time",
unit: "s",
default: 0,
minimumExclusive: false,
description: "分段信号开始输出第一段之前的等待时间。",
},
...ud00StageParameters,
{
name: "nstages",
label: "段数",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 8,
minimumExclusive: false,
editor: "choice",
options: Array.from({ length: 8 }, (_, offset) => ({
value: offset + 1,
label: String(offset + 1),
})),
description: "参与输出计算的有效线性分段数量。",
},
{
name: "iscyclic",
label: "循环",
quantity: "dimensionless",
unit: "",
default: 0,
minimum: 0,
maximum: 1,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 0, label: "否" },
{ value: 1, label: "是" },
],
description: "当前公共协议编码:0 表示单次输出,1 表示循环输出。",
},
],
},
{
type: "amesim_lstp00a",
label: "LSTP00A 弹性接触",
modelType: "amesim_lstp00a",
modelVersion: "0.2.0",
symbol: "symbol-not-registered",
order: 10,
category: {
id: "generic",
label: "通用组件",
order: 1,
},
ports: [],
parameterGroups: [
{
id: "stiffness",
label: "刚度",
order: 10,
parameters: ["kcont", "G", "sdiam", "wdiam", "na"],
defaultExpanded: false,
},
{
id: "contact",
label: "接触",
order: 20,
parameters: ["gap0", "rcont", "Pdis"],
defaultExpanded: false,
},
],
parameters: [
{
name: "na",
label: "有效圈数",
quantity: "dimensionless",
unit: "",
default: 10,
minimum: 0,
minimumExclusive: true,
visibleWhen: [{ parameter: "stiffmode", values: [2] }],
},
{
name: "gap0",
label: "初始间隙",
quantity: "length",
unit: "m",
default: 0,
minimumExclusive: false,
},
{
name: "kcont",
label: "接触刚度",
quantity: "translational_stiffness",
unit: "N/m",
default: 1e6,
minimum: 0,
minimumExclusive: false,
visibleWhen: [{ parameter: "stiffmode", values: [1] }],
},
{
name: "G",
label: "剪切模量",
quantity: "pressure",
unit: "Pa",
default: 8.57e10,
minimum: 0,
minimumExclusive: false,
visibleWhen: [{ parameter: "stiffmode", values: [2] }],
},
{
name: "sdiam",
label: "弹簧直径",
quantity: "length",
unit: "m",
default: 0.02,
minimum: 0,
minimumExclusive: false,
visibleWhen: [{ parameter: "stiffmode", values: [2] }],
},
{
name: "wdiam",
label: "线径",
quantity: "length",
unit: "m",
default: 0.002,
minimum: 0,
minimumExclusive: false,
visibleWhen: [{ parameter: "stiffmode", values: [2] }],
},
{
name: "rcont",
label: "接触阻尼",
quantity: "translational_damping",
unit: "N/(m/s)",
default: 0,
minimum: 0,
minimumExclusive: false,
},
{
name: "Pdis",
label: "满阻尼穿透",
quantity: "length",
unit: "m",
default: 1e-7,
minimum: 0,
minimumExclusive: false,
},
{
name: "stiffmode",
label: "刚度模式",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "数值刚度" },
{ value: 2, label: "几何参数" },
],
},
{
name: "discContactOption",
label: "允许负接触力",
quantity: "dimensionless",
unit: "",
default: 1,
minimum: 1,
maximum: 2,
minimumExclusive: false,
editor: "choice",
options: [
{ value: 1, label: "允许负接触力" },
{ value: 2, label: "不允许负接触力" },
],
},
],
},
],
},
],
};
const projectPort = (
name: string,
side: "left" | "right",
nominalRole: "inlet" | "outlet" | "bidirectional",
) => ({
name,
kind: "physical",
domain: "pneumatic",
nominalRole,
positiveFlowDirection: "intoComponent",
side,
});
export const wideProject = {
name: "demo-system",
nodes: Array.from({ length: 4 }, (_, index) => ({
id: `generic_sensor_${index + 1}`,
type: "simulationComponent",
position: {
x: index * 620,
y: index % 2 === 0 ? 0 : 260,
},
data: {
label: `generic_sensor_${index + 1}`,
componentType: "generic_sensor",
modelType: "generic_sensor",
symbol: "symbol-not-registered",
ports: [
projectPort("port_a", "left", "bidirectional"),
projectPort("port_b", "right", "bidirectional"),
],
parameters: { gain: 1 },
parameterUnits: { gain: "" },
rotation: 0,
mirrored: false,
},
})),
edges: Array.from({ length: 3 }, (_, index) => ({
id: `edge-${index + 1}`,
source: `generic_sensor_${index + 1}`,
target: `generic_sensor_${index + 2}`,
sourceHandle: "port_b",
targetHandle: "port_a",
})),
simulation: {
t_start: 0,
t_stop: 10,
step: 0.1,
max_step: 0.05,
method: "RK45",
},
};
export const resultSnapshot = {
id: "e2e-result",
createdAt: "2026-07-28T10:00:00.000Z",
project: wideProject,
result: {
success: true,
status: "completed",
partial: false,
message: "Simulation completed",
simulatedUntil: 10,
requestedStopTime: 10,
variables: [
{
key: "generic_sensor_1.value",
componentId: "generic_sensor_1",
componentType: "generic_sensor",
scope: "component",
portName: null,
name: "value",
label: "数值",
quantity: "dimensionless",
unit: "",
category: "state",
order: 1,
},
],
final: { "generic_sensor_1.value": 1 },
series: {
time: [0, 5, 10],
"generic_sensor_1.value": [0, 0.5, 1],
},
diagnostics: {
pressureFlow: {
solveCount: 3,
maxScaledResidual: 0,
maxEvaluationsPerSolve: 1,
},
stream: {
maxIterationsPerSolve: 1,
},
stateCount: 1,
sampleCount: 3,
},
},
};
export async function prepareApp(page: Page) {
await page.addInitScript(() => {
const preparedKey = "system-simulation-flow:e2e-storage-prepared";
if (window.sessionStorage.getItem(preparedKey) !== "1") {
window.localStorage.clear();
window.sessionStorage.clear();
window.sessionStorage.setItem(preparedKey, "1");
}
window.location.hash = "#/modeling";
});
await page.route("**/api/components/catalog", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(componentCatalog),
});
});
}
export async function expectAllNodesInsideCanvas(
page: Page,
canvasSelector: string,
expectedCount = wideProject.nodes.length,
) {
const canvas = page.locator(canvasSelector);
await expect(canvas).toBeVisible();
const nodes = canvas.locator(".react-flow__node");
await expect(nodes).toHaveCount(expectedCount);
await expect
.poll(
async () =>
page.evaluate(
({ selector, count }) => {
const canvasElement = document.querySelector<HTMLElement>(selector);
const nodeElements = Array.from(
canvasElement?.querySelectorAll<HTMLElement>(".react-flow__node") ?? [],
);
if (!canvasElement || nodeElements.length !== count) {
return false;
}
const canvasBounds = canvasElement.getBoundingClientRect();
const tolerance = 3;
return nodeElements.every((node) => {
const bounds = node.getBoundingClientRect();
return (
bounds.left >= canvasBounds.left - tolerance &&
bounds.top >= canvasBounds.top - tolerance &&
bounds.right <= canvasBounds.right + tolerance &&
bounds.bottom <= canvasBounds.bottom + tolerance
);
});
},
{ selector: canvasSelector, count: expectedCount },
),
{ message: `所有节点应完整显示在 ${canvasSelector} 内` },
)
.toBe(true);
}