Files
MineGeo/scripts/test-multiplayer-browser.mjs

182 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import fs from "node:fs";
import { chromium, expect } from "@playwright/test";
import { createClient } from "@supabase/supabase-js";
if (fs.existsSync(".env.local")) process.loadEnvFile(".env.local");
const origin = new URL(process.argv[2] || "http://127.0.0.1:3007").origin;
const db = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY,
{ auth: { persistSession: false, autoRefreshToken: false } },
);
const rooms = [];
const browser = await chromium.launch({
headless: true,
args: ["--use-angle=swiftshader", "--enable-unsafe-swiftshader"],
});
try {
const { data: maps, error } = await db
.from("datasets")
.select("id")
.eq("state", "published")
.limit(1);
if (error) throw error;
assert.ok(maps.length);
const desktop = await browser.newContext({
viewport: { width: 1280, height: 900 },
locale: "en-US",
});
const mobile = await browser.newContext({
viewport: { width: 390, height: 844 },
locale: "en-US",
isMobile: true,
hasTouch: true,
});
const host = await desktop.newPage(),
friend = await mobile.newPage();
const pageErrors = [];
for (const page of [host, friend])
page.on("pageerror", (error) => pageErrors.push(error.message));
await host.goto(`${origin}/servers/${maps[0].id}`, {
waitUntil: "networkidle",
timeout: 60000,
});
await host.getByRole("button", { name: "Multiplayer", exact: true }).click();
await host.getByLabel("Your player name").fill("Browser Host");
await host.getByLabel("Player limit (including you)").selectOption("2");
await host
.getByRole("button", { name: "Create multiplayer room", exact: true })
.click();
await host.waitForURL("**/multiplayer/*", { timeout: 30000 });
const id = host.url().split("/").pop();
rooms.push(id);
await expect(host.getByLabel("Invite link")).toHaveValue(
`${origin}/multiplayer/${id}`,
);
await friend.goto(host.url(), {
waitUntil: "domcontentloaded",
timeout: 60000,
});
await friend.getByLabel("Your player name").fill("Browser Friend");
await friend.getByRole("button", { name: "Join room", exact: true }).click();
await expect(
friend.getByText("You’re in! Waiting for the room owner to start.", {
exact: true,
}),
).toBeVisible();
await expect(
host.getByRole("button", { name: "Start multiplayer match", exact: true }),
).toBeEnabled({ timeout: 20000 });
fs.mkdirSync("artifacts/multiplayer", { recursive: true });
await host.screenshot({
path: "artifacts/multiplayer/desktop-lobby.png",
fullPage: true,
});
await friend.screenshot({
path: "artifacts/multiplayer/mobile-lobby.png",
fullPage: true,
});
assert.ok(
await friend.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
),
"mobile lobby fits viewport",
);
await host
.getByRole("button", { name: "Start multiplayer match", exact: true })
.click();
for (let round = 0; round < 5; round++) {
await Promise.all([
expect(host.locator(".game-info strong").first()).toHaveText(
`${round + 1} / 5`,
{ timeout: 30000 },
),
expect(friend.locator(".game-info strong").first()).toHaveText(
`${round + 1} / 5`,
{ timeout: 30000 },
),
]);
await Promise.all([
expect(host.locator(".game-loading")).toHaveCount(0, { timeout: 45000 }),
expect(friend.locator(".game-loading")).toHaveCount(0, {
timeout: 45000,
}),
]);
if (round === 0) {
await host.reload({ waitUntil: "domcontentloaded" });
await expect(host.locator(".game-loading")).toHaveCount(0, {
timeout: 45000,
});
}
await friend
.getByRole("button", { name: "Open guess map", exact: true })
.click();
for (const [page, fraction] of [
[host, 0.48],
[friend, 0.7],
]) {
const map = page.getByRole("application", { name: /Minecraft map/ });
const box = await map.boundingBox();
assert.ok(box);
await map.click({
position: { x: box.width * fraction, y: box.height * 0.5 },
});
await expect(
page.getByRole("button", { name: "Submit guess", exact: true }),
).toBeEnabled();
if (round === 0 && page === host)
await page.screenshot({
path: "artifacts/multiplayer/desktop-round.png",
});
await page
.getByRole("button", { name: "Submit guess", exact: true })
.click();
}
if (round < 4) {
await expect(
host.getByRole("heading", { name: "Round standings", exact: true }),
).toBeVisible({ timeout: 20000 });
await expect(
friend.getByRole("heading", { name: "Round standings", exact: true }),
).toBeVisible({ timeout: 20000 });
await host.getByRole("button", { name: /^Next round/ }).click();
}
console.log(`Browser round ${round + 1}/5 completed.`);
}
await expect(
host.getByRole("heading", { name: "Final standings", exact: true }),
).toBeVisible({ timeout: 20000 });
await expect(
friend.getByRole("heading", { name: "Final standings", exact: true }),
).toBeVisible({ timeout: 20000 });
assert.equal(await host.locator(".multiplayer-final tbody tr").count(), 2);
await host.screenshot({
path: "artifacts/multiplayer/desktop-results.png",
fullPage: true,
});
await friend.screenshot({
path: "artifacts/multiplayer/mobile-results.png",
fullPage: true,
});
assert.ok(
await friend.evaluate(
() => document.documentElement.scrollWidth <= window.innerWidth,
),
"mobile results fit viewport with table scrolling",
);
assert.deepEqual(pageErrors, []);
console.log(
"Desktop/mobile browser multiplayer passed: create, invite, join, reconnect, five shared rounds, map guesses, and final standings. No page errors.",
);
} finally {
await browser.close();
if (rooms.length) {
const { error } = await db
.from("multiplayer_rooms")
.delete()
.in("id", rooms);
if (error)
throw new Error(`Browser test-room cleanup failed: ${error.message}`);
}
}