Add standing Playwright e2e suite; fix two real bugs it caught
The whole point of driving a real browser instead of curling api/frontend separately: none of this session's prior "live verification" ever exercised the same-origin routing production relies on (Caddy: /ws*+/api/* -> api, else -> frontend), so a real browser's relative fetch()/WebSocket calls were never actually proven to resolve. e2e/proxy.ts mirrors that routing (no caddy binary available locally); global-setup.ts/global-teardown.ts orchestrate throwaway infra + seeded data + the api/frontend/proxy processes end to end. Getting the suite green surfaced two genuine bugs invisible to unit tests: - index.pug loaded map.js via two <script type="module"> tags (one moved to <head> to fix load-order, the original left in place by mistake), causing Alpine's x-init="init()" to run twice and Leaflet to throw "Map container is already initialized" on the second call. - map.js's exportRegion() passed the Alpine-reactive `regionBounds` object straight into worker.postMessage(); Alpine wraps assigned state in Proxies, which the structured clone algorithm can't clone, so every export silently failed. Fixed by spreading into a plain object first. Covers the two flows flagged all session as verified only at the unit/curl level: the marker click-to-place/edit popup (including that a marker created while linked shows up in a second browser context with the same session, proving server-side sync) and the region-select drag + glTF export (including a real triggered file download).
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// Shared constants between global-setup.ts, global-teardown.ts, playwright.config.ts, and the
|
||||
// proxy process — kept in one place deliberately (see region-select.js's MAX_EXPORT_CHUNKS
|
||||
// comment for why hand-synced duplicate constants across files are worth avoiding when a single
|
||||
// source is this easy).
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
export const ROOT = join(import.meta.dirname, "..");
|
||||
export const API_DIR = join(ROOT, "api");
|
||||
export const FRONTEND_DIR = join(ROOT, "frontend");
|
||||
|
||||
// Distinct container names/ports from the README's `bun test` throwaway infra (mcmapper-test-*
|
||||
// on 15432/16379/19000) so an e2e run and a `bun test` run can happen at the same time without
|
||||
// colliding.
|
||||
export const PG_CONTAINER = "mcmapper-e2e-pg";
|
||||
export const REDIS_CONTAINER = "mcmapper-e2e-redis";
|
||||
export const MINIO_CONTAINER = "mcmapper-e2e-minio";
|
||||
|
||||
export const PG_PORT = 15433;
|
||||
export const REDIS_PORT = 16380;
|
||||
export const MINIO_PORT = 19002;
|
||||
|
||||
export const API_PORT = 13010;
|
||||
export const FRONTEND_PORT = 13011;
|
||||
export const PROXY_PORT = 14010;
|
||||
|
||||
export const DATABASE_URL = `postgres://mcmapper:mcmapper@localhost:${PG_PORT}/mcmapper`;
|
||||
export const REDIS_URL = `redis://localhost:${REDIS_PORT}`;
|
||||
export const MINIO_ACCESS_KEY = "mcmapper";
|
||||
export const MINIO_SECRET_KEY = "mcmapper-e2e-only";
|
||||
|
||||
export const PROXY_ORIGIN = `http://localhost:${PROXY_PORT}`;
|
||||
|
||||
export const STATE_FILE = join(import.meta.dirname, ".e2e-state.json");
|
||||
|
||||
export type E2eState = {
|
||||
pids: number[];
|
||||
containers: string[];
|
||||
baseURL: string;
|
||||
serverId: string;
|
||||
serverToken: string;
|
||||
sessionToken: string;
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
// Read by spec files — by the time tests run, global-setup.ts has already written this.
|
||||
export function readE2eState(): E2eState {
|
||||
return JSON.parse(readFileSync(STATE_FILE, "utf-8"));
|
||||
}
|
||||
Reference in New Issue
Block a user