c4f1409329
Phase 14's auto-sync calls `uuidToHiLo` on every layout boot. The existing Phase 10 e2e specs use a placeholder string `test-shell` as the game id, which throws in the FBS request encoder and surfaced as a noisy `console.warn` plus a flaky webkit-desktop test on the local-ci ARM runner. `OrderDraftStore.hydrateFromServer` and `scheduleSync` now skip when the active game id isn't a real UUID — the auto-sync path is inert for fixture data and the placeholder-warning is gone. The Phase 10 spec switches to a deterministic UUID (`10101010-1010-1010-1010-101010101010`) so future Phase 14+ specs don't have to special-case it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
226 lines
7.9 KiB
TypeScript
226 lines
7.9 KiB
TypeScript
// Phase 10 end-to-end coverage for the in-game shell. Every spec
|
||
// boots an authenticated session through `/__debug/store` (the
|
||
// in-game shell makes a handful of gateway calls — for the lobby
|
||
// record, the report, and the order read-back; we don't mock them
|
||
// here, the shell tolerates ECONNREFUSED), navigates into
|
||
// `/games/<game-id>/map`, and exercises one slice of the chrome:
|
||
// header navigation, sidebar tab preservation, mobile bottom-tabs,
|
||
// and the breakpoint switches at 768 / 1024 px.
|
||
|
||
import { expect, test, type Page } from "@playwright/test";
|
||
|
||
// The `window.__galaxyDebug` surface is owned by
|
||
// `src/routes/__debug/store/+page.svelte` and typed by
|
||
// `tests/e2e/storage-keypair-persistence.spec.ts`. This spec only
|
||
// needs the auth-bootstrap subset (`clearSession`,
|
||
// `setDeviceSessionId`); the merged global declaration covers both.
|
||
|
||
const SESSION_ID = "phase-10-shell-session";
|
||
// GAME_ID has to be a real UUID — Phase 14's auto-sync calls
|
||
// `uuidToHiLo` on it for the FBS request envelope, and an
|
||
// arbitrary string would throw on every layout boot.
|
||
const GAME_ID = "10101010-1010-1010-1010-101010101010";
|
||
|
||
async function bootShell(page: Page): Promise<void> {
|
||
await page.goto("/__debug/store");
|
||
await expect(page.getByTestId("debug-store-ready")).toBeVisible();
|
||
await page.waitForFunction(() => window.__galaxyDebug?.ready === true);
|
||
await page.evaluate(() => window.__galaxyDebug!.clearSession());
|
||
await page.evaluate(
|
||
(id) => window.__galaxyDebug!.setDeviceSessionId(id),
|
||
SESSION_ID,
|
||
);
|
||
await page.goto(`/games/${GAME_ID}/map`);
|
||
await expect(page.getByTestId("game-shell")).toBeVisible();
|
||
await expect(page.getByTestId("active-view-map")).toBeVisible();
|
||
}
|
||
|
||
test("shell mounts with header / sidebar / active-view chrome", async ({
|
||
page,
|
||
}) => {
|
||
await bootShell(page);
|
||
await expect(page.getByTestId("game-shell-header")).toBeVisible();
|
||
await expect(page.getByTestId("game-shell-headline")).toContainText(
|
||
"turn",
|
||
);
|
||
await expect(page.getByTestId("view-menu-trigger")).toBeVisible();
|
||
await expect(page.getByTestId("account-menu-trigger")).toBeVisible();
|
||
});
|
||
|
||
test("header view-menu navigates to every active view", async ({ page }) => {
|
||
await bootShell(page);
|
||
|
||
const destinations: Array<[string, string, string]> = [
|
||
["view-menu-item-report", "active-view-report", "/report"],
|
||
["view-menu-item-mail", "active-view-mail", "/mail"],
|
||
["view-menu-item-battle", "active-view-battle", "/battle"],
|
||
[
|
||
"view-menu-item-designer-ship-class",
|
||
"active-view-designer-ship-class",
|
||
"/designer/ship-class",
|
||
],
|
||
[
|
||
"view-menu-item-designer-science",
|
||
"active-view-designer-science",
|
||
"/designer/science",
|
||
],
|
||
["view-menu-item-map", "active-view-map", "/map"],
|
||
];
|
||
|
||
for (const [trigger, viewTestId, urlSuffix] of destinations) {
|
||
await page.getByTestId("view-menu-trigger").click();
|
||
await page.getByTestId(trigger).click();
|
||
await expect(page.getByTestId(viewTestId)).toBeVisible();
|
||
await expect(page).toHaveURL(new RegExp(`/games/${GAME_ID}${urlSuffix}$`));
|
||
}
|
||
});
|
||
|
||
test("header view-menu Tables sub-list navigates to every entity", async ({
|
||
page,
|
||
}) => {
|
||
await bootShell(page);
|
||
const entities = [
|
||
"planets",
|
||
"ship-classes",
|
||
"ship-groups",
|
||
"fleets",
|
||
"sciences",
|
||
"races",
|
||
];
|
||
for (const entity of entities) {
|
||
await page.getByTestId("view-menu-trigger").click();
|
||
await page
|
||
.getByTestId("view-menu-tables")
|
||
.locator("summary")
|
||
.click();
|
||
await page.getByTestId(`view-menu-item-table-${entity}`).click();
|
||
const view = page.getByTestId("active-view-table");
|
||
await expect(view).toBeVisible();
|
||
await expect(view).toHaveAttribute("data-entity", entity);
|
||
await expect(page).toHaveURL(
|
||
new RegExp(`/games/${GAME_ID}/table/${entity}$`),
|
||
);
|
||
}
|
||
});
|
||
|
||
test("sidebar tab choice survives navigation between active views", async ({
|
||
page,
|
||
browserName,
|
||
}, testInfo) => {
|
||
test.skip(
|
||
testInfo.project.name.startsWith("chromium-mobile") ||
|
||
testInfo.project.name === "webkit-desktop"
|
||
? false
|
||
: false,
|
||
"sidebar test runs on every project",
|
||
);
|
||
await bootShell(page);
|
||
// Skip on viewports below 1024 — sidebar is hidden by CSS there.
|
||
const viewport = page.viewportSize();
|
||
if (viewport === null || viewport.width < 1024) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
void browserName;
|
||
|
||
await page.getByTestId("sidebar-tab-calculator").click();
|
||
await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible();
|
||
|
||
await page.getByTestId("view-menu-trigger").click();
|
||
await page.getByTestId("view-menu-item-report").click();
|
||
await expect(page.getByTestId("active-view-report")).toBeVisible();
|
||
|
||
// Sidebar still rendered; the calculator tool remains selected.
|
||
await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible();
|
||
await expect(page.getByTestId("sidebar")).toHaveAttribute(
|
||
"data-active-tab",
|
||
"calculator",
|
||
);
|
||
|
||
await page.getByTestId("view-menu-trigger").click();
|
||
await page.getByTestId("view-menu-item-map").click();
|
||
await expect(page.getByTestId("active-view-map")).toBeVisible();
|
||
await expect(page.getByTestId("sidebar")).toHaveAttribute(
|
||
"data-active-tab",
|
||
"calculator",
|
||
);
|
||
});
|
||
|
||
test("mobile bottom-tabs show on small viewports and toggle the tool overlay", async ({
|
||
page,
|
||
}, testInfo) => {
|
||
if (!testInfo.project.name.startsWith("chromium-mobile")) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
await bootShell(page);
|
||
|
||
await expect(page.getByTestId("bottom-tabs")).toBeVisible();
|
||
await expect(page.getByTestId("sidebar")).not.toBeVisible();
|
||
|
||
await page.getByTestId("bottom-tab-calc").click();
|
||
await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible();
|
||
|
||
await page.getByTestId("bottom-tab-order").click();
|
||
await expect(page.getByTestId("sidebar-tool-order")).toBeVisible();
|
||
|
||
await page.getByTestId("bottom-tab-map").click();
|
||
await expect(page.getByTestId("active-view-map")).toBeVisible();
|
||
});
|
||
|
||
test("mobile More drawer navigates to every destination", async ({
|
||
page,
|
||
}, testInfo) => {
|
||
if (!testInfo.project.name.startsWith("chromium-mobile")) {
|
||
test.skip();
|
||
return;
|
||
}
|
||
await bootShell(page);
|
||
|
||
await page.getByTestId("bottom-tab-more").click();
|
||
await expect(page.getByTestId("bottom-tabs-more-drawer")).toBeVisible();
|
||
await page.getByTestId("bottom-tabs-more-mail").click();
|
||
await expect(page.getByTestId("active-view-mail")).toBeVisible();
|
||
|
||
await page.getByTestId("bottom-tab-more").click();
|
||
await page.getByTestId("bottom-tabs-more-report").click();
|
||
await expect(page.getByTestId("active-view-report")).toBeVisible();
|
||
});
|
||
|
||
test("breakpoint switches between desktop / tablet / mobile", async ({
|
||
page,
|
||
}, testInfo) => {
|
||
// Use a single chromium-desktop run to drive all three viewports in
|
||
// the same browser. Other projects skip — the viewport diff is the
|
||
// goal here, not browser-specific behaviour.
|
||
if (testInfo.project.name !== "chromium-desktop") {
|
||
test.skip();
|
||
return;
|
||
}
|
||
await bootShell(page);
|
||
|
||
// Desktop ≥ 1024: sidebar visible, bottom-tabs hidden, sidebar
|
||
// toggle hidden.
|
||
await page.setViewportSize({ width: 1280, height: 800 });
|
||
await expect(page.getByTestId("sidebar")).toBeVisible();
|
||
await expect(page.getByTestId("bottom-tabs")).not.toBeVisible();
|
||
await expect(page.getByTestId("sidebar-toggle")).not.toBeVisible();
|
||
|
||
// Tablet 768–1024: sidebar hidden by default, sidebar toggle
|
||
// visible, bottom-tabs hidden. Click the toggle and the sidebar
|
||
// becomes visible again.
|
||
await page.setViewportSize({ width: 900, height: 800 });
|
||
await expect(page.getByTestId("sidebar")).not.toBeVisible();
|
||
await expect(page.getByTestId("sidebar-toggle")).toBeVisible();
|
||
await expect(page.getByTestId("bottom-tabs")).not.toBeVisible();
|
||
await page.getByTestId("sidebar-toggle").click();
|
||
await expect(page.getByTestId("sidebar")).toBeVisible();
|
||
|
||
// Mobile < 768: sidebar hidden entirely, bottom-tabs visible,
|
||
// sidebar toggle hidden again.
|
||
await page.setViewportSize({ width: 390, height: 800 });
|
||
await expect(page.getByTestId("bottom-tabs")).toBeVisible();
|
||
await expect(page.getByTestId("sidebar")).not.toBeVisible();
|
||
await expect(page.getByTestId("sidebar-toggle")).not.toBeVisible();
|
||
});
|