149 lines
6.4 KiB
JavaScript
149 lines
6.4 KiB
JavaScript
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;
|
|
});
|