const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const { chromium } = require("C:/Users/phenom/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright"); const base = process.env.MOUSE_CAMERA_TEST_URL || "http://127.0.0.1:5176"; const directory = path.resolve("playtest-artifacts/mouse-camera"); fs.mkdirSync(directory, { recursive: true }); (async () => { const browser = await chromium.launch({ channel: "msedge", headless: true }); const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }); page.setDefaultTimeout(120_000); const errors = []; page.on("pageerror", (error) => errors.push(error.message)); const gameState = () => page.evaluate(async () => { const storeEntry = performance.getEntriesByType("resource") .findLast((entry) => new URL(entry.name).pathname === "/src/game/store.ts"); if (!storeEntry) throw new Error("game store module was not loaded"); const { useGameStore } = await import(storeEntry.name); const state = useGameStore.getState(); return { cameraLookActive: state.cameraLookActive, cameraYaw: state.cameraYaw, pointerLocked: document.pointerLockElement !== null, selection: window.getSelection()?.toString() ?? "", }; }); try { await page.goto(base, { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: /Play offline/ }).click(); await page.getByRole("button", { name: /Create character/ }).click(); await page.getByRole("button", { name: /Customize & name/ }).click(); await page.getByLabel("Character name").fill("Mouselooker"); await page.getByRole("button", { name: /Create character/ }).click(); await page.getByRole("button", { name: /Continue|Enter world/ }).first().click(); await page.locator(".main-menu-screen").waitFor(); await page.getByRole("button", { name: /^Dungeons/ }).click(); await page.getByRole("button", { name: /Choose role/ }).click(); await page.locator(".dungeon-role-option").first().click(); await page.locator(".game-shell").waitFor(); await page.waitForFunction(async () => { const storeEntry = performance.getEntriesByType("resource") .findLast((entry) => new URL(entry.name).pathname === "/src/game/store.ts"); if (!storeEntry) return false; const { useGameStore } = await import(storeEntry.name); return ["ready", "proxy", "error"].includes(useGameStore.getState().assetStatus); }); await page.locator(".loading-screen").waitFor({ state: "hidden" }); await page.waitForTimeout(1_000); const selectionBefore = await page.evaluate(() => { const fixture = document.createElement("p"); fixture.textContent = "Existing browser selection"; fixture.style.userSelect = "text"; fixture.style.position = "fixed"; fixture.style.left = "-10000px"; document.body.append(fixture); const range = document.createRange(); range.selectNodeContents(fixture); const selection = window.getSelection(); if (!selection) throw new Error("browser selection API is unavailable"); selection.removeAllRanges(); selection.addRange(range); return selection.toString(); }); assert.equal(selectionBefore, "Existing browser selection"); const scene = page.locator(".scene-shell"); const bounds = await scene.boundingBox(); assert(bounds, "3D scene is visible"); const cdp = await page.context().newCDPSession(page); const start = { x: bounds.x + bounds.width * 0.55, y: bounds.y + bounds.height * 0.55 }; const before = await gameState(); assert.equal(before.cameraLookActive, false); await page.mouse.move(start.x, start.y); await page.mouse.down({ button: "right" }); await page.waitForFunction(async () => { const storeEntry = performance.getEntriesByType("resource") .findLast((entry) => new URL(entry.name).pathname === "/src/game/store.ts"); if (!storeEntry) return false; const { useGameStore } = await import(storeEntry.name); return useGameStore.getState().cameraLookActive; }); const captured = await gameState(); assert.equal(captured.pointerLocked, false); assert.equal(captured.selection, ""); await cdp.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: start.x + 160, y: start.y - 55, button: "right", buttons: 2, pointerType: "mouse", }); await page.waitForTimeout(350); const moved = await gameState(); assert.notEqual(moved.cameraYaw, before.cameraYaw); await page.screenshot({ path: path.join(directory, "right-drag-camera.png") }); await page.mouse.up({ button: "right" }); await page.waitForFunction(async () => { const storeEntry = performance.getEntriesByType("resource") .findLast((entry) => new URL(entry.name).pathname === "/src/game/store.ts"); if (!storeEntry) return false; const { useGameStore } = await import(storeEntry.name); return !useGameStore.getState().cameraLookActive; }); const released = await gameState(); await page.mouse.move(start.x - 120, start.y + 70, { steps: 8 }); await page.waitForTimeout(350); const afterReleaseMove = await gameState(); assert.equal(afterReleaseMove.cameraYaw, released.cameraYaw); assert.equal(afterReleaseMove.pointerLocked, false); assert.equal( await page.locator(".game-shell").evaluate((element) => getComputedStyle(element).userSelect), "none", ); assert.deepEqual(errors, []); const result = { passed: true, yawBefore: before.cameraYaw, yawDuringDrag: moved.cameraYaw, yawAfterRelease: afterReleaseMove.cameraYaw, pointerLocked: afterReleaseMove.pointerLocked, selectionCleared: captured.selection === "", errors, }; fs.writeFileSync(path.join(directory, "results.json"), `${JSON.stringify(result, null, 2)}\n`); console.log(JSON.stringify(result, null, 2)); console.log("Mouse camera playtest passed"); } catch (error) { await page.mouse.up({ button: "right" }).catch(() => {}); await page.screenshot({ path: path.join(directory, "failure.png") }); console.log((await page.locator("body").innerText()).slice(-2_000)); console.log("Browser errors:", errors); throw error; } finally { await browser.close(); } })().catch((error) => { console.error(error); process.exitCode = 1; });