mirror of
https://github.com/diodeinc/pcb.git
synced 2026-09-14 20:17:19 +08:00
acff75845c
* Support net symbols * Run renderer tests in CI * renderer -> LayoutEngine * wip * rename * add examples * support symbol "extends" * start reworking layout engine + snapshot tests * add examples * wip add nets * Add signature to ModuleValue * tweaks * per-component net labels * movable * WIP with ELK * wip libavoid * misc fix * better * betterer * improvements * more * update * junction detection * better * snapping and stuff * fix re-rendering * more better * more * cargo fmt * update bg * eslint * cleanup, tests
305 lines
8.9 KiB
TypeScript
305 lines
8.9 KiB
TypeScript
import { test, expect } from "@playwright/experimental-ct-react";
|
|
import React from "react";
|
|
import SchematicContainer from "../../src/components/SchematicContainer";
|
|
import type { Netlist } from "../../src/types/NetlistTypes";
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import { execSync } from "child_process";
|
|
|
|
test.describe("Schematic Visual Tests", () => {
|
|
const examplesDir = path.join(__dirname, "../../../../examples");
|
|
|
|
// Helper function to build netlist from example
|
|
async function buildNetlistFromExample(
|
|
exampleName: string
|
|
): Promise<Netlist> {
|
|
const examplePath = path.join(examplesDir, exampleName);
|
|
const starFile = path.join(examplePath, `${exampleName}.zen`);
|
|
|
|
if (!fs.existsSync(starFile)) {
|
|
throw new Error(`Example file not found: ${starFile}`);
|
|
}
|
|
|
|
// Run cargo to build netlist
|
|
try {
|
|
const output = execSync(`cargo run -- build --netlist "${starFile}"`, {
|
|
cwd: path.join(__dirname, "../../../../"),
|
|
stdio: "pipe",
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
// Parse the output directly from stdout
|
|
return JSON.parse(output) as Netlist;
|
|
} catch (error: any) {
|
|
console.error(
|
|
`Failed to build netlist for ${exampleName}:`,
|
|
error.stderr?.toString()
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Get all example directories
|
|
const exampleDirs = fs
|
|
.readdirSync(examplesDir)
|
|
.filter((name) => fs.statSync(path.join(examplesDir, name)).isDirectory());
|
|
|
|
// Dynamically generate a test for each example
|
|
for (const exampleName of exampleDirs) {
|
|
test(`${exampleName}`, async ({ mount, page }) => {
|
|
// Capture console logs from the browser
|
|
page.on("console", async (msg) => {
|
|
const type = msg.type();
|
|
|
|
// Get all arguments passed to console.log/error/etc
|
|
const args: string[] = [];
|
|
for (const arg of msg.args()) {
|
|
args.push(arg.toString());
|
|
}
|
|
|
|
if (msg.text().includes("kicanvas")) {
|
|
return;
|
|
}
|
|
|
|
// Log to Node.js console with appropriate formatting
|
|
if (type === "log") {
|
|
console.log("[Browser Console]", ...args);
|
|
} else if (type === "error") {
|
|
console.error("[Browser Console Error]", ...args);
|
|
} else if (type === "warning") {
|
|
console.warn("[Browser Console Warning]", ...args);
|
|
} else if (type === "info") {
|
|
console.info("[Browser Console Info]", ...args);
|
|
}
|
|
});
|
|
|
|
// Also capture page errors
|
|
page.on("pageerror", (error) => {
|
|
console.error(`[Page Error] ${error.message}`);
|
|
console.error(error.stack);
|
|
});
|
|
|
|
const netlist = await buildNetlistFromExample(exampleName);
|
|
|
|
// Mount the component
|
|
const component = await mount(
|
|
<>
|
|
<style>{`
|
|
/* Ensure the mount root has full height */
|
|
#root {
|
|
width: 1920px;
|
|
height: 1080px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
/* Override the 100vh in SchematicContainer to use parent height */
|
|
.schematic-layout {
|
|
height: 100% !important;
|
|
}
|
|
.schematic-viewer-container {
|
|
height: 100% !important;
|
|
}
|
|
/* Ensure React Flow fills the container */
|
|
.react-flow {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow__renderer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow__viewport {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.schematic-viewer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow-schematic-viewer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
`}</style>
|
|
<SchematicContainer
|
|
netlistData={netlist}
|
|
currentFile={netlist.root_ref.split(":")[0]}
|
|
selectedModule={netlist.root_ref}
|
|
/>
|
|
</>
|
|
);
|
|
|
|
// Wait for React Flow to finish rendering
|
|
await page.waitForSelector(".react-flow__renderer", {
|
|
state: "attached",
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Wait for nodes to be rendered
|
|
await page.waitForSelector(".react-flow__node", {
|
|
state: "visible",
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Give time for layout to stabilize and animations to complete
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Check for any error messages
|
|
const errorElement = await page.$(".error-message");
|
|
if (errorElement) {
|
|
const errorText = await errorElement.textContent();
|
|
throw new Error(`Error message found in ${exampleName}: ${errorText}`);
|
|
}
|
|
|
|
// Take screenshot
|
|
await expect(component).toHaveScreenshot(
|
|
`${exampleName.toLowerCase()}.png`,
|
|
{
|
|
animations: "disabled",
|
|
scale: "css",
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
// Test rotation functionality
|
|
test("VoltageDivider with rotation", async ({ mount, page }) => {
|
|
// Capture console logs from the browser
|
|
page.on("console", async (msg) => {
|
|
const type = msg.type();
|
|
const args: string[] = [];
|
|
for (const arg of msg.args()) {
|
|
args.push(arg.toString());
|
|
}
|
|
if (msg.text().includes("kicanvas")) {
|
|
return;
|
|
}
|
|
if (type === "log") {
|
|
console.log("[Browser Console]", ...args);
|
|
} else if (type === "error") {
|
|
console.error("[Browser Console Error]", ...args);
|
|
}
|
|
});
|
|
|
|
page.on("pageerror", (error) => {
|
|
console.error(`[Page Error] ${error.message}`);
|
|
console.error(error.stack);
|
|
});
|
|
|
|
const netlist = await buildNetlistFromExample("VoltageDivider");
|
|
|
|
// Mount the component
|
|
const component = await mount(
|
|
<>
|
|
<style>{`
|
|
/* Ensure the mount root has full height */
|
|
#root {
|
|
width: 1920px;
|
|
height: 1080px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
/* Override the 100vh in SchematicContainer to use parent height */
|
|
.schematic-layout {
|
|
height: 100% !important;
|
|
}
|
|
.schematic-viewer-container {
|
|
height: 100% !important;
|
|
}
|
|
/* Ensure React Flow fills the container */
|
|
.react-flow {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow__renderer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow__viewport {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.schematic-viewer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
.react-flow-schematic-viewer {
|
|
height: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
`}</style>
|
|
<SchematicContainer
|
|
netlistData={netlist}
|
|
currentFile={netlist.root_ref.split(":")[0]}
|
|
selectedModule={netlist.root_ref}
|
|
/>
|
|
</>
|
|
);
|
|
|
|
// Wait for React Flow to finish rendering
|
|
await page.waitForSelector(".react-flow__renderer", {
|
|
state: "attached",
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Wait for nodes to be rendered
|
|
await page.waitForSelector(".react-flow__node", {
|
|
state: "visible",
|
|
timeout: 10000,
|
|
});
|
|
|
|
// Give time for layout to stabilize
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Find and click on a resistor node (R1 or R2)
|
|
const resistorNode = await page
|
|
.locator('.react-flow__node[data-id*="R1"]')
|
|
.first();
|
|
|
|
// Click to select the resistor
|
|
await resistorNode.click();
|
|
|
|
// Wait a bit for selection to register
|
|
await page.waitForTimeout(100);
|
|
|
|
// Press 'R' to rotate
|
|
await page.keyboard.press("r");
|
|
|
|
// Wait for rotation animation and re-layout
|
|
await page.waitForTimeout(500);
|
|
|
|
// Take screenshot with rotated component
|
|
await expect(component).toHaveScreenshot("voltagedivider-rotated.png", {
|
|
animations: "disabled",
|
|
scale: "css",
|
|
});
|
|
|
|
// Rotate again (should be 180 degrees now)
|
|
await page.keyboard.press("r");
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(component).toHaveScreenshot("voltagedivider-rotated-180.png", {
|
|
animations: "disabled",
|
|
scale: "css",
|
|
});
|
|
|
|
// Rotate again (should be 270 degrees now)
|
|
await page.keyboard.press("r");
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(component).toHaveScreenshot("voltagedivider-rotated-270.png", {
|
|
animations: "disabled",
|
|
scale: "css",
|
|
});
|
|
|
|
// Rotate once more (should be back to 0 degrees)
|
|
await page.keyboard.press("r");
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(component).toHaveScreenshot("voltagedivider-rotated-360.png", {
|
|
animations: "disabled",
|
|
scale: "css",
|
|
});
|
|
});
|
|
});
|