Add multiplayer rooms and Vercel analytics

This commit is contained in:
2026-10-07 12:28:45 +05:00
parent ce26e6fdbc
commit 48b34db6f1
20 changed files with 2345 additions and 9 deletions

View File

@@ -0,0 +1,181 @@
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}`);
}
}