diff --git a/packages/tools/playground/test/interactions.playground.test.ts b/packages/tools/playground/test/interactions.playground.test.ts index 2e399fef469..08027605065 100644 --- a/packages/tools/playground/test/interactions.playground.test.ts +++ b/packages/tools/playground/test/interactions.playground.test.ts @@ -1,6 +1,7 @@ import { test, expect } from "@playwright/test"; +import { getGlobalConfig } from "@tools/test-tools"; -const url = process.env.PLAYGROUND_BASE_URL || "https://playground.babylonjs.com/"; +const url = process.env.PLAYGROUND_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.PLAYGROUND_PORT || ":1338"); test("Playground is loaded (Desktop)", async ({ page }) => { await page.goto(url, { diff --git a/packages/tools/sandbox/test/LogoSandbox.png b/packages/tools/sandbox/test/LogoSandbox.png new file mode 100644 index 00000000000..e0e9bfe6f69 Binary files /dev/null and b/packages/tools/sandbox/test/LogoSandbox.png differ diff --git a/packages/tools/sandbox/test/interaction.sandbox.test.ts b/packages/tools/sandbox/test/interaction.sandbox.test.ts new file mode 100644 index 00000000000..ee2bff9c03e --- /dev/null +++ b/packages/tools/sandbox/test/interaction.sandbox.test.ts @@ -0,0 +1,88 @@ +import { test, expect } from "@playwright/test"; +import { readFileSync } from "fs"; +import { getGlobalConfig } from "@tools/test-tools"; + +const url = process.env.SANDBOX_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.SANDBOX_PORT || ":1339"); + +test("Sandbox is loaded (Desktop)", async ({ page }) => { + await page.goto(url, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // check visibility of both canvas AND the editor + await expect(page.locator("#canvasZone")).toBeVisible(); + // check snapshot of the page + const snapshot = await page.screenshot(); + expect(snapshot).toMatchSnapshot(); +}); + +test("dropping an image to the sandbox", async ({ page }) => { + await page.goto(url, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + // Read your file into a buffer. + const buffer = readFileSync(__dirname + "/LogoSandbox.png"); + + // Create the DataTransfer and File + const dataTransfer = await page.evaluateHandle((data) => { + const dt = new DataTransfer(); + const file = new File([new Uint8Array(data)], "file.png", { type: "image/png" }); + dt.items.add(file); + return dt; + }, buffer.toJSON().data); + + // Now dispatch + await page.dispatchEvent("#renderCanvas", "drop", { dataTransfer }); + // wait for #babylonjsLoadingDiv to be hidden + await page.waitForSelector("#babylonjsLoadingDiv", { state: "hidden" }); + await page.waitForSelector("#babylonjsLoadingDiv", { state: "detached" }); + // check snapshot of the page + const snapshot = await page.screenshot(); + expect(snapshot).toMatchSnapshot(); +}); + +test("loading a model using query parameters", async ({ page }) => { + await page.goto(url + "?assetUrl=https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/Box/glTF-Binary/Box.glb", { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // wait for #babylonjsLoadingDiv to be hidden + await page.waitForSelector("#babylonjsLoadingDiv", { state: "hidden" }); + await page.waitForSelector("#babylonjsLoadingDiv", { state: "detached" }); + // check snapshot of the page + const snapshot = await page.screenshot(); + expect(snapshot).toMatchSnapshot(); +}); + +test("inspector is opened when clicking on the button", async ({ page }) => { + await page.goto(url + "?assetUrl=https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/Box/glTF-Binary/Box.glb", { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + // wait for #babylonjsLoadingDiv to be hidden + await page.waitForSelector("#babylonjsLoadingDiv", { state: "hidden" }); + await page.waitForSelector("#babylonjsLoadingDiv", { state: "detached" }); + + // click the "Inspector" button + await page.getByTitle("Display inspector").click(); + await expect(page.locator("#inspector-host")).toBeVisible(); + await expect(page.locator("#scene-explorer-host")).toBeVisible(); + // check snapshot of the page + const snapshot = await page.screenshot(); + expect(snapshot).toMatchSnapshot(); +}); diff --git a/packages/tools/tests/test/playwright/interaction.tools.test.ts b/packages/tools/tests/test/playwright/interaction.tools.test.ts new file mode 100644 index 00000000000..f8453f93b7a --- /dev/null +++ b/packages/tools/tests/test/playwright/interaction.tools.test.ts @@ -0,0 +1,438 @@ +import { getGlobalConfig } from "@tools/test-tools"; +import { test, expect } from "@playwright/test"; + +const nmeUrl = process.env.NME_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.NME_PORT || ":1340"); +const ngeUrl = process.env.NGE_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.NGE_PORT || ":1343"); +const guiUrl = process.env.GUIEDITOR_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.GUIEDITOR_PORT || ":1341"); +const nrgeUrl = process.env.NRGE_BASE_URL || getGlobalConfig().baseUrl.replace(":1337", process.env.NRGE_PORT || ":1344"); + +test("NME is loaded correctly", async ({ page }) => { + await page.goto(nmeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // check visibility of both canvas AND the editor + await expect(page.locator("#graph-canvas")).toBeVisible(); + await expect(page.locator("#nmeNodeList")).toBeVisible(); + await expect(page.locator(".nme-right-panel")).toBeVisible(); +}); + +test("NGE is loaded correctly", async ({ page }) => { + await page.goto(ngeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // check visibility of both canvas AND the editor + await expect(page.locator("#graph-canvas")).toBeVisible(); + await expect(page.locator("#ngeNodeList")).toBeVisible(); + await expect(page.locator(".nge-right-panel")).toBeVisible(); +}); + +test("GUIEditor is loaded", async ({ page }) => { + await page.goto(guiUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // check visibility of both canvas AND the editor + await expect(page.locator("#workbench-canvas")).toBeVisible(); + await expect(page.locator(".left-panel")).toBeVisible(); + await expect(page.locator(".right-panel")).toBeVisible(); +}); + +test("NRGE is loaded correctly", async ({ page }) => { + await page.goto(nrgeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // check visibility of both canvas AND the editor + await expect(page.locator("#graph-canvas")).toBeVisible(); + await expect(page.locator("#nrgeNodeList")).toBeVisible(); + await expect(page.locator(".nrge-right-panel")).toBeVisible(); +}); + +/////// NME TESTS /////// + +test("[NME] User can drag graph nodes", async ({ page }) => { + await page.goto(nmeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const node = page.locator(".FragmentOutputBlock"); + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 40, nodePosition.y + 20, { steps: 5 }); + await page.mouse.down(); + await page.mouse.move(nodePosition.x + 200, nodePosition.y + 200, { steps: 5 }); + await page.mouse.up(); + + const newNodePosition = await node.boundingBox(); + if (!newNodePosition) { + throw new Error("Node not found"); + } + // check if the node has moved + expect(newNodePosition.x).toBeGreaterThan(nodePosition.x); + expect(newNodePosition.y).toBeGreaterThan(nodePosition.y); +}); + +test("[NME] User can zoom in and out of the graph", async ({ page }) => { + await page.goto(nmeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const graph = page.locator("#graph-canvas"); + const graphPosition = await graph.boundingBox(); + // check the background size of the graph + const backgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const backgroundParsed = backgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + if (!graphPosition) { + throw new Error("Graph not found"); + } + await page.mouse.move(graphPosition.x + 40, graphPosition.y + 20, { steps: 5 }); + await page.mouse.wheel(0, 300); + const newBackgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const newBackgroundParsed = newBackgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + // check if the background size has changed + expect(newBackgroundParsed).toHaveLength(2); + expect(backgroundParsed[0]).toBeGreaterThan(newBackgroundParsed[0]); + expect(backgroundParsed[1]).toBeGreaterThan(newBackgroundParsed[1]); +}); + +test("[NME] User can add a new node to the graph", async ({ page }) => { + await page.goto(nmeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + // get the number of nodes in the graph. this is the number of direct children in #graph-canvas-container + const nodeCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + + // get a node with innerText "Color4" + const node = page.getByText("Color4").nth(0); + // move to this node + 10px + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 10, nodePosition.y + 10, { steps: 5 }); + await page.mouse.down(); + // move to the right + await page.mouse.move(nodePosition.x + 400, nodePosition.y, { steps: 5 }); + await page.mouse.up(); + // check the new number of children + const newCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + // expect newCount to be nodeCount + 1 + expect(newCount).toBe(nodeCount + 1); +}); + +///////// NGE TESTS ///////// + +test("[NGE] User can drag graph nodes", async ({ page }) => { + await page.goto(ngeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const node = page.locator(".GeometryOutputBlock"); + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 40, nodePosition.y + 20, { steps: 5 }); + await page.mouse.down(); + await page.mouse.move(nodePosition.x + 200, nodePosition.y + 200, { steps: 5 }); + await page.mouse.up(); + + const newNodePosition = await node.boundingBox(); + if (!newNodePosition) { + throw new Error("Node not found"); + } + // check if the node has moved + expect(newNodePosition.x).toBeGreaterThan(nodePosition.x); + expect(newNodePosition.y).toBeGreaterThan(nodePosition.y); +}); + +test("[NGE] User can zoom in and out of the graph", async ({ page }) => { + await page.goto(ngeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const graph = page.locator("#graph-canvas"); + const graphPosition = await graph.boundingBox(); + // check the background size of the graph + const backgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const backgroundParsed = backgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + if (!graphPosition) { + throw new Error("Graph not found"); + } + await page.mouse.move(graphPosition.x + 40, graphPosition.y + 20, { steps: 5 }); + await page.mouse.wheel(0, 300); + const newBackgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const newBackgroundParsed = newBackgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + // check if the background size has changed + expect(newBackgroundParsed).toHaveLength(2); + expect(backgroundParsed[0]).toBeGreaterThan(newBackgroundParsed[0]); + expect(backgroundParsed[1]).toBeGreaterThan(newBackgroundParsed[1]); +}); + +test("[NGE] User can add a new node to the graph", async ({ page }) => { + await page.goto(ngeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + // get the number of nodes in the graph. this is the number of direct children in #graph-canvas-container + const nodeCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + + // get a node with innerText "Color4" + const node = page.getByText("Vector3").nth(0); + // move to this node + 10px + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 10, nodePosition.y + 10, { steps: 5 }); + await page.mouse.down(); + // move to the right + await page.mouse.move(nodePosition.x + 400, nodePosition.y, { steps: 5 }); + await page.mouse.up(); + // check the new number of children + const newCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + // expect newCount to be nodeCount + 1 + expect(newCount).toBe(nodeCount + 1); +}); + +//////// NRGE TESTS //////// + +test("[NRGE] User can drag graph nodes", async ({ page }) => { + await page.goto(nrgeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const node = page.locator(".NodeRenderGraphOutputBlock"); + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 40, nodePosition.y + 20, { steps: 5 }); + await page.mouse.down(); + await page.mouse.move(nodePosition.x + 200, nodePosition.y + 200, { steps: 5 }); + await page.mouse.up(); + + const newNodePosition = await node.boundingBox(); + if (!newNodePosition) { + throw new Error("Node not found"); + } + // check if the node has moved + expect(newNodePosition.x).toBeGreaterThan(nodePosition.x); + expect(newNodePosition.y).toBeGreaterThan(nodePosition.y); +}); + +test("[NRGE] User can zoom in and out of the graph", async ({ page }) => { + await page.goto(nrgeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + const graph = page.locator("#graph-canvas"); + const graphPosition = await graph.boundingBox(); + // check the background size of the graph + const backgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const backgroundParsed = backgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + if (!graphPosition) { + throw new Error("Graph not found"); + } + await page.mouse.move(graphPosition.x + 40, graphPosition.y + 20, { steps: 5 }); + await page.mouse.wheel(0, 300); + const newBackgroundSize = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas") as HTMLCanvasElement; + return graph.style.backgroundSize; + }); + const newBackgroundParsed = newBackgroundSize.split(" ").map((size) => parseFloat(size.replace("px", ""))); + // check if the background size has changed + expect(newBackgroundParsed).toHaveLength(2); + expect(backgroundParsed[0]).toBeGreaterThan(newBackgroundParsed[0]); + expect(backgroundParsed[1]).toBeGreaterThan(newBackgroundParsed[1]); +}); + +test("[NRGE] User can add a new node to the graph", async ({ page }) => { + await page.goto(nrgeUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + + // get the number of nodes in the graph. this is the number of direct children in #graph-canvas-container + const nodeCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + + // get a node with innerText "Color4" + const node = page.getByText("ObjectList").nth(0); + // move to this node + 10px + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 10, nodePosition.y + 10, { steps: 5 }); + await page.mouse.down(); + // move to the right + await page.mouse.move(nodePosition.x + 400, nodePosition.y, { steps: 5 }); + await page.mouse.up(); + // check the new number of children + const newCount = await page.evaluate(() => { + const graph = document.getElementById("graph-canvas-container"); + if (!graph) { + throw new Error("Graph not found"); + } + return graph.children.length; + }); + // expect newCount to be nodeCount + 1 + expect(newCount).toBe(nodeCount + 1); +}); + +//////// GUIEDITOR TESTS //////// + +// single test adding, moving an element +test("[GUIEDITOR] User can add and drag graph nodes", async ({ page }) => { + await page.goto(guiUrl, { + waitUntil: "networkidle", + }); + await page.setViewportSize({ + width: 1920, + height: 1080, + }); + // expect the tree to be empty - no children + const treeChildren = await page.evaluate(() => { + const tree = document.querySelector("#ge-sceneExplorer > div#tree > div") as HTMLElement; + return tree.children.length; + }); + + expect(treeChildren).toBe(0); + + const node = page.getByTitle("InputText").first(); + const nodePosition = await node.boundingBox(); + if (!nodePosition) { + throw new Error("Node not found"); + } + await page.mouse.move(nodePosition.x + 10, nodePosition.y + 10, { steps: 5 }); + await page.mouse.down(); + await page.mouse.move(nodePosition.x + 200, nodePosition.y, { steps: 5 }); + await page.mouse.up(); + + // update tree children + const newTreeChildren = await page.evaluate(() => { + const tree = document.querySelector("#ge-sceneExplorer > div#tree > div") as HTMLElement; + return tree.children.length; + }); + + expect(newTreeChildren).toBe(1); + + // drag the node - it was added to the middle of the canvas + const canvasNode = page.locator("#workbench-canvas"); + const canvasPosition = await canvasNode.boundingBox(); + if (!canvasPosition) { + throw new Error("Canvas not found"); + } + const center = { x: canvasPosition.x + canvasPosition.width / 2, y: canvasPosition.y + canvasPosition.height / 2 }; + + await page.mouse.move(center.x + 30, center.y, { steps: 5 }); + + // take a screenshot (for visual inspection) + const snapshot = await canvasNode.screenshot(); + expect(snapshot).toMatchSnapshot(); + + await page.mouse.down(); + await page.mouse.move(center.x + 50, center.y + 50, { steps: 5 }); + await page.mouse.up(); + + // take a screenshot (for visual inspection) + const newSnapshot = await canvasNode.screenshot(); + expect(newSnapshot).toMatchSnapshot(); +}); diff --git a/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-1.png b/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-1.png new file mode 100644 index 00000000000..560dbf10132 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-1.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-2.png b/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-2.png new file mode 100644 index 00000000000..235e996148f Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/-GUIEDITOR-User-can-add-and-drag-graph-nodes-2.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/Sandbox-is-loaded-Desktop-1.png b/packages/tools/tests/test/visualization/ReferenceImages/Sandbox-is-loaded-Desktop-1.png new file mode 100644 index 00000000000..4fa7002f16e Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/Sandbox-is-loaded-Desktop-1.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/dropping-an-image-to-the-sandbox-1.png b/packages/tools/tests/test/visualization/ReferenceImages/dropping-an-image-to-the-sandbox-1.png new file mode 100644 index 00000000000..088c3bcebb2 Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/dropping-an-image-to-the-sandbox-1.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/inspector-is-opened-when-clicking-on-the-button-1.png b/packages/tools/tests/test/visualization/ReferenceImages/inspector-is-opened-when-clicking-on-the-button-1.png new file mode 100644 index 00000000000..6cf682940bb Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/inspector-is-opened-when-clicking-on-the-button-1.png differ diff --git a/packages/tools/tests/test/visualization/ReferenceImages/loading-a-model-using-query-parameters-1.png b/packages/tools/tests/test/visualization/ReferenceImages/loading-a-model-using-query-parameters-1.png new file mode 100644 index 00000000000..7f6d9bd1cbd Binary files /dev/null and b/packages/tools/tests/test/visualization/ReferenceImages/loading-a-model-using-query-parameters-1.png differ diff --git a/playwright.config.ts b/playwright.config.ts index 59fe31c5d98..3d455daea17 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -85,6 +85,16 @@ export default defineConfig({ testMatch: "**/*.playground.test.ts", use: getUseDefinition("Playground"), }, + { + name: "sandbox", + testMatch: "**/*.sandbox.test.ts", + use: getUseDefinition("Sandbox"), + }, + { + name: "graphTools", + testMatch: "**/*.tools.test.ts", + use: getUseDefinition("Graph Tools"), + }, ], snapshotPathTemplate: "packages/tools/tests/test/visualization/ReferenceImages/{arg}{ext}",