182 lines
6.0 KiB
JavaScript
182 lines
6.0 KiB
JavaScript
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}`);
|
||
}
|
||
}
|