2026-08-06 15:43:57 +00:00
|
|
|
import type {BrowserContext} from "@playwright/test"
|
2026-09-02 22:21:18 +00:00
|
|
|
import {ms} from "@welshman/lib"
|
2026-08-06 15:43:57 +00:00
|
|
|
import type {TrustedEvent} from "@welshman/util"
|
|
|
|
|
import type {TestUser} from "../keys"
|
2026-09-29 12:52:30 +00:00
|
|
|
import {injectChecked, injectEvents, injectSession} from "./session"
|
2026-08-06 15:43:57 +00:00
|
|
|
|
|
|
|
|
// Must match TEST_ENV_KEY in src/lib/test/env.ts.
|
|
|
|
|
const TEST_ENV_KEY = "__TEST_ENV__"
|
|
|
|
|
|
2026-09-24 18:23:21 +00:00
|
|
|
// Set the first time the app reads the injected env, the only evidence here that src/app/env.ts ran.
|
2026-08-06 15:43:57 +00:00
|
|
|
const TEST_ENV_READ_KEY = "__TEST_ENV_READ__"
|
|
|
|
|
|
|
|
|
|
export type BootOptions = {
|
2026-09-24 18:23:21 +00:00
|
|
|
// Every relay list the app reads at startup is pointed here, so it can only dial the scenario's.
|
2026-08-06 15:43:57 +00:00
|
|
|
relays: string[]
|
2026-09-24 18:23:21 +00:00
|
|
|
// What a pubkey's own lists are resolved from, defaulting to `relays`.
|
2026-09-10 03:28:02 +00:00
|
|
|
indexers?: string[]
|
2026-08-06 15:43:57 +00:00
|
|
|
spaces?: string[]
|
|
|
|
|
user?: TestUser
|
2026-09-30 17:21:48 +00:00
|
|
|
// Whether this user signs through the window.nostr injectNip07 installed, rather than with a key.
|
|
|
|
|
nip07?: boolean
|
2026-08-06 15:43:57 +00:00
|
|
|
// What this user's client already has in local storage, e.g. their room list.
|
|
|
|
|
events?: TrustedEvent[]
|
2026-09-29 12:52:30 +00:00
|
|
|
// Which paths this user has already read, keyed the way src/app/notifications.ts keys them.
|
|
|
|
|
checked?: Record<string, number>
|
2026-08-06 15:43:57 +00:00
|
|
|
path?: string
|
2026-09-24 18:23:21 +00:00
|
|
|
// VITE_ values the scenario sets for itself. Anything named here has to be something the test owns.
|
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-29 12:52:30 +00:00
|
|
|
{
|
|
|
|
|
relays,
|
|
|
|
|
indexers = relays,
|
|
|
|
|
spaces = [],
|
|
|
|
|
user,
|
2026-09-30 17:21:48 +00:00
|
|
|
nip07 = false,
|
2026-09-29 12:52:30 +00:00
|
|
|
events = [],
|
|
|
|
|
checked,
|
|
|
|
|
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: "",
|
2026-09-24 18:23:21 +00:00
|
|
|
// Nothing serves this url, so a push bridge connection is reported as a leak.
|
2026-08-06 15:43:57 +00:00
|
|
|
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) {
|
2026-09-30 17:21:48 +00:00
|
|
|
await injectSession(context, user, nip07)
|
2026-08-06 15:43:57 +00:00
|
|
|
await injectEvents(context, events)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 12:52:30 +00:00
|
|
|
if (checked) {
|
|
|
|
|
await injectChecked(context, checked)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-06 15:43:57 +00:00
|
|
|
const page = await context.newPage()
|
|
|
|
|
|
|
|
|
|
await page.goto(path)
|
|
|
|
|
|
2026-09-24 18:23:21 +00:00
|
|
|
// The root layout renders nothing until its async setup resolves, and a rejected session renders the landing dialog.
|
2026-09-02 22:21:18 +00:00
|
|
|
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) {
|
2026-09-21 16:48:35 +00:00
|
|
|
if (attempt === 3) {
|
|
|
|
|
throw e
|
|
|
|
|
}
|
2026-09-02 22:21:18 +00:00
|
|
|
|
|
|
|
|
await page.reload()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-06 15:43:57 +00:00
|
|
|
|
2026-09-24 18:23:21 +00:00
|
|
|
// src/app/env.ts reads every VITE_ value as it is imported, so by now the app has resolved them.
|
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
|
|
|
|
|
}
|