diff --git a/apps/client-standalone/src/local-bridge.ts b/apps/client-standalone/src/local-bridge.ts index dbd7e326d2..df57db2e84 100644 --- a/apps/client-standalone/src/local-bridge.ts +++ b/apps/client-standalone/src/local-bridge.ts @@ -6,10 +6,28 @@ function showFatalErrorDialog(message: string) { alert(message); } +/** + * Collects query params from both `location.search` and the hash's "?..." + * suffix. The SPA uses hash-based routing, so flags like `?integrationTest=memory` + * often end up after the `#` (e.g. `/#root/foo?integrationTest=memory`) and + * are invisible to `location.search`. + */ +function collectQueryString(): string { + const params = new URLSearchParams(location.search); + const hashQueryIndex = location.hash.indexOf("?"); + if (hashQueryIndex >= 0) { + const hashParams = new URLSearchParams(location.hash.substring(hashQueryIndex + 1)); + for (const [key, value] of hashParams) { + if (!params.has(key)) params.set(key, value); + } + } + return params.toString(); +} + export function startLocalServerWorker() { if (localWorker) return localWorker; localWorker = new LocalServerWorker(); - localWorker.postMessage({ type: "INIT", queryString: location.search }); + localWorker.postMessage({ type: "INIT", queryString: collectQueryString() }); // Handle worker errors during initialization localWorker.onerror = (event) => { diff --git a/apps/client-standalone/src/local-server-worker.ts b/apps/client-standalone/src/local-server-worker.ts index 647db5384b..5ea83dcbec 100644 --- a/apps/client-standalone/src/local-server-worker.ts +++ b/apps/client-standalone/src/local-server-worker.ts @@ -74,6 +74,30 @@ let initPromise: Promise | null = null; let initError: Error | null = null; let queryString = ""; +/** + * Remove every entry from the OPFS root. Used by integration tests to + * guarantee a clean slate — the previous run's DB, logs, and backups would + * otherwise survive and cross-contaminate the next run. + */ +async function clearOpfs(): Promise { + if (typeof navigator === "undefined" || !navigator.storage?.getDirectory) { + return; + } + console.log("[Worker] Clearing OPFS..."); + const root = await navigator.storage.getDirectory(); + const names: string[] = []; + for await (const name of (root as unknown as { keys(): AsyncIterableIterator }).keys()) { + names.push(name); + } + for (const name of names) { + try { + await root.removeEntry(name, { recursive: true }); + } catch (err) { + console.warn(`[Worker] Failed to remove OPFS entry "${name}":`, err); + } + } +} + /** * Load all required modules using dynamic imports. * This allows errors to be caught by our error handlers. @@ -149,7 +173,13 @@ async function initialize(): Promise { const params = new URLSearchParams(queryString); const integrationTestMode = params.get("integrationTest"); + console.log("Starting with integration test mode ", integrationTestMode); + if (integrationTestMode === "memory") { + // Wipe OPFS so e2e runs start from a clean slate (stale DB, logs, + // backups from previous sessions would otherwise leak across runs). + await clearOpfs(); + // Load the pre-built test fixture database for e2e tests console.log("[Worker] Integration test mode: loading fixture database..."); const response = await fetch("/test-fixtures/document.db"); diff --git a/apps/client-standalone/vite.config.mts b/apps/client-standalone/vite.config.mts index c53855e2ed..af90d334a8 100644 --- a/apps/client-standalone/vite.config.mts +++ b/apps/client-standalone/vite.config.mts @@ -164,6 +164,7 @@ if (process.env.TRILIUM_INTEGRATION_TEST) { { src: "../../../packages/trilium-core/src/test/fixtures/document.db", dest: "test-fixtures", + rename: "document.db" } ] })