The suite could not pass on the test database: global setup now seeds the shared dev/Test123 login with a superuser Roles doc and the dashboard rank, and points its own Payload connection at the test database. Playwright runs one worker with generous test and expect timeouts because first navigation to a route triggers a cold webpack compile, and gains E2E_BROWSER_PATH to swap the browser binary when the installed Vivaldi crashes under automation. The landing page gains the missing h1 the guest specs assert, login flows wait for the redirect to land, strict-mode locator collisions get exact or level-scoped matches, the session banner test re-signs the login claims with a short expiry using the runtime secret, and the reservation dialog click forces the dispatch and verifies the dialog opened (the trigger goes aria-hidden behind the modal mid-click, which stranded the retry loop).
66 lines
2.8 KiB
TypeScript
66 lines
2.8 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
import { formatOpordGrid, generateOpordScenario } from "../../src/lib/minigames/opord-recall";
|
|
|
|
const FIXED_SEED = 424242;
|
|
const QUIZ_OPTION_BUTTON = "button.font-mono";
|
|
|
|
async function login(page: Page): Promise<void> {
|
|
await page.goto("/login");
|
|
await page.fill("#username", "dev");
|
|
await page.fill("#password", "Test123");
|
|
await page.locator('button[type="submit"]').click();
|
|
await page.waitForURL((url) => !url.pathname.startsWith("/login"), { timeout: 30_000 });
|
|
}
|
|
|
|
async function playOpordRound(page: Page): Promise<void> {
|
|
await page.goto(`/minigames/opord?seed=${FIXED_SEED}`);
|
|
await expect(page.getByRole("heading", { name: "OPORD Recall", level: 1 })).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Receive briefing" }).click();
|
|
await expect(page.getByText("Study at your own pace")).toBeVisible();
|
|
|
|
// The briefing is drawn on canvas, so derive the expected grid from the lib.
|
|
const scenario = generateOpordScenario(FIXED_SEED);
|
|
const grid = formatOpordGrid(scenario.grid);
|
|
|
|
await page.getByRole("button", { name: "Begin recall" }).click();
|
|
|
|
const questionCounter = page.getByText(/Question \d+ of \d+/);
|
|
await expect(questionCounter).toBeVisible();
|
|
await expect(questionCounter).toContainText("Question 1 of 10");
|
|
|
|
// Question 1 is always the objective grid: answer it correctly.
|
|
await page.locator(QUIZ_OPTION_BUTTON).filter({ hasText: grid }).first().click();
|
|
|
|
for (let index = 1; index < 10; index += 1) {
|
|
await expect(questionCounter).toContainText(`Question ${index + 1} of 10`, {
|
|
timeout: 10_000,
|
|
});
|
|
await page.locator(QUIZ_OPTION_BUTTON).first().click();
|
|
}
|
|
|
|
await expect(page.getByRole("heading", { name: "Debrief" })).toBeVisible({ timeout: 15_000 });
|
|
}
|
|
|
|
test.describe("OPORD Recall", () => {
|
|
test("plays a full round and records the score", async ({ page }) => {
|
|
await login(page);
|
|
await playOpordRound(page);
|
|
|
|
// The grid answer alone scores 100; the XP metre shows a positive bonus.
|
|
await expect(page.locator("body")).toContainText(/Recalled/);
|
|
await expect(page.getByText(/^\+\d+$/).first()).toBeVisible();
|
|
|
|
// The briefing must not expose selectable text to copy.
|
|
const bodyText = await page.locator("body").innerText();
|
|
expect(bodyText).not.toContain(formatOpordGrid(generateOpordScenario(FIXED_SEED).grid));
|
|
|
|
// The leaderboard refetches after the run and shows the recorded run for this user.
|
|
const leaderboard = page.getByRole("table");
|
|
await expect(leaderboard).toBeVisible({ timeout: 15_000 });
|
|
const myRow = leaderboard.locator("tbody tr").filter({ hasText: "(you)" }).first();
|
|
await expect(myRow).toBeVisible({ timeout: 15_000 });
|
|
await expect(myRow.locator("td").last()).toContainText(/\d{3}/);
|
|
});
|
|
});
|