flotilla/e2e/harness/app/boot.ts

112 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-08-06 15:43:57 +00:00
import type {BrowserContext} from "@playwright/test"
import {ms} from "@welshman/lib"
2026-08-06 15:43:57 +00:00
import type {TrustedEvent} from "@welshman/util"
import type {TestUser} from "../keys"
import {injectEvents, injectSession} from "./session"
// Must match TEST_ENV_KEY in src/lib/test/env.ts.
const TEST_ENV_KEY = "__TEST_ENV__"
// Set the first time the app reads a value out of the injected env, the only evidence this side
// has that the hook in src/app/env.ts ran.
2026-08-06 15:43:57 +00:00
const TEST_ENV_READ_KEY = "__TEST_ENV_READ__"
export type BootOptions = {
// Every relay list the app reads at startup is pointed here, so it can only dial relays the
// scenario created.
2026-08-06 15:43:57 +00:00
relays: string[]
2026-09-10 03:28:02 +00:00
// What a pubkey's own lists are resolved from, which is a relay of its own only when the
// scenario opened one. Defaults to `relays`.
indexers?: string[]
2026-08-06 15:43:57 +00:00
spaces?: string[]
user?: TestUser
// What this user's client already has in local storage, e.g. their room list.
events?: TrustedEvent[]
path?: string
2026-08-20 20:33:20 +00:00
// VITE_ values the scenario sets for itself, applied over the relay-derived ones below. Anything
// named here has to be something the test owns, or the test fails on a leak.
2026-08-20 20:33:20 +00:00
env?: Record<string, string>
2026-08-06 15:43:57 +00:00
}
export const boot = async (
context: BrowserContext,
2026-09-10 03:28:02 +00:00
{relays, indexers = relays, spaces = [], user, events = [], path = "/", env = {}}: BootOptions,
2026-08-06 15:43:57 +00:00
) => {
const urls = relays.join(",")
await context.addInitScript(
([key, readKey, env]) => {
Object.assign(window, {
[key]: new Proxy(env, {
get(target, prop) {
Object.assign(window, {[readKey]: true})
return Reflect.get(target, prop)
},
}),
})
},
[
TEST_ENV_KEY,
TEST_ENV_READ_KEY,
{
VITE_DEFAULT_RELAYS: urls,
2026-09-10 03:28:02 +00:00
VITE_INDEXER_RELAYS: indexers.join(","),
2026-08-06 15:43:57 +00:00
VITE_DEFAULT_SEARCH_RELAYS: urls,
VITE_DEFAULT_MESSAGING_RELAYS: urls,
VITE_SIGNER_RELAYS: urls,
VITE_DEFAULT_SPACES: spaces.join(","),
VITE_PLATFORM_RELAYS: "",
VITE_BLOCKED_RELAYS: "",
// Nothing serves this url, so a push bridge connection is reported as a leak instead of
// blending into the traffic of a relay the scenario did create.
VITE_PUSH_BRIDGE: "ws://localhost:1/",
2026-08-20 20:33:20 +00:00
...env,
2026-08-06 15:43:57 +00:00
},
] as const,
)
if (user) {
await injectSession(context, user)
await injectEvents(context, events)
}
const page = await context.newPage()
await page.goto(path)
// The root layout renders nothing until its async setup block resolves, so the shell appearing
// is the first point at which the app is running. With a session injected, wait for the signed-in
// nav instead. A session the app rejected renders the landing dialog, and failing on that here
// reads far better than the assertions it would break later.
const shell = page.locator(user ? ".primary-nav" : ".fl")
for (let attempt = 0; ; attempt++) {
try {
await shell.waitFor({state: "attached", timeout: ms(15)})
break
} catch (e) {
if (attempt === 3) throw e
await page.reload()
}
}
2026-08-06 15:43:57 +00:00
// src/app/env.ts reads every VITE_ value as it is imported, so by now the app has resolved them
// against either the env above or .env's real relays.
2026-08-06 15:43:57 +00:00
const usedTestEnv = await page.evaluate(
key => Boolean(Reflect.get(window, key)),
TEST_ENV_READ_KEY,
)
if (!usedTestEnv) {
throw new Error(
"The app read none of its VITE_ values from the harness, so it is pointed at the relays in " +
".env rather than at this scenario's. Check that src/app/env.ts still resolves them " +
"through maybeGetTestEnv, and that the dev server is running in dev mode.",
)
}
return page
}