214 lines
5.4 KiB
TypeScript
214 lines
5.4 KiB
TypeScript
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,
|
|
});
|
|
|
|
export const componentCatalog = {
|
|
schemaVersion: 1,
|
|
libraries: [
|
|
{
|
|
id: "e2e-library",
|
|
label: "自动化测试组件库",
|
|
version: "1.0.0",
|
|
sourcePackage: "tests.e2e",
|
|
temporary: true,
|
|
order: 1,
|
|
components: [
|
|
{
|
|
type: "generic_sensor",
|
|
label: "通用测试元件",
|
|
modelType: "generic_sensor",
|
|
modelVersion: "1.0.0",
|
|
symbol: "symbol-not-registered",
|
|
order: 1,
|
|
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,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
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(() => {
|
|
window.localStorage.clear();
|
|
window.sessionStorage.clear();
|
|
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);
|
|
}
|