git » sdk » commit cb22a84

Make our browser testing much faster

author Eric Roberts
2026-09-10 19:47:57 UTC
committer Stephen Paul Weber
2026-09-14 14:02:44 UTC
parent 49f911a2d199d6ce74b5a8c7d45878e232c56583

Make our browser testing much faster

We were using playwright essentially to unit test the js version of the
sdk in actual browser engines.

We can get the same effect, but much faster, with vitest browser
mode*

This takes our tests from 32s on my computer down to about 6s.

It also means we don't have to do this page.evaluate thing and
serialize things across the page boundary over and over, which was a pain.

Now that we're not using page.evaluate it's exposed a lot more type
errors if your editor shows them. Fixing this seemed a little out of
scope since we don't check anyway and the tests pass, but I'd like to
come back to it in the future.

A big part of the reason this is faster is only initializing one sqlite
db and resetting it per test. I tried applying just this fix to the
playwright version and it also sped things up a fair bit, from 21s
(for just the sqlite tests) to 8.35s. But that's still slower overall,
and I think not having to do the page.evaluate stuff is a big quality of
life improvement.

Something about the vitest packages being added caused tsc to fail with
this error:

```
Cannot find module 'assertion-
  error'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the
  'paths' option?

import { AssertionError as ImportedAssertionError } from "assertion-error";
```

Changing moduleResolution to node fixes this. It appears to me from
reading that without this we are getting "classic"†, which is apparently
not to be used‡.

* https://vitest.dev/guide/browser/
† https://www.typescriptlang.org/docs/handbook/modules/theory.html#module-resolution-is-host-defined
‡ https://www.typescriptlang.org/docs/handbook/modules/reference.html#classic

.gitignore +2 -0
Makefile +7 -6
npm/package.json +5 -2
playwright.config.ts +1 -3
test/browser-test.ts +0 -202
test/idb.spec.ts +115 -82
test/persistence-tests.ts +1546 -2082
test/sqlite.spec.ts +62 -1
vitest.config.mts +39 -0

diff --git a/.gitignore b/.gitignore
index c6137e0..b8ba41e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,5 @@ docs/js/borogove*.md
 /blob-report/
 /playwright/.cache/
 /playwright/.auth/
+
+.vitest
diff --git a/Makefile b/Makefile
index dd67f5a..b9299b4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
 HAXE_PATH=$$HOME/Software/haxe-4.3.1/hxnodejs/12,1,0/src
 CJSTOESM=npx -p typescript@6 -p cjstoesm cjstoesm
 
-.PHONY: all test doc format format-check hx-build-dep cpp/libborogove.dso npm/borogove-browser.js npm/borogove.js cpp playwright ci
+.PHONY: all test doc format format-check hx-build-dep cpp/libborogove.dso npm/borogove-browser.js npm/borogove.js cpp browser-tests ci
 
 all: npm libborogove.batteriesincluded.so .WAIT libborogove.so libborogove.a
 
 test:
 	haxe test.hxml
 
-ci: format-check lint test playwright
+ci: format-check lint test browser-tests
 	mkdir -p .cache
 	haxe testjs.hxml
 	haxe testcpp.hxml
@@ -64,9 +64,9 @@ npm: format-check lint npm/borogove-browser.js npm/borogove.js borogove/persiste
 	cp borogove/persistence/MediaStoreCache.js npm
 	cp borogove/persistence/sqlite-worker1.mjs npm
 	$(RM) npm/calls.js npm/persistence.js npm/persistence-browser.js npm/borogove-enums.js
-	-cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --preserveConstEnums --allowJs --checkJs -d index.ts > /dev/null
-	cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --preserveConstEnums --allowJs --checkJs -d index.ts
-	cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --preserveConstEnums --allowJs --checkJs -d persistence-browser.ts
+	-cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --moduleResolution node --preserveConstEnums --allowJs --checkJs -d index.ts > /dev/null
+	cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --moduleResolution node --preserveConstEnums --allowJs --checkJs -d index.ts
+	cd npm && npx tsc --esModuleInterop --lib esnext,dom --target esnext --moduleResolution node --preserveConstEnums --allowJs --checkJs -d persistence-browser.ts
 
 playwright/.cache/borogove.js: npm
 	cd npm && esbuild index.js --bundle --format=esm "--alias:node:dns=./test/no-dns.mjs" "--footer:js=export { borogove_JID as JID, borogove_Stanza as Stanza, borogove_ReactionUpdate as ReactionUpdate, borogove_MemberUpdate as MemberUpdate }" --outfile=../$@
@@ -84,8 +84,9 @@ playwright/.cache/sqlite-worker1.js: npm
 	$(RM) $@.mjs
 	$(RM) $@.mjs.bak
 
-playwright: playwright/.cache/borogove.js playwright/.cache/sqlite-wasm.js playwright/.cache/sqlite-worker1.js
+browser-tests: playwright/.cache/borogove.js playwright/.cache/sqlite-wasm.js playwright/.cache/sqlite-worker1.js
 	npx playwright test
+	cd npm && npm run test:vitest-browser
 
 cpp/libborogove.dso:
 	haxe cpp.hxml
diff --git a/npm/package.json b/npm/package.json
index 9479a79..26fd6be 100644
--- a/npm/package.json
+++ b/npm/package.json
@@ -23,7 +23,7 @@
 		"./sqlite-wasm": "./sqlite-wasm.js"
 	},
 	"scripts": {
-		"test": "echo \"Error: no test specified\" && exit 1"
+		"test:vitest-browser": "vitest run --config ../vitest.config.mts"
 	},
 	"author": "",
 	"license": "Apache-2.0",
@@ -40,8 +40,11 @@
 	"devDependencies": {
 		"@playwright/test": "1.60.0",
 		"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
+		"@vitest/browser": "^5.0.0",
+		"@vitest/browser-playwright": "^5.0.0",
 		"oxlint": "^1.82.0",
 		"oxfmt": "^0.67.0",
-		"typescript": "^5.4.5"
+		"typescript": "^5.4.5",
+		"vitest": "^5.0.0"
 	}
 }
diff --git a/playwright.config.ts b/playwright.config.ts
index 730adc1..656f03a 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -14,6 +14,7 @@ import { defineConfig, devices } from "@playwright/test";
 export default defineConfig({
 	timeout: 260000,
 	testDir: "./test",
+	testMatch: ["**/fast-auth.spec.ts"],
 	webServer: {
 		command: "node playwright/server.mjs",
 		url: "http://127.0.0.1:49276",
@@ -51,9 +52,6 @@ export default defineConfig({
 		{
 			name: "webkit",
 			use: { ...devices["Desktop Safari"] },
-			// Skip sqlite tests on webkit because the
-			// version in playwright lacks OPFS
-			testIgnore: "sqlite.spec.ts",
 		},
 	],
 });
diff --git a/test/browser-test.ts b/test/browser-test.ts
deleted file mode 100644
index 036e096..0000000
--- a/test/browser-test.ts
+++ /dev/null
@@ -1,202 +0,0 @@
-import {
-	test as base,
-	expect,
-	type JSHandle,
-	type Page,
-} from "@playwright/test";
-import type { borogove_Persistence } from "../npm/borogove-browser";
-import type { borogove_persistence_Sqlite } from "../npm/sqlite-wasm";
-
-type PageKeyPair = {
-	privKey: number[];
-	pubKey: number[];
-};
-
-type PageHelpers = {
-	keyToBuffer(key: number[]): ArrayBuffer;
-	bufferToKey(key: ArrayBuffer): number[];
-	keyPairToBuffers(keyPair: PageKeyPair): {
-		privKey: ArrayBuffer;
-		pubKey: ArrayBuffer;
-	};
-	buffersToKeyPair(keyPair: {
-		privKey: ArrayBuffer;
-		pubKey: ArrayBuffer;
-	}): PageKeyPair;
-};
-
-type BrowserFixtures = {
-	borogove: JSHandle<any>;
-	createChannel: JSHandle<any>;
-	pageHelpers: JSHandle<PageHelpers>;
-	persistence: JSHandle<borogove_Persistence>;
-	storeIncompleteMember: JSHandle<any>;
-};
-
-type SqliteFixtures = BrowserFixtures & {
-	sqlite: JSHandle<any>;
-};
-
-declare global {
-	interface Window {
-		borogove: any;
-		sqlite: any;
-		sqliteWorker1Url: URL;
-	}
-}
-
-const pageHelpers = async (
-	{ page }: { page: Page },
-	use: (helpers: JSHandle<PageHelpers>) => Promise<void>,
-) => {
-	const helpers = await page.evaluateHandle(() => ({
-		keyToBuffer: (key: number[]) => new Uint8Array(key).buffer,
-		bufferToKey: (key: ArrayBuffer) => [...new Uint8Array(key)],
-		keyPairToBuffers: (keyPair: PageKeyPair) => ({
-			privKey: new Uint8Array(keyPair.privKey).buffer,
-			pubKey: new Uint8Array(keyPair.pubKey).buffer,
-		}),
-		buffersToKeyPair: (keyPair: {
-			privKey: ArrayBuffer;
-			pubKey: ArrayBuffer;
-		}) => ({
-			privKey: [...new Uint8Array(keyPair.privKey)],
-			pubKey: [...new Uint8Array(keyPair.pubKey)],
-		}),
-	}));
-	await use(helpers);
-};
-
-export const idbTest = base.extend<BrowserFixtures>({
-	page: async ({ page }, use) => {
-		await page.goto("/idb");
-		await use(page);
-	},
-	borogove: async ({ page }, use) => {
-		const borogove = await page.evaluateHandle(() => window.borogove);
-		await use(borogove);
-	},
-	pageHelpers,
-	createChannel: async ({ page, borogove }, use) => {
-		const createChannel = await page.evaluateHandle(
-			(borogove) => (persistence, chatId) =>
-				new borogove.Channel(null, null, persistence, chatId),
-			borogove,
-		);
-		await use(createChannel);
-	},
-	persistence: async ({ page, borogove }, use) => {
-		const persistence = await page.evaluateHandle(async (borogove) => {
-			const mediaStore = await borogove.persistence.MediaStoreCache("snikket");
-			return borogove.persistence.IDB("snikket", mediaStore);
-		}, borogove);
-		await use(persistence);
-	},
-	storeIncompleteMember: async ({ page }, use) => {
-		const storeIncompleteMember = await page.evaluateHandle(
-			() => async (chatId) => {
-				const request = indexedDB.open("snikket");
-				const db = await new Promise<IDBDatabase>((resolve, reject) => {
-					request.onsuccess = () => resolve(request.result);
-					request.onerror = () => reject(request.error);
-				});
-				const transaction = db.transaction(["members"], "readwrite");
-				transaction.objectStore("members").put({
-					account: "alice@example.com",
-					chatId,
-					id: "room-members-7@example.com/incomplete",
-					displayName: "",
-					photoUri: null,
-					isSelf: 0,
-					chat: "",
-					roles: [],
-					presence: new Map(),
-					jid: "",
-				});
-				await new Promise((resolve, reject) => {
-					transaction.oncomplete = () => resolve(null);
-					transaction.onerror = () => reject(transaction.error);
-				});
-			},
-		);
-		await use(storeIncompleteMember);
-	},
-});
-
-export const sqliteTest = base.extend<SqliteFixtures>({
-	page: async ({ page }, use) => {
-		await page.goto("/sqlite");
-		await use(page);
-	},
-	borogove: async ({ page }, use) => {
-		const borogove = await page.evaluateHandle(() => window.borogove);
-		await use(borogove);
-	},
-	pageHelpers,
-	sqlite: async ({ page }, use) => {
-		const sqlite = await page.evaluateHandle(() => window.sqlite);
-		await use(sqlite);
-	},
-	createChannel: async ({ page, sqlite }, use) => {
-		const createChannel = await page.evaluateHandle(
-			(sqlite) => (persistence, chatId) =>
-				new sqlite.Channel(null, null, persistence, chatId),
-			sqlite,
-		);
-		await use(createChannel);
-	},
-	persistence: async ({ page, borogove, sqlite }, use) => {
-		const persistence = await page.evaluateHandle(
-			async ({ borogove, sqlite }) => {
-				const mediaStore =
-					await borogove.persistence.MediaStoreCache("snikket");
-				return new sqlite.borogove_persistence_Sqlite("snikket", mediaStore);
-			},
-			{ borogove, sqlite },
-		);
-		await page.evaluate((persistence: borogove_persistence_Sqlite) => {
-			// TODO: remove this wrapper
-			//
-			// storeChats is not currently awaitable. The Sqlite tests were adding
-			// manual delays that the IDB tests didn't need. I wanted to share the
-			// tests, so wrapping the implementation with a delay here lets me do
-			// that.
-			//
-			// Changing storeChats to return a Promise that could be awaited would
-			// be the better long term solution, but there's already a debounce
-			// thing in storeChats itself that maybe should be revisited and that
-			// seemed like a riskier change, so I went with this for now.
-			const storeChats = persistence.storeChats.bind(persistence);
-			persistence.storeChats = (...args) => {
-				storeChats(...args);
-				return new Promise((resolve) => setTimeout(resolve, 200));
-			};
-		}, persistence);
-		await use(persistence);
-	},
-	storeIncompleteMember: async ({ page, persistence }, use) => {
-		const storeIncompleteMember = await page.evaluateHandle(
-			(persistence) => async (chatId) => {
-				await persistence.db.exec(
-					"INSERT INTO members(account_id, chat_id, member_id, display_name, photo_uri, is_self, chat, roles, presence, jid) VALUES(?, ?, ?, ?, ?, ?, ?, jsonb(?), jsonb(?), ?)",
-					[
-						"alice@example.com",
-						chatId,
-						"room-members-7@example.com/incomplete",
-						"",
-						null,
-						0,
-						"{}",
-						"[]",
-						"{}",
-						"",
-					],
-				);
-			},
-			persistence,
-		);
-		await use(storeIncompleteMember);
-	},
-});
-
-export { expect };
diff --git a/test/idb.spec.ts b/test/idb.spec.ts
index 420e047..36df8c5 100644
--- a/test/idb.spec.ts
+++ b/test/idb.spec.ts
@@ -1,90 +1,123 @@
-import { idbTest as test, expect } from "./browser-test";
+import { expect, test } from "vitest";
+import * as borogove from "../playwright/.cache/borogove.js";
 import { sharedPersistenceTests } from "./persistence-tests";
 
-sharedPersistenceTests(test);
-
-test("hydrate message with incomplete replyToMessage keys", async ({
-	page,
-	borogove,
-	persistence,
-}) => {
-	const result = await page.evaluate(
-		async ({ borogove, persistence }) => {
-			const builder = new borogove.ChatMessageBuilder({
-				serverId: "parent",
-				serverIdBy: "hatter@example.com",
-				localId: "loc1",
-				senderId: "hatter@example.com",
-				direction: 0,
-			});
-			builder.sortId = "a0";
-			builder.to = borogove.JID.parse("alice@example.com");
-			builder.from = borogove.JID.parse("hatter@example.com");
-			builder.recipients = [builder.to];
-			builder.replyTo = [builder.from];
-			const parentMsg = builder.build();
-
-			const builder2 = new borogove.ChatMessageBuilder({
-				serverId: "child",
-				serverIdBy: "hatter@example.com",
-				localId: "loc2",
-				senderId: "hatter@example.com",
-				direction: 0,
-			});
-			builder2.sortId = "a1";
-			builder2.to = borogove.JID.parse("alice@example.com");
-			builder2.from = borogove.JID.parse("hatter@example.com");
-			builder2.recipients = [builder2.to];
-			builder2.replyTo = [builder2.from];
-			builder2.replyToMessage = parentMsg;
-			const childMsg = builder2.build();
+async function idbFixture() {
+	const databaseName = `idb-${crypto.randomUUID()}`;
+	const mediaStore = await borogove.persistence.MediaStoreCache(
+		`${databaseName}-media`,
+	);
+	const persistence = await borogove.persistence.IDB(databaseName, mediaStore);
+	const createChannel = (p: any, chatId: string) =>
+		new borogove.Channel(null, null, p, chatId);
+	const storeIncompleteMember = async (chatId: string) => {
+		const request = indexedDB.open(databaseName);
+		const db = await new Promise<IDBDatabase>((resolve, reject) => {
+			request.onsuccess = () => resolve(request.result);
+			request.onerror = () => reject(request.error);
+		});
+		const transaction = db.transaction(["members"], "readwrite");
+		transaction.objectStore("members").put({
+			account: "alice@example.com",
+			chatId,
+			id: "room-members-7@example.com/incomplete",
+			displayName: "",
+			photoUri: null,
+			isSelf: 0,
+			chat: "",
+			roles: [],
+			presence: new Map(),
+			jid: "",
+		});
+		await new Promise<void>((resolve, reject) => {
+			transaction.oncomplete = () => resolve();
+			transaction.onerror = () => reject(transaction.error);
+		});
+	};
+	return {
+		borogove,
+		persistence,
+		createChannel,
+		storeIncompleteMember,
+		databaseName,
+	};
+}
 
-			await persistence.storeMessages("alice@example.com", [
-				parentMsg,
-				childMsg,
-			]);
+function idbTest(name: string, fn: (fixture: any) => Promise<void>) {
+	test("idb: " + name, async () => {
+		const current = await idbFixture();
+		await fn(current);
+	});
+}
 
-			const db = await new Promise<IDBDatabase>((resolve, reject) => {
-				const req = indexedDB.open("snikket");
-				req.onsuccess = () => resolve(req.result);
-				req.onerror = () => reject(req.error);
-			});
-			const tx = db.transaction(["messages"], "readwrite");
-			const store = tx.objectStore("messages");
-			const key = ["alice@example.com", "child", "hatter@example.com", "loc2"];
-			const rawChild = await new Promise<{ replyToMessage: string[] }>(
-				(resolve) => {
-					const req = store.get(key);
-					req.onsuccess = () => resolve(req.result);
-				},
-			);
+sharedPersistenceTests(idbTest);
 
-			rawChild.replyToMessage = ["alice@example.com", "parent", "", ""];
+idbTest(
+	"hydrate message with incomplete replyToMessage keys",
+	async ({ borogove, persistence, databaseName }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "parent",
+			serverIdBy: "hatter@example.com",
+			localId: "loc1",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		const parentMsg = builder.build();
 
-			await new Promise<void>((resolve) => {
-				const req = store.put(rawChild);
-				req.onsuccess = () => resolve();
-			});
-			await new Promise<void>((resolve) => {
-				tx.oncomplete = () => resolve();
-			});
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "child",
+			serverIdBy: "hatter@example.com",
+			localId: "loc2",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+		builder2.replyToMessage = parentMsg;
+		const childMsg = builder2.build();
 
-			const retrievedChild = await persistence.getMessage(
-				"alice@example.com",
-				"hatter@example.com",
-				"child",
-				"loc2",
-			);
-			return {
-				hasReply: !!retrievedChild.replyToMessage,
-				replyServerId: retrievedChild.replyToMessage
-					? retrievedChild.replyToMessage.serverId
-					: null,
-			};
-		},
-		{ borogove, persistence },
-	);
+		await persistence.storeMessages("alice@example.com", [parentMsg, childMsg]);
+		const db = await new Promise<IDBDatabase>((resolve, reject) => {
+			const request = indexedDB.open(databaseName);
+			request.onsuccess = () => resolve(request.result);
+			request.onerror = () => reject(request.error);
+		});
+		const transaction = db.transaction(["messages"], "readwrite");
+		const store = transaction.objectStore("messages");
+		const rawChild = await new Promise<{ replyToMessage: string[] }>(
+			(resolve, reject) => {
+				const request = store.get([
+					"alice@example.com",
+					"child",
+					"hatter@example.com",
+					"loc2",
+				]);
+				request.onsuccess = () => resolve(request.result);
+				request.onerror = () => reject(request.error);
+			},
+		);
+		rawChild.replyToMessage = ["alice@example.com", "parent", "", ""];
+		store.put(rawChild);
+		await new Promise<void>((resolve, reject) => {
+			transaction.oncomplete = () => resolve();
+			transaction.onerror = () => reject(transaction.error);
+		});
 
-	expect(result.hasReply).toBe(true);
-	expect(result.replyServerId).toBe("parent");
-});
+		const retrievedChild = await persistence.getMessage(
+			"alice@example.com",
+			"hatter@example.com",
+			"child",
+			"loc2",
+		);
+		expect(!!retrievedChild.replyToMessage).toBe(true);
+		expect(retrievedChild.replyToMessage?.serverId).toBe("parent");
+	},
+);
diff --git a/test/persistence-tests.ts b/test/persistence-tests.ts
index 0a4ef6c..658b65f 100644
--- a/test/persistence-tests.ts
+++ b/test/persistence-tests.ts
@@ -1,388 +1,288 @@
-import { idbTest, sqliteTest, expect } from "./browser-test";
-import { randomBytes } from "node:crypto";
+import { expect } from "vitest";
 
-type PersistenceTest = typeof idbTest | typeof sqliteTest;
-
-export function sharedPersistenceTests(test: PersistenceTest) {
-	test("storeChats and getChats", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = new borogove.DirectChat(
-					null,
-					null,
-					persistence,
-					"hatter@example.com",
-				);
-				chat.displayName = "The Mad Hatter";
-				chat.trusted = true;
-				chat.threads = new Map([
-					[null, "Tea Time"],
-					["thread-1", "Introductions"],
-				]);
-
-				await persistence.storeChats("alice@example.com", [chat]);
-				const chats = await persistence.getChats("alice@example.com");
-				return {
-					length: chats.length,
-					chatId: chats[0]?.chatId,
-					displayName: chats[0]?.displayName,
-					trusted: chats[0]?.trusted,
-					klass: chats[0]?.klass,
-					channelSubject: chats[0]?.threads?.get(null),
-					threadSubject: chats[0]?.threads?.get("thread-1"),
-				};
-			},
-			{ borogove, persistence },
+export function sharedPersistenceTests(test) {
+	test("storeChats and getChats", async ({ borogove, persistence }) => {
+		const chat = new borogove.DirectChat(
+			null,
+			null,
+			persistence,
+			"hatter@example.com",
 		);
-
-		expect(result.length).toBe(1);
-		expect(result.chatId).toBe("hatter@example.com");
-		expect(result.displayName).toBe("The Mad Hatter");
-		expect(result.trusted).toBe(true);
-		expect(result.klass).toBe("DirectChat");
-		expect(result.channelSubject).toBe("Tea Time");
-		expect(result.threadSubject).toBe("Introductions");
+		chat.displayName = "The Mad Hatter";
+		chat.trusted = true;
+		chat.threads = new Map([
+			[null, "Tea Time"],
+			["thread-1", "Introductions"],
+		]);
+
+		await persistence.storeChats("alice@example.com", [chat]);
+		const chats = await persistence.getChats("alice@example.com");
+		expect(chats.length).toBe(1);
+		expect(chats[0]?.chatId).toBe("hatter@example.com");
+		expect(chats[0]?.displayName).toBe("The Mad Hatter");
+		expect(chats[0]?.trusted).toBe(true);
+		expect(chats[0]?.klass).toBe("DirectChat");
+		expect(chats[0]?.threads?.get(null)).toBe("Tea Time");
+		expect(chats[0]?.threads?.get("thread-1")).toBe("Introductions");
 	});
 
 	test("storeChats and getChats with status", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = new borogove.DirectChat(
-					null,
-					null,
-					persistence,
-					"hatter@example.com",
-				);
-				chat.displayName = "The Mad Hatter";
-				chat.trusted = true;
-				chat.status = new borogove.Status("🎩", "Time for tea!");
-
-				await persistence.storeChats("alice@example.com", [chat]);
-				const chats = await persistence.getChats("alice@example.com");
-				return {
-					length: chats.length,
-					chatId: chats[0]?.chatId,
-					statusEmoji: chats[0]?.status?.emoji,
-					statusText: chats[0]?.status?.text,
-				};
-			},
-			{ borogove, persistence },
+		const chat = new borogove.DirectChat(
+			null,
+			null,
+			persistence,
+			"hatter@example.com",
 		);
-
-		expect(result.length).toBe(1);
-		expect(result.chatId).toBe("hatter@example.com");
-		expect(result.statusEmoji).toBe("🎩");
-		expect(result.statusText).toBe("Time for tea!");
+		chat.displayName = "The Mad Hatter";
+		chat.trusted = true;
+		chat.status = new borogove.Status("🎩", "Time for tea!");
+
+		await persistence.storeChats("alice@example.com", [chat]);
+		const chats = await persistence.getChats("alice@example.com");
+		expect(chats.length).toBe(1);
+		expect(chats[0]?.chatId).toBe("hatter@example.com");
+		expect(chats[0]?.status?.emoji).toBe("🎩");
+		expect(chats[0]?.status?.text).toBe("Time for tea!");
 	});
 
 	test("getChats uses member presence for direct chats", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = new borogove.DirectChat(
-					null,
-					null,
-					persistence,
-					"hatter@example.com",
-				);
-				chat.displayName = "The Mad Hatter";
-				chat.trusted = true;
-
-				await persistence.storeChats("alice@example.com", [chat]);
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "hatter@example.com",
-						displayName: "The Mad Hatter",
-						photoUri: null,
-						isSelf: false,
-						roles: [],
-						jid: borogove.JID.parse("hatter@example.com"),
-						presence: new Map([
-							["phone", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: null,
-					},
-				]);
-				const [stored] = await persistence.getChats("alice@example.com");
-				return [...stored.presence.keys()].sort();
-			},
-			{ borogove, persistence },
+		const chat = new borogove.DirectChat(
+			null,
+			null,
+			persistence,
+			"hatter@example.com",
 		);
+		chat.displayName = "The Mad Hatter";
+		chat.trusted = true;
 
-		expect(result).toEqual(["phone"]);
+		await persistence.storeChats("alice@example.com", [chat]);
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "hatter@example.com",
+				displayName: "The Mad Hatter",
+				photoUri: null,
+				isSelf: false,
+				roles: [],
+				jid: borogove.JID.parse("hatter@example.com"),
+				presence: new Map([["phone", borogove.Stanza.parse("<presence />")]]),
+				chat: null,
+			},
+		]);
+		const [stored] = await persistence.getChats("alice@example.com");
+		expect([...stored.presence.keys()]).toEqual(["phone"]);
 	});
 
 	test("getChats hydrates membersForName and mavUntil from members", async ({
-		page,
 		borogove,
 		persistence,
 		createChannel,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence, createChannel }) => {
-				const chat = createChannel(
-					persistence,
-					"room-chat-hydrate@example.com",
-				);
-				chat.displayName = "Tea Room";
-				chat.trusted = true;
-				chat.mavUntil = "2024-05-01T12:00:00Z";
-
-				await persistence.storeChats("alice@example.com", [chat]);
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: chat.chatId,
-						displayName: "Tea Room",
-						photoUri: null,
-						isSelf: false,
-						roles: [],
-						jid: borogove.JID.parse(chat.chatId),
-						presence: new Map(),
-						chat: null,
-					},
-					{
-						id: `${chat.chatId}/self`,
-						displayName: "Myself",
-						photoUri: null,
-						isSelf: true,
-						roles: [{ id: "owner", title: "Owner" }],
-						jid: borogove.JID.parse("alice@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: null,
-					},
-					{
-						id: `${chat.chatId}/zulu`,
-						displayName: "Zulu",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("zulu@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "zulu@example.com" },
-					},
-					{
-						id: `${chat.chatId}/alpha`,
-						displayName: "Alpha",
-						photoUri: null,
-						isSelf: false,
-						roles: [],
-						jid: borogove.JID.parse("alpha@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alpha@example.com" },
-					},
-					{
-						id: `${chat.chatId}/hidden`,
-						displayName: "Hidden",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Guest" }],
-						jid: borogove.JID.parse("hidden@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "hidden@example.com" },
-					},
-				]);
-				const [stored] = await persistence.getChats("alice@example.com");
-				return {
-					mavUntil: stored.mavUntil,
-					membersForName: stored.membersForName.map((m) => m.displayName),
-					presenceKeys: [...stored.presence.keys()].sort(),
-				};
-			},
-			{ borogove, persistence, createChannel },
-		);
+		const chat = createChannel(persistence, "room-chat-hydrate@example.com");
+		chat.displayName = "Tea Room";
+		chat.trusted = true;
+		chat.mavUntil = "2024-05-01T12:00:00Z";
 
-		expect(result.mavUntil).toBe("2024-05-01T12:00:00Z");
-		expect(result.membersForName).toEqual(["Alpha", "Zulu"]);
-		expect(result.presenceKeys).toEqual(["desk"]);
+		await persistence.storeChats("alice@example.com", [chat]);
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: chat.chatId,
+				displayName: "Tea Room",
+				photoUri: null,
+				isSelf: false,
+				roles: [],
+				jid: borogove.JID.parse(chat.chatId),
+				presence: new Map(),
+				chat: null,
+			},
+			{
+				id: `${chat.chatId}/self`,
+				displayName: "Myself",
+				photoUri: null,
+				isSelf: true,
+				roles: [{ id: "owner", title: "Owner" }],
+				jid: borogove.JID.parse("alice@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: null,
+			},
+			{
+				id: `${chat.chatId}/zulu`,
+				displayName: "Zulu",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("zulu@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "zulu@example.com" },
+			},
+			{
+				id: `${chat.chatId}/alpha`,
+				displayName: "Alpha",
+				photoUri: null,
+				isSelf: false,
+				roles: [],
+				jid: borogove.JID.parse("alpha@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alpha@example.com" },
+			},
+			{
+				id: `${chat.chatId}/hidden`,
+				displayName: "Hidden",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Guest" }],
+				jid: borogove.JID.parse("hidden@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "hidden@example.com" },
+			},
+		]);
+		const [stored] = await persistence.getChats("alice@example.com");
+		expect(stored.mavUntil).toBe("2024-05-01T12:00:00Z");
+		expect(stored.membersForName.map((m) => m.displayName)).toEqual([
+			"Alpha",
+			"Zulu",
+		]);
+		expect([...stored.presence.keys()]).toEqual(["desk"]);
 	});
 
 	test("hydrate replyToMessage for groupchats", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "parent",
-					serverIdBy: "hatter@example.com",
-					localId: "loc1",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				builder.type = borogove.MessageType.MessageChannel;
-				const parentStub = builder.build();
-
-				builder.setBody(borogove.Html.text("Hello"));
-				const parentMsg = builder.build();
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "child",
-					serverIdBy: "hatter@example.com",
-					localId: "loc2",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder2.sortId = "a1";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("hatter@example.com");
-				builder2.recipients = [builder2.to];
-				builder2.replyTo = [builder2.from];
-				builder2.replyToMessage = parentStub;
-				builder2.type = borogove.MessageType.MessageChannel;
-
-				await persistence.storeMessages("alice@example.com", [parentMsg]);
-				const [childStored] = await persistence.storeMessages(
-					"alice@example.com",
-					[builder2.build()],
-				);
-				return childStored.replyToMessage.body().toPlainText();
-			},
-			{ borogove, persistence },
-		);
-
-		expect(result).toBe("Hello");
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "parent",
+			serverIdBy: "hatter@example.com",
+			localId: "loc1",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		builder.type = borogove.MessageType.MessageChannel;
+		const parentStub = builder.build();
+
+		builder.setBody(borogove.Html.text("Hello"));
+		const parentMsg = builder.build();
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "child",
+			serverIdBy: "hatter@example.com",
+			localId: "loc2",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+		builder2.replyToMessage = parentStub;
+		builder2.type = borogove.MessageType.MessageChannel;
+
+		await persistence.storeMessages("alice@example.com", [parentMsg]);
+		const [childStored] = await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+		]);
+		expect(childStored.replyToMessage.body().toPlainText()).toBe("Hello");
 	});
 
 	test("getMessage by serverId and localId", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "srv1",
-					serverIdBy: "hatter@example.com",
-					localId: "loc1",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				const msg = builder.build();
-
-				await persistence.storeMessages("alice@example.com", [msg]);
-
-				const byServerId = await persistence.getMessage(
-					"alice@example.com",
-					"hatter@example.com",
-					"srv1",
-					null,
-				);
-				const byLocalId = await persistence.getMessage(
-					"alice@example.com",
-					"hatter@example.com",
-					null,
-					"loc1",
-				);
-
-				return {
-					byServerId: byServerId
-						? { serverId: byServerId.serverId, localId: byServerId.localId }
-						: null,
-					byLocalId: byLocalId
-						? { serverId: byLocalId.serverId, localId: byLocalId.localId }
-						: null,
-				};
-			},
-			{ borogove, persistence },
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			localId: "loc1",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		const msg = builder.build();
+
+		await persistence.storeMessages("alice@example.com", [msg]);
+
+		const byServerId = await persistence.getMessage(
+			"alice@example.com",
+			"hatter@example.com",
+			"srv1",
+			null,
 		);
-
-		expect(result.byServerId).not.toBeNull();
-		expect(result.byServerId.serverId).toBe("srv1");
-		expect(result.byLocalId).not.toBeNull();
-		expect(result.byLocalId.localId).toBe("loc1");
+		const byLocalId = await persistence.getMessage(
+			"alice@example.com",
+			"hatter@example.com",
+			null,
+			"loc1",
+		);
+		expect(byServerId).not.toBeNull();
+		expect(byServerId.serverId).toBe("srv1");
+		expect(byLocalId).not.toBeNull();
+		expect(byLocalId.localId).toBe("loc1");
 	});
 
 	test("persists message encryption information", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const account = "encryption-alice@example.com";
-				const chatId = "encryption-hatter@example.com";
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "encrypted-message",
-					serverIdBy: account,
-					senderId: chatId,
-					direction: 0,
-				});
-				builder.sortId = "encrypted-a0";
-				builder.syncPoint = true;
-				builder.text = "Encrypted persistence marker";
-				builder.to = borogove.JID.parse(account);
-				builder.from = borogove.JID.parse(chatId);
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				builder.encryption = {
-					status: borogove.EncryptionStatus.DecryptionFailure,
-					method: "urn:xmpp:omemo:2",
-					methodName: "OMEMO 2",
-					reason: "invalid-key",
-					reasonText: "The sender key was invalid",
-				};
-
-				const [stored] = await persistence.storeMessages(account, [
-					builder.build(),
-				]);
-				const fetched = await persistence.getMessage(
-					account,
-					chatId,
-					"encrypted-message",
-					null,
-				);
-				const [searched] = await persistence.searchMessages(
-					account,
-					chatId,
-					"persistence marker",
-				);
-				const [paged] = await persistence.getMessagesBefore(account, chatId);
-				const syncPoint = await persistence.syncPoint(account, null);
-
-				const fields = (message) => ({
-					status: message?.encryption?.status,
-					method: message?.encryption?.method,
-					methodName: message?.encryption?.methodName,
-					reason: message?.encryption?.reason,
-					reasonText: message?.encryption?.reasonText,
-				});
-				return {
-					stored: fields(stored),
-					fetched: fields(fetched),
-					searched: fields(searched),
-					paged: fields(paged),
-					syncPoint: fields(syncPoint),
-				};
-			},
-			{ borogove, persistence },
-		);
+		const account = "encryption-alice@example.com";
+		const chatId = "encryption-hatter@example.com";
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "encrypted-message",
+			serverIdBy: account,
+			senderId: chatId,
+			direction: 0,
+		});
+		builder.sortId = "encrypted-a0";
+		builder.syncPoint = true;
+		builder.text = "Encrypted persistence marker";
+		builder.to = borogove.JID.parse(account);
+		builder.from = borogove.JID.parse(chatId);
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		builder.encryption = {
+			status: borogove.EncryptionStatus.DecryptionFailure,
+			method: "urn:xmpp:omemo:2",
+			methodName: "OMEMO 2",
+			reason: "invalid-key",
+			reasonText: "The sender key was invalid",
+		};
 
+		const [stored] = await persistence.storeMessages(account, [
+			builder.build(),
+		]);
+		const fetched = await persistence.getMessage(
+			account,
+			chatId,
+			"encrypted-message",
+			null,
+		);
+		const [searched] = await persistence.searchMessages(
+			account,
+			chatId,
+			"persistence marker",
+		);
+		const [paged] = await persistence.getMessagesBefore(account, chatId);
+		const syncPoint = await persistence.syncPoint(account, null);
+
+		const fields = (message) => ({
+			status: message?.encryption?.status,
+			method: message?.encryption?.method,
+			methodName: message?.encryption?.methodName,
+			reason: message?.encryption?.reason,
+			reasonText: message?.encryption?.reasonText,
+		});
 		const expected = {
 			status: 1,
 			method: "urn:xmpp:omemo:2",
@@ -390,58 +290,50 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 			reason: "invalid-key",
 			reasonText: "The sender key was invalid",
 		};
-		expect(result.stored).toEqual(expected);
-		expect(result.fetched).toEqual(expected);
-		expect(result.searched).toEqual(expected);
-		expect(result.paged).toEqual(expected);
-		expect(result.syncPoint).toEqual(expected);
+		expect(fields(stored)).toEqual(expected);
+		expect(fields(fetched)).toEqual(expected);
+		expect(fields(searched)).toEqual(expected);
+		expect(fields(paged)).toEqual(expected);
+		expect(fields(syncPoint)).toEqual(expected);
 	});
 
 	test("preserves encryption information when updating message status", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const account = "encryption-status-alice@example.com";
-				const builder = new borogove.ChatMessageBuilder({
-					localId: "encrypted-outgoing",
-					senderId: account,
-					direction: 1,
-				});
-				builder.sortId = "encrypted-b0";
-				builder.to = borogove.JID.parse("encryption-hatter@example.com");
-				builder.from = borogove.JID.parse(account);
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				builder.encryption = {
-					status: borogove.EncryptionStatus.DecryptionSuccess,
-					method: "eu.siacs.conversations.axolotl",
-					methodName: "OMEMO",
-					reason: null,
-					reasonText: null,
-				};
-
-				await persistence.storeMessages(account, [builder.build()]);
-				const updated = await persistence.updateMessageStatus(
-					account,
-					"encrypted-outgoing",
-					1,
-					"Delivered",
-				);
-				return {
-					status: updated.encryption?.status,
-					method: updated.encryption?.method,
-					methodName: updated.encryption?.methodName,
-					reason: updated.encryption?.reason,
-					reasonText: updated.encryption?.reasonText,
-				};
-			},
-			{ borogove, persistence },
-		);
+		const account = "encryption-status-alice@example.com";
+		const builder = new borogove.ChatMessageBuilder({
+			localId: "encrypted-outgoing",
+			senderId: account,
+			direction: 1,
+		});
+		builder.sortId = "encrypted-b0";
+		builder.to = borogove.JID.parse("encryption-hatter@example.com");
+		builder.from = borogove.JID.parse(account);
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		builder.encryption = {
+			status: borogove.EncryptionStatus.DecryptionSuccess,
+			method: "eu.siacs.conversations.axolotl",
+			methodName: "OMEMO",
+			reason: null,
+			reasonText: null,
+		};
 
-		expect(result).toEqual({
+		await persistence.storeMessages(account, [builder.build()]);
+		const updated = await persistence.updateMessageStatus(
+			account,
+			"encrypted-outgoing",
+			1,
+			"Delivered",
+		);
+		expect({
+			status: updated.encryption?.status,
+			method: updated.encryption?.method,
+			methodName: updated.encryption?.methodName,
+			reason: updated.encryption?.reason,
+			reasonText: updated.encryption?.reasonText,
+		}).toEqual({
 			status: 0,
 			method: "eu.siacs.conversations.axolotl",
 			methodName: "OMEMO",
@@ -451,79 +343,71 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 	});
 
 	test("preserves encryption information for corrected message versions", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const account = "encryption-correction-alice@example.com";
-				const chatId = "encryption-correction-hatter@example.com";
-				const originalBuilder = new borogove.ChatMessageBuilder({
-					localId: "encrypted-original",
-					senderId: account,
-					direction: 1,
-					timestamp: "2026-08-26T12:00:00Z",
-				});
-				originalBuilder.sortId = "encrypted-c0";
-				originalBuilder.text = "Original encrypted text";
-				originalBuilder.to = borogove.JID.parse(chatId);
-				originalBuilder.from = borogove.JID.parse(account);
-				originalBuilder.recipients = [originalBuilder.to];
-				originalBuilder.replyTo = [originalBuilder.from];
-				originalBuilder.encryption = {
-					status: borogove.EncryptionStatus.DecryptionSuccess,
-					method: "urn:xmpp:omemo:1",
-					methodName: "OMEMO 1",
-					reason: null,
-					reasonText: null,
-				};
-				const original = originalBuilder.build();
-				await persistence.storeMessages(account, [original]);
-
-				const correctionBuilder = new borogove.ChatMessageBuilder({
-					localId: "encrypted-correction",
-					senderId: account,
-					direction: 1,
-					timestamp: "2026-08-26T12:01:00Z",
-				});
-				correctionBuilder.sortId = "encrypted-c0";
-				correctionBuilder.text = "Corrected encrypted text";
-				correctionBuilder.to = borogove.JID.parse(chatId);
-				correctionBuilder.from = borogove.JID.parse(account);
-				correctionBuilder.recipients = [correctionBuilder.to];
-				correctionBuilder.replyTo = [correctionBuilder.from];
-				correctionBuilder.encryption = {
-					status: borogove.EncryptionStatus.DecryptionFailure,
-					method: "urn:xmpp:omemo:2",
-					methodName: "OMEMO 2",
-					reason: "invalid-key",
-					reasonText: "Correction could not be decrypted",
-				};
-				const correctionVersion = correctionBuilder.build();
-				correctionBuilder.versions = [correctionVersion];
-				correctionBuilder.localId = original.localId;
-
-				const [stored] = await persistence.storeMessages(account, [
-					correctionBuilder.build(),
-				]);
-				const [fetched] = await persistence.getMessagesBefore(account, chatId);
-				const summarize = (message) => ({
-					text: message.text,
-					method: message.encryption?.method,
-					reason: message.encryption?.reason,
-					versions: message.versions.map((version) => ({
-						text: version.text,
-						method: version.encryption?.method,
-						reason: version.encryption?.reason,
-					})),
-				});
-				return { stored: summarize(stored), fetched: summarize(fetched) };
-			},
-			{ borogove, persistence },
-		);
-
-		for (const message of [result.stored, result.fetched]) {
+		const account = "encryption-correction-alice@example.com";
+		const chatId = "encryption-correction-hatter@example.com";
+		const originalBuilder = new borogove.ChatMessageBuilder({
+			localId: "encrypted-original",
+			senderId: account,
+			direction: 1,
+			timestamp: "2026-08-26T12:00:00Z",
+		});
+		originalBuilder.sortId = "encrypted-c0";
+		originalBuilder.text = "Original encrypted text";
+		originalBuilder.to = borogove.JID.parse(chatId);
+		originalBuilder.from = borogove.JID.parse(account);
+		originalBuilder.recipients = [originalBuilder.to];
+		originalBuilder.replyTo = [originalBuilder.from];
+		originalBuilder.encryption = {
+			status: borogove.EncryptionStatus.DecryptionSuccess,
+			method: "urn:xmpp:omemo:1",
+			methodName: "OMEMO 1",
+			reason: null,
+			reasonText: null,
+		};
+		const original = originalBuilder.build();
+		await persistence.storeMessages(account, [original]);
+
+		const correctionBuilder = new borogove.ChatMessageBuilder({
+			localId: "encrypted-correction",
+			senderId: account,
+			direction: 1,
+			timestamp: "2026-08-26T12:01:00Z",
+		});
+		correctionBuilder.sortId = "encrypted-c0";
+		correctionBuilder.text = "Corrected encrypted text";
+		correctionBuilder.to = borogove.JID.parse(chatId);
+		correctionBuilder.from = borogove.JID.parse(account);
+		correctionBuilder.recipients = [correctionBuilder.to];
+		correctionBuilder.replyTo = [correctionBuilder.from];
+		correctionBuilder.encryption = {
+			status: borogove.EncryptionStatus.DecryptionFailure,
+			method: "urn:xmpp:omemo:2",
+			methodName: "OMEMO 2",
+			reason: "invalid-key",
+			reasonText: "Correction could not be decrypted",
+		};
+		const correctionVersion = correctionBuilder.build();
+		correctionBuilder.versions = [correctionVersion];
+		correctionBuilder.localId = original.localId;
+
+		const [stored] = await persistence.storeMessages(account, [
+			correctionBuilder.build(),
+		]);
+		const [fetched] = await persistence.getMessagesBefore(account, chatId);
+		const summarize = (message) => ({
+			text: message.text,
+			method: message.encryption?.method,
+			reason: message.encryption?.reason,
+			versions: message.versions.map((version) => ({
+				text: version.text,
+				method: version.encryption?.method,
+				reason: version.encryption?.reason,
+			})),
+		});
+		for (const message of [summarize(stored), summarize(fetched)]) {
 			expect(message.text).toBe("Corrected encrypted text");
 			expect(message.method).toBe("urn:xmpp:omemo:2");
 			expect(message.reason).toBe("invalid-key");
@@ -542,1572 +426,1235 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 		}
 	});
 
-	test("storeReaction", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "srv1",
-					serverIdBy: "hatter@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				await persistence.storeMessages("alice@example.com", [builder.build()]);
-
-				const reaction = new borogove.Reaction(
-					"alice@example.com",
-					"2020-01-01T00:00:01Z",
-					"👍",
-				);
-				const update = new borogove.ReactionUpdate(
-					"up1",
-					"srv1",
-					"hatter@example.com",
-					null,
-					"hatter@example.com",
-					"alice@example.com",
-					"2020-01-01T00:00:01Z",
-					[reaction],
-					borogove.ReactionUpdateKind.EmojiReactions,
-				);
-
-				const msg = await persistence.storeReaction(
-					"alice@example.com",
-					update,
-				);
-				return {
-					reactions: [...msg.reactions.entries()].map(([k, v]) => ({
-						key: k,
-						count: v.length,
-					})),
-				};
-			},
-			{ borogove, persistence },
+	test("storeReaction", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		await persistence.storeMessages("alice@example.com", [builder.build()]);
+
+		const reaction = new borogove.Reaction(
+			"alice@example.com",
+			"2020-01-01T00:00:01Z",
+			"👍",
+		);
+		const update = new borogove.ReactionUpdate(
+			"up1",
+			"srv1",
+			"hatter@example.com",
+			null,
+			"hatter@example.com",
+			"alice@example.com",
+			"2020-01-01T00:00:01Z",
+			[reaction],
+			borogove.ReactionUpdateKind.EmojiReactions,
 		);
 
-		expect(result.reactions.length).toBe(1);
-		expect(result.reactions[0].key).toBe("👍");
-		expect(result.reactions[0].count).toBe(1);
+		const msg = await persistence.storeReaction("alice@example.com", update);
+		const reactions = [...msg.reactions.entries()].map(([k, v]) => ({
+			key: k,
+			count: v.length,
+		}));
+		expect(reactions.length).toBe(1);
+		expect(reactions[0].key).toBe("👍");
+		expect(reactions[0].count).toBe(1);
 	});
 
-	test("searchMessages", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "srv1",
-					serverIdBy: "hatter@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.text = "Hello world";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "srv2",
-					serverIdBy: "hatter@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder2.sortId = "a1";
-				builder2.text = "Goodbye world";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("hatter@example.com");
-				builder2.recipients = [builder2.to];
-				builder2.replyTo = [builder2.from];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder.build(),
-					builder2.build(),
-				]);
-
-				const results = await persistence.searchMessages(
-					"alice@example.com",
-					"hatter@example.com",
-					"hello",
-				);
-				return results.map((m) => m.text);
-			},
-			{ borogove, persistence },
+	test("searchMessages", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.text = "Hello world";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "srv2",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.text = "Goodbye world";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder.build(),
+			builder2.build(),
+		]);
+
+		const results = await persistence.searchMessages(
+			"alice@example.com",
+			"hatter@example.com",
+			"hello",
 		);
-
-		expect(result.length).toBe(1);
-		expect(result[0]).toBe("Hello world");
+		expect(results.length).toBe(1);
+		expect(results[0].text).toBe("Hello world");
 	});
 
-	test("1:1 come back ordered by sortId", async ({
-		page,
-		borogove,
-		persistence,
-	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "alice@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.replyTo = [builder.from];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "alice@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("hatter@example.com");
-				builder2.replyTo = [builder.from];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesBefore(
-					"alice@example.com",
-					"hatter@example.com",
-				);
-			},
-			{ borogove, persistence },
+	test("1:1 come back ordered by sortId", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "alice@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.replyTo = [builder.from];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "alice@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.replyTo = [builder.from];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesBefore(
+			"alice@example.com",
+			"hatter@example.com",
 		);
-
-		expect(result.length).toBe(2);
-		expect(result[0].serverId).toBe("1");
-		expect(result[1].serverId).toBe("2");
+		expect(messages.length).toBe(2);
+		expect(messages[0].serverId).toBe("1");
+		expect(messages[1].serverId).toBe("2");
 	});
 
 	test("getMessagesBefore the end: MUC come back ordered by sortId, PM by timestamp", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "a0";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesBefore(
-					"alice@example.com",
-					"teaparty@example.com",
-				);
-			},
-			{ borogove, persistence },
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "a0";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesBefore(
+			"alice@example.com",
+			"teaparty@example.com",
 		);
-
-		expect(result.length).toBe(3);
-		expect(result[0].serverId).toBe("1");
-		expect(result[1].serverId).toBe("2");
-		expect(result[2].serverId).toBe("3");
+		expect(messages.length).toBe(3);
+		expect(messages[0].serverId).toBe("1");
+		expect(messages[1].serverId).toBe("2");
+		expect(messages[2].serverId).toBe("3");
 	});
 
 	test("getMessagesBefore some point: MUC come back ordered by sortId, PM by timestamp", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "Z~";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				const builder4 = new borogove.ChatMessageBuilder({
-					serverId: "4",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:04Z",
-				});
-				builder4.sortId = "c0";
-				builder4.to = borogove.JID.parse("alice@example.com");
-				builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder4.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder4.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesBefore(
-					"alice@example.com",
-					"teaparty@example.com",
-					builder4.build(),
-				);
-			},
-			{ borogove, persistence },
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "Z~";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		const builder4 = new borogove.ChatMessageBuilder({
+			serverId: "4",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:04Z",
+		});
+		builder4.sortId = "c0";
+		builder4.to = borogove.JID.parse("alice@example.com");
+		builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder4.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder4.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesBefore(
+			"alice@example.com",
+			"teaparty@example.com",
+			builder4.build(),
 		);
-
-		expect(result.length).toBe(3);
-		expect(result[0].serverId).toBe("1");
-		expect(result[1].serverId).toBe("2");
-		expect(result[2].serverId).toBe("3");
+		expect(messages.length).toBe(3);
+		expect(messages[0].serverId).toBe("1");
+		expect(messages[1].serverId).toBe("2");
+		expect(messages[2].serverId).toBe("3");
 	});
 
-	test("getMessagesBefore a PM", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "Z~";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				const builder4 = new borogove.ChatMessageBuilder({
-					serverId: "4",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:04Z",
-				});
-				builder4.sortId = "c0";
-				builder4.to = borogove.JID.parse("alice@example.com");
-				builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder4.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder4.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesBefore(
-					"alice@example.com",
-					"teaparty@example.com",
-					builder3.build(),
-				);
-			},
-			{ borogove, persistence },
+	test("getMessagesBefore a PM", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "Z~";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		const builder4 = new borogove.ChatMessageBuilder({
+			serverId: "4",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:04Z",
+		});
+		builder4.sortId = "c0";
+		builder4.to = borogove.JID.parse("alice@example.com");
+		builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder4.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder4.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesBefore(
+			"alice@example.com",
+			"teaparty@example.com",
+			builder3.build(),
 		);
-
-		expect(result.length).toBe(2);
-		expect(result[0].serverId).toBe("1");
-		expect(result[1].serverId).toBe("2");
+		expect(messages.length).toBe(2);
+		expect(messages[0].serverId).toBe("1");
+		expect(messages[1].serverId).toBe("2");
 	});
 
 	test("getMessagesAfter the start: MUC come back ordered by sortId, PM by timestamp", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "a1";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesAfter(
-					"alice@example.com",
-					"teaparty@example.com",
-				);
-			},
-			{ borogove, persistence },
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "a1";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesAfter(
+			"alice@example.com",
+			"teaparty@example.com",
 		);
-
-		expect(result.length).toBe(3);
-		expect(result[0].serverId).toBe("1");
-		expect(result[1].serverId).toBe("2");
-		expect(result[2].serverId).toBe("3");
+		expect(messages.length).toBe(3);
+		expect(messages[0].serverId).toBe("1");
+		expect(messages[1].serverId).toBe("2");
+		expect(messages[2].serverId).toBe("3");
 	});
 
 	test("getMessagesAfter some point: MUC come back ordered by sortId, PM by timestamp", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "Z~";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				const builder4 = new borogove.ChatMessageBuilder({
-					serverId: "4",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:04Z",
-				});
-				builder4.sortId = "c0";
-				builder4.to = borogove.JID.parse("alice@example.com");
-				builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder4.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder4.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesAfter(
-					"alice@example.com",
-					"teaparty@example.com",
-					builder.build(),
-				);
-			},
-			{ borogove, persistence },
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "Z~";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		const builder4 = new borogove.ChatMessageBuilder({
+			serverId: "4",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:04Z",
+		});
+		builder4.sortId = "c0";
+		builder4.to = borogove.JID.parse("alice@example.com");
+		builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder4.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder4.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesAfter(
+			"alice@example.com",
+			"teaparty@example.com",
+			builder.build(),
 		);
-
-		expect(result.length).toBe(3);
-		expect(result[0].serverId).toBe("2");
-		expect(result[1].serverId).toBe("3");
-		expect(result[2].serverId).toBe("4");
+		expect(messages.length).toBe(3);
+		expect(messages[0].serverId).toBe("2");
+		expect(messages[1].serverId).toBe("3");
+		expect(messages[2].serverId).toBe("4");
 	});
 
-	test("getMessagesAfter a PM", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "1",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:00Z",
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder.replyTo = [builder.from.asBare()];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "2",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:01Z",
-				});
-				builder2.sortId = "b0";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder2.replyTo = [builder.from.asBare()];
-
-				const builder3 = new borogove.ChatMessageBuilder({
-					serverId: "3",
-					serverIdBy: "alice@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannelPrivate,
-					timestamp: "2020-01-01T00:00:03Z",
-				});
-				builder3.sortId = "Z~";
-				builder3.to = borogove.JID.parse("alice@example.com");
-				builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder3.replyTo = [builder.from.asBare()];
-
-				const builder4 = new borogove.ChatMessageBuilder({
-					serverId: "4",
-					serverIdBy: "teaparty@example.com",
-					senderId: "teaparty@example.com/hatter",
-					direction: 0,
-					type: borogove.MessageType.MessageChannel,
-					timestamp: "2020-01-01T00:00:04Z",
-				});
-				builder4.sortId = "c0";
-				builder4.to = borogove.JID.parse("alice@example.com");
-				builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
-				builder4.replyTo = [builder.from.asBare()];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder2.build(),
-					builder4.build(),
-					builder3.build(),
-					builder.build(),
-				]);
-
-				return await persistence.getMessagesAfter(
-					"alice@example.com",
-					"teaparty@example.com",
-					builder3.build(),
-				);
-			},
-			{ borogove, persistence },
+	test("getMessagesAfter a PM", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "1",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:00Z",
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder.replyTo = [builder.from.asBare()];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "2",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:01Z",
+		});
+		builder2.sortId = "b0";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder2.replyTo = [builder.from.asBare()];
+
+		const builder3 = new borogove.ChatMessageBuilder({
+			serverId: "3",
+			serverIdBy: "alice@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannelPrivate,
+			timestamp: "2020-01-01T00:00:03Z",
+		});
+		builder3.sortId = "Z~";
+		builder3.to = borogove.JID.parse("alice@example.com");
+		builder3.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder3.replyTo = [builder.from.asBare()];
+
+		const builder4 = new borogove.ChatMessageBuilder({
+			serverId: "4",
+			serverIdBy: "teaparty@example.com",
+			senderId: "teaparty@example.com/hatter",
+			direction: 0,
+			type: borogove.MessageType.MessageChannel,
+			timestamp: "2020-01-01T00:00:04Z",
+		});
+		builder4.sortId = "c0";
+		builder4.to = borogove.JID.parse("alice@example.com");
+		builder4.from = borogove.JID.parse("teaparty@example.com/hatter");
+		builder4.replyTo = [builder.from.asBare()];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder2.build(),
+			builder4.build(),
+			builder3.build(),
+			builder.build(),
+		]);
+		const messages = await persistence.getMessagesAfter(
+			"alice@example.com",
+			"teaparty@example.com",
+			builder3.build(),
 		);
-
-		expect(result.length).toBe(1);
-		expect(result[0].serverId).toBe("4");
+		expect(messages.length).toBe(1);
+		expect(messages[0].serverId).toBe("4");
 	});
 
-	test("updateMessageStatus", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					localId: "loc1",
-					senderId: "alice@example.com",
-					direction: 1, // MessageSent
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("hatter@example.com");
-				builder.from = borogove.JID.parse("alice@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				await persistence.storeMessages("alice@example.com", [builder.build()]);
-
-				const updated = await persistence.updateMessageStatus(
-					"alice@example.com",
-					"loc1",
-					1,
-					"Delivered",
-				); // MessageDelivered
-				return { status: updated.status, statusText: updated.statusText };
-			},
-			{ borogove, persistence },
-		);
-
-		expect(result.status).toBe(1);
-		expect(result.statusText).toBe("Delivered");
+	test("updateMessageStatus", async ({ borogove, persistence }) => {
+		const builder = new borogove.ChatMessageBuilder({
+			localId: "loc1",
+			senderId: "alice@example.com",
+			direction: 1, // MessageSent
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("hatter@example.com");
+		builder.from = borogove.JID.parse("alice@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		await persistence.storeMessages("alice@example.com", [builder.build()]);
+
+		const updated = await persistence.updateMessageStatus(
+			"alice@example.com",
+			"loc1",
+			1,
+			"Delivered",
+		); // MessageDelivered
+		expect(updated.status).toBe(1);
+		expect(updated.statusText).toBe("Delivered");
 	});
 
-	test("removeAccount and listAccounts", async ({
-		page,
-		borogove,
-		persistence,
-	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) => {
-				await persistence.storeLogin(
-					"alice@example.com",
-					"client1",
-					"Alice",
-					null,
-				);
-				await persistence.storeLogin("bob@example.com", "client2", "Bob", null);
-
-				const accountsBefore = await persistence.listAccounts();
-				await persistence.removeAccount("alice@example.com", true);
-				const accountsAfter = await persistence.listAccounts();
-
-				return { accountsBefore, accountsAfter };
-			},
-			{ borogove, persistence },
-		);
-
-		expect(result.accountsBefore).toContain("alice@example.com");
-		expect(result.accountsBefore).toContain("bob@example.com");
-		expect(result.accountsAfter).not.toContain("alice@example.com");
-		expect(result.accountsAfter).toContain("bob@example.com");
+	test("removeAccount and listAccounts", async ({ persistence }) => {
+		await persistence.storeLogin("alice@example.com", "client1", "Alice", null);
+		await persistence.storeLogin("bob@example.com", "client2", "Bob", null);
+
+		const accountsBefore = await persistence.listAccounts();
+		await persistence.removeAccount("alice@example.com", true);
+		const accountsAfter = await persistence.listAccounts();
+		expect(accountsBefore).toContain("alice@example.com");
+		expect(accountsBefore).toContain("bob@example.com");
+		expect(accountsAfter).not.toContain("alice@example.com");
+		expect(accountsAfter).toContain("bob@example.com");
 	});
 
-	test("getChatUnreadDetails", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.DirectChat.prototype);
-				chat.chatId = "hatter@example.com";
-				chat.readUpToId = "srv1";
-				chat.notificationsFiltered = () => false;
-
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "srv1",
-					serverIdBy: "hatter@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "srv2",
-					serverIdBy: "hatter@example.com",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder2.sortId = "a1";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("hatter@example.com");
-				builder2.recipients = [builder2.to];
-				builder2.replyTo = [builder2.from];
-
-				await persistence.storeMessages("alice@example.com", [
-					builder.build(),
-					builder2.build(),
-				]);
-
-				return await persistence.getChatUnreadDetails(
-					"alice@example.com",
-					chat,
-				);
-			},
-			{ borogove, persistence },
+	test("getChatUnreadDetails", async ({ borogove, persistence }) => {
+		const chat = Object.create(borogove.DirectChat.prototype);
+		chat.chatId = "hatter@example.com";
+		chat.readUpToId = "srv1";
+		chat.notificationsFiltered = () => false;
+
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "srv1",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "srv2",
+			serverIdBy: "hatter@example.com",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+
+		await persistence.storeMessages("alice@example.com", [
+			builder.build(),
+			builder2.build(),
+		]);
+		const unreadDetails = await persistence.getChatUnreadDetails(
+			"alice@example.com",
+			chat,
 		);
-
-		expect(result.unreadCount).toBe(1);
-		expect(result.message.serverId).toBe("srv2");
+		expect(unreadDetails.unreadCount).toBe(1);
+		expect(unreadDetails.message.serverId).toBe("srv2");
 	});
 
-	test("media storage functions", async ({ page, borogove, persistence }) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				function bufferToByteStream(buffer) {
-					let done = false;
-					return new ReadableStream({
-						type: "bytes",
-						async pull(controller) {
-							if (done) {
-								controller.close();
-							} else {
-								controller.enqueue(buffer);
-								done = true;
-							}
-						},
-					});
-				}
-
-				const buffer = new Uint8Array([1, 2, 3]);
-				const sha256 = await crypto.subtle.digest("SHA-256", buffer.buffer);
-				await persistence.storeMedia("image/png", bufferToByteStream(buffer));
-				const hash = new borogove.Hash("sha-256", sha256);
-				const hasBefore = await persistence.hasMedia(hash);
-				await persistence.removeMedia("sha-256", sha256);
-				const hasAfter = await persistence.hasMedia(hash);
-
-				return { hasBefore, hasAfter };
-			},
-			{ borogove, persistence },
-		);
+	test("media storage functions", async ({ borogove, persistence }) => {
+		function bufferToByteStream(buffer) {
+			let done = false;
+			return new ReadableStream({
+				type: "bytes",
+				async pull(controller) {
+					if (done) {
+						controller.close();
+					} else {
+						controller.enqueue(buffer);
+						done = true;
+					}
+				},
+			});
+		}
 
-		expect(result.hasBefore).toBe(
+		const buffer = new Uint8Array([1, 2, 3]);
+		const sha256 = await crypto.subtle.digest("SHA-256", buffer.buffer);
+		await persistence.storeMedia("image/png", bufferToByteStream(buffer));
+		const hash = new borogove.Hash("sha-256", sha256);
+		const hasBefore = await persistence.hasMedia(hash);
+		await persistence.removeMedia("sha-256", sha256);
+		const hasAfter = await persistence.hasMedia(hash);
+		expect(hasBefore).toBe(
 			"/.well-known/ni/sha-256/A5BYxvLAy0ksUzsKTRTvd8wPeKvMztUofYShogEc-4E",
 		);
-		expect(result.hasAfter).toBe(null);
+		expect(hasAfter).toBe(null);
 	});
 
 	test("storeStreamManamagement and getStreamManagement", async ({
-		page,
-		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) => {
-				await persistence.storeLogin("alice@example.com", "", "", null); // or updating with SM may not work
-				await persistence.storeStreamManagement(
-					"alice@example.com",
-					new Uint8Array([1, 2, 0, 4]).buffer,
-					"ZZ",
-				);
-				const result =
-					await persistence.getStreamManagement("alice@example.com");
-				return {
-					smIsArrayBuffer: result.sm instanceof ArrayBuffer,
-					smIsEq: result.sm
-						? indexedDB.cmp(result.sm, new Uint8Array([1, 2, 0, 4]).buffer)
-						: "null",
-					sortId: result.sortId,
-				};
-			},
-			{ borogove, persistence },
+		await persistence.storeLogin("alice@example.com", "", "", null); // or updating with SM may not work
+		await persistence.storeStreamManagement(
+			"alice@example.com",
+			new Uint8Array([1, 2, 0, 4]).buffer,
+			"ZZ",
 		);
-
-		expect(result.smIsEq).toBe(0);
-		expect(result.smIsArrayBuffer).toBe(true);
-		expect(result.sortId).toBe("ZZ");
+		const streamManagement =
+			await persistence.getStreamManagement("alice@example.com");
+		expect(streamManagement.sm instanceof ArrayBuffer).toBe(true);
+		expect(
+			streamManagement.sm
+				? indexedDB.cmp(
+						streamManagement.sm,
+						new Uint8Array([1, 2, 0, 4]).buffer,
+					)
+				: "null",
+		).toBe(0);
+		expect(streamManagement.sortId).toBe("ZZ");
 	});
 
 	test("getMembers hydrates persisted member data", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.Channel.prototype);
-				chat.chatId = "room-members-1@example.com";
-				chat.getDisplayName = () => "Tea Room";
-
-				const member = {
-					id: "room-members-1@example.com/occ-1",
-					displayName: "Alice",
-					photoUri: "photo:alice",
-					isSelf: false,
-					roles: [{ id: "admin", title: "Admin" }],
-					jid: borogove.JID.parse("alice@example.com"),
-					presence: new Map([
-						[
-							"laptop",
-							borogove.Stanza.parse("<presence><show>away</show></presence>"),
-						],
-					]),
-					chat: { chatId: "alice@example.com" },
-				};
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					member,
-				]);
-				const [stored] = await persistence.getMembers(
-					"alice@example.com",
-					chat,
-					false,
-				);
-
-				return {
-					id: stored.id,
-					displayName: stored.displayName,
-					chatId: stored.chat?.chatId,
-					roleIds: stored.roles.map((r) => r.id),
-					presenceKeys: [...stored.presence.keys()],
-					showPresence: stored.showPresence,
-				};
-			},
-			{ borogove, persistence },
-		);
+		const chat = Object.create(borogove.Channel.prototype);
+		chat.chatId = "room-members-1@example.com";
+		chat.getDisplayName = () => "Tea Room";
+
+		const member = {
+			id: "room-members-1@example.com/occ-1",
+			displayName: "Alice",
+			photoUri: "photo:alice",
+			isSelf: false,
+			roles: [{ id: "admin", title: "Admin" }],
+			jid: borogove.JID.parse("alice@example.com"),
+			presence: new Map([
+				[
+					"laptop",
+					borogove.Stanza.parse("<presence><show>away</show></presence>"),
+				],
+			]),
+			chat: { chatId: "alice@example.com" },
+		};
 
-		expect(result.id).toBe("room-members-1@example.com/occ-1");
-		expect(result.displayName).toBe("Alice");
-		expect(result.chatId).toBe("alice@example.com");
-		expect(result.roleIds).toEqual(["admin"]);
-		expect(result.presenceKeys).toEqual(["laptop"]);
-		expect(result.showPresence).toBe(1);
+		await persistence.storeMembers("alice@example.com", chat.chatId, [member]);
+		const [stored] = await persistence.getMembers(
+			"alice@example.com",
+			chat,
+			false,
+		);
+		expect(stored.id).toBe("room-members-1@example.com/occ-1");
+		expect(stored.displayName).toBe("Alice");
+		expect(stored.chat?.chatId).toBe("alice@example.com");
+		expect(stored.roles.map((r) => r.id)).toEqual(["admin"]);
+		expect([...stored.presence.keys()]).toEqual(["laptop"]);
+		expect(stored.showPresence).toBe(1);
 	});
 
 	test("getMemberDetails returns null for incomplete rows", async ({
-		page,
 		borogove,
 		createChannel,
 		persistence,
 		storeIncompleteMember,
 	}) => {
-		const result = await page.evaluate(
-			async ({
-				borogove,
-				createChannel,
-				persistence,
-				storeIncompleteMember,
-			}) => {
-				const chat = createChannel(persistence, "room-members-7@example.com");
-				chat.displayName = "A Chat";
-				chat.trusted = true;
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-members-7@example.com/admin",
-						displayName: "Alpha",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alpha@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alpha@example.com" },
-					},
-				]);
-
-				await storeIncompleteMember(chat.chatId);
-				const details = await persistence.getMemberDetails(
-					"alice@example.com",
-					chat,
-					[
-						"room-members-7@example.com/admin",
-						"room-members-7@example.com/incomplete",
-					],
-				);
-				return details.map((m) => (m ? m.displayName : null));
-			},
+		const chat = createChannel(persistence, "room-members-7@example.com");
+		chat.displayName = "A Chat";
+		chat.trusted = true;
+
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
 			{
-				borogove,
-				createChannel,
-				persistence,
-				storeIncompleteMember,
+				id: "room-members-7@example.com/admin",
+				displayName: "Alpha",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alpha@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alpha@example.com" },
 			},
+		]);
+
+		await storeIncompleteMember(chat.chatId);
+		const details = await persistence.getMemberDetails(
+			"alice@example.com",
+			chat,
+			[
+				"room-members-7@example.com/admin",
+				"room-members-7@example.com/incomplete",
+			],
 		);
-
-		expect(result).toEqual(["Alpha", null]);
+		expect(details.map((m) => (m ? m.displayName : null))).toEqual([
+			"Alpha",
+			null,
+		]);
 	});
 
 	test("storeMemberUpdates merges existing member data", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.Channel.prototype);
-				chat.chatId = "room-members-2@example.com";
-				chat.getDisplayName = () => "Tea Room";
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-members-2@example.com/occ-1",
-						displayName: "Alice",
-						photoUri: null,
-						isSelf: false,
-						roles: [
-							{ id: "admin", title: "Admin" },
-							{ id: "urn:xmpp:hats:test", title: "Tea Host" },
-						],
-						jid: borogove.JID.parse("alice@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alice@example.com" },
-					},
-				]);
-
-				const updates = [
-					new borogove.MemberUpdate(
-						"room-members-2@example.com/occ-1",
-						borogove.JID.parse("alice@example.com"),
-						"Alice Cooper",
-						false,
-						null,
-						new Map([["mobile", borogove.Stanza.parse("<presence />")]]),
-					),
-				];
-
-				const updated = await persistence.storeMemberUpdates(
-					"alice@example.com",
-					chat,
-					updates,
-					false,
-				);
+		const chat = Object.create(borogove.Channel.prototype);
+		chat.chatId = "room-members-2@example.com";
+		chat.getDisplayName = () => "Tea Room";
 
-				return {
-					updatedRoleIds: updated[0].roles.map((r) => r.id),
-					updatedPresenceKeys: [...updated[0].presence.keys()].sort(),
-					updatedDisplayName: updated[0].displayName,
-				};
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "room-members-2@example.com/occ-1",
+				displayName: "Alice",
+				photoUri: null,
+				isSelf: false,
+				roles: [
+					{ id: "admin", title: "Admin" },
+					{ id: "urn:xmpp:hats:test", title: "Tea Host" },
+				],
+				jid: borogove.JID.parse("alice@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alice@example.com" },
 			},
-			{ borogove, persistence },
-		);
+		]);
+
+		const updates = [
+			new borogove.MemberUpdate(
+				"room-members-2@example.com/occ-1",
+				borogove.JID.parse("alice@example.com"),
+				"Alice Cooper",
+				false,
+				null,
+				new Map([["mobile", borogove.Stanza.parse("<presence />")]]),
+			),
+		];
 
-		expect(result.updatedRoleIds).toEqual(["urn:xmpp:hats:test"]);
-		expect(result.updatedPresenceKeys).toEqual(["desk", "mobile"]);
-		expect(result.updatedDisplayName).toBe("Alice Cooper");
+		const updated = await persistence.storeMemberUpdates(
+			"alice@example.com",
+			chat,
+			updates,
+			false,
+		);
+		expect(updated[0].roles.map((r) => r.id)).toEqual(["urn:xmpp:hats:test"]);
+		expect([...updated[0].presence.keys()].sort()).toEqual(["desk", "mobile"]);
+		expect(updated[0].displayName).toBe("Alice Cooper");
 	});
 
 	test("storeMemberUpdates clears omitted full-list affiliations", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.Channel.prototype);
-				chat.chatId = "room-members-2b@example.com";
-				chat.getDisplayName = () => "Tea Room";
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-members-2b@example.com/occ-1",
-						displayName: "Alice",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alice@example.com"),
-						presence: new Map(),
-						chat: { chatId: "alice@example.com" },
-					},
-					{
-						id: "room-members-2b@example.com/occ-2",
-						displayName: "Bob",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "owner", title: "Owner" }],
-						jid: borogove.JID.parse("bob@example.com"),
-						presence: new Map(),
-						chat: { chatId: "bob@example.com" },
-					},
-				]);
-
-				await persistence.storeMemberUpdates(
-					"alice@example.com",
-					chat,
-					[
-						new borogove.MemberUpdate(
-							"room-members-2b@example.com/occ-1",
-							borogove.JID.parse("alice@example.com"),
-							"Alice",
-							false,
-							null,
-							new Map(),
-						),
-					],
-					true,
-				);
-				const members = await persistence.getMembers(
-					"alice@example.com",
-					chat,
-					true,
-				);
-
-				return members
-					.find((m) => m.id.endsWith("occ-2"))
-					.roles.map((r) => r.id);
+		const chat = Object.create(borogove.Channel.prototype);
+		chat.chatId = "room-members-2b@example.com";
+		chat.getDisplayName = () => "Tea Room";
+
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "room-members-2b@example.com/occ-1",
+				displayName: "Alice",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alice@example.com"),
+				presence: new Map(),
+				chat: { chatId: "alice@example.com" },
 			},
-			{ borogove, persistence },
+			{
+				id: "room-members-2b@example.com/occ-2",
+				displayName: "Bob",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "owner", title: "Owner" }],
+				jid: borogove.JID.parse("bob@example.com"),
+				presence: new Map(),
+				chat: { chatId: "bob@example.com" },
+			},
+		]);
+
+		await persistence.storeMemberUpdates(
+			"alice@example.com",
+			chat,
+			[
+				new borogove.MemberUpdate(
+					"room-members-2b@example.com/occ-1",
+					borogove.JID.parse("alice@example.com"),
+					"Alice",
+					false,
+					null,
+					new Map(),
+				),
+			],
+			true,
 		);
-
-		expect(result).toEqual([]);
+		const members = await persistence.getMembers(
+			"alice@example.com",
+			chat,
+			true,
+		);
+		expect(members.find((m) => m.id.endsWith("occ-2")).roles).toEqual([]);
 	});
 
 	test("storeMemberUpdates matches existing member by true JID", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat1 = Object.create(borogove.Channel.prototype);
-				chat1.chatId = "room-members-3@example.com";
-				chat1.getDisplayName = () => "Tea Room";
-				const chat2 = Object.create(borogove.Channel.prototype);
-				chat2.chatId = "room-members-4@example.com";
-				chat2.getDisplayName = () => "Other Room";
-
-				await persistence.storeMembers("alice@example.com", chat1.chatId, [
-					{
-						id: "room-members-3@example.com/occ-1",
-						displayName: "Alice",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alice@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alice@example.com" },
-					},
-					{
-						id: "room-members-4@example.com/occ-1",
-						displayName: "Bob",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("bob@example.com"),
-						presence: new Map([
-							["phone", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "bob@example.com" },
-					},
-				]);
-
-				await persistence.storeMemberUpdates(
-					"alice@example.com",
-					chat1,
-					[
-						new borogove.MemberUpdate(
-							null,
-							borogove.JID.parse("alice@example.com"),
-							"Alice Renamed",
-							false,
-							null,
-							new Map(),
-						),
-					],
-					false,
-				);
-				const [chat1Member] = await persistence.getMemberDetails(
-					"alice@example.com",
-					chat1,
-					["room-members-3@example.com/occ-1"],
-				);
-
-				return chat1Member.displayName;
+		const chat1 = Object.create(borogove.Channel.prototype);
+		chat1.chatId = "room-members-3@example.com";
+		chat1.getDisplayName = () => "Tea Room";
+		const chat2 = Object.create(borogove.Channel.prototype);
+		chat2.chatId = "room-members-4@example.com";
+		chat2.getDisplayName = () => "Other Room";
+
+		await persistence.storeMembers("alice@example.com", chat1.chatId, [
+			{
+				id: "room-members-3@example.com/occ-1",
+				displayName: "Alice",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alice@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alice@example.com" },
 			},
-			{ borogove, persistence },
-		);
+			{
+				id: "room-members-4@example.com/occ-1",
+				displayName: "Bob",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("bob@example.com"),
+				presence: new Map([["phone", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "bob@example.com" },
+			},
+		]);
 
-		expect(result).toBe("Alice Renamed");
+		await persistence.storeMemberUpdates(
+			"alice@example.com",
+			chat1,
+			[
+				new borogove.MemberUpdate(
+					null,
+					borogove.JID.parse("alice@example.com"),
+					"Alice Renamed",
+					false,
+					null,
+					new Map(),
+				),
+			],
+			false,
+		);
+		const [chat1Member] = await persistence.getMemberDetails(
+			"alice@example.com",
+			chat1,
+			["room-members-3@example.com/occ-1"],
+		);
+		expect(chat1Member.displayName).toBe("Alice Renamed");
 	});
 
 	test("clearMemberPresence only clears the targeted chat", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat1 = Object.create(borogove.Channel.prototype);
-				chat1.chatId = "room-members-4a@example.com";
-				chat1.getDisplayName = () => "Tea Room";
-				const chat2 = Object.create(borogove.Channel.prototype);
-				chat2.chatId = "room-members-4b@example.com";
-				chat2.getDisplayName = () => "Other Room";
-
-				await persistence.storeMembers("alice@example.com", chat1.chatId, [
-					{
-						id: "room-members-4a@example.com/occ-1",
-						displayName: "Alice",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alice@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alice@example.com" },
-					},
-				]);
-				await persistence.storeMembers("alice@example.com", chat2.chatId, [
-					{
-						id: "room-members-4b@example.com/occ-1",
-						displayName: "Bob",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("bob@example.com"),
-						presence: new Map([
-							["phone", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "bob@example.com" },
-					},
-				]);
-
-				await persistence.clearMemberPresence(
-					"alice@example.com",
-					chat1.chatId,
-				);
-				const [chat1Member] = await persistence.getMemberDetails(
-					"alice@example.com",
-					chat1,
-					["room-members-4a@example.com/occ-1"],
-				);
-				const [chat2Member] = await persistence.getMemberDetails(
-					"alice@example.com",
-					chat2,
-					["room-members-4b@example.com/occ-1"],
-				);
-
-				return {
-					chat1PresenceKeys: [...chat1Member.presence.keys()],
-					chat2PresenceKeys: [...chat2Member.presence.keys()],
-				};
+		const chat1 = Object.create(borogove.Channel.prototype);
+		chat1.chatId = "room-members-4a@example.com";
+		chat1.getDisplayName = () => "Tea Room";
+		const chat2 = Object.create(borogove.Channel.prototype);
+		chat2.chatId = "room-members-4b@example.com";
+		chat2.getDisplayName = () => "Other Room";
+
+		await persistence.storeMembers("alice@example.com", chat1.chatId, [
+			{
+				id: "room-members-4a@example.com/occ-1",
+				displayName: "Alice",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alice@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alice@example.com" },
 			},
-			{ borogove, persistence },
-		);
+		]);
+		await persistence.storeMembers("alice@example.com", chat2.chatId, [
+			{
+				id: "room-members-4b@example.com/occ-1",
+				displayName: "Bob",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("bob@example.com"),
+				presence: new Map([["phone", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "bob@example.com" },
+			},
+		]);
 
-		expect(result.chat1PresenceKeys).toEqual([]);
-		expect(result.chat2PresenceKeys).toEqual(["phone"]);
+		await persistence.clearMemberPresence("alice@example.com", chat1.chatId);
+		const [chat1Member] = await persistence.getMemberDetails(
+			"alice@example.com",
+			chat1,
+			["room-members-4a@example.com/occ-1"],
+		);
+		const [chat2Member] = await persistence.getMemberDetails(
+			"alice@example.com",
+			chat2,
+			["room-members-4b@example.com/occ-1"],
+		);
+		expect([...chat1Member.presence.keys()]).toEqual([]);
+		expect([...chat2Member.presence.keys()]).toEqual(["phone"]);
 	});
 
 	test("getMembers filters hidden rows for non-moderators", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = new borogove.Channel(
-					null,
-					null,
-					persistence,
-					"room-members-5@example.com",
-				);
-				chat.displayName = "Tea Room";
-				chat.trusted = true;
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-members-5@example.com/owner",
-						displayName: "Zulu",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "owner", title: "Owner" }],
-						jid: borogove.JID.parse("zulu@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "zulu@example.com" },
-					},
-					{
-						id: "room-members-5@example.com/outcast",
-						displayName: "Banned",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "outcast", title: "Banned" }],
-						jid: borogove.JID.parse("banned@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "banned@example.com" },
-					},
-					{
-						id: "room-members-5@example.com/guest-offline",
-						displayName: "Guest",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Guest" }],
-						jid: borogove.JID.parse("guest@example.com"),
-						presence: new Map([
-							[
-								"desk",
-								borogove.Stanza.parse('<presence type="unavailable" />'),
-							],
-						]),
-						chat: { chatId: "guest@example.com" },
-					},
-					{
-						id: "room-members-5@example.com/guest-offline2",
-						displayName: "Guest2",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Guest" }],
-						jid: borogove.JID.parse("guest2@example.com"),
-						presence: new Map(),
-						chat: { chatId: "guest2@example.com" },
-					},
-					{
-						id: "room-members-5@example.com/admin",
-						displayName: "Alpha",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alpha@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alpha@example.com" },
-					},
-				]);
-
-				const normal = await persistence.getMembers(
-					"alice@example.com",
-					chat,
-					false,
-				);
+		const chat = new borogove.Channel(
+			null,
+			null,
+			persistence,
+			"room-members-5@example.com",
+		);
+		chat.displayName = "Tea Room";
+		chat.trusted = true;
 
-				return normal.map((m) => m.displayName);
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "room-members-5@example.com/owner",
+				displayName: "Zulu",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "owner", title: "Owner" }],
+				jid: borogove.JID.parse("zulu@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "zulu@example.com" },
 			},
-			{ borogove, persistence },
-		);
+			{
+				id: "room-members-5@example.com/outcast",
+				displayName: "Banned",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "outcast", title: "Banned" }],
+				jid: borogove.JID.parse("banned@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "banned@example.com" },
+			},
+			{
+				id: "room-members-5@example.com/guest-offline",
+				displayName: "Guest",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Guest" }],
+				jid: borogove.JID.parse("guest@example.com"),
+				presence: new Map([
+					["desk", borogove.Stanza.parse('<presence type="unavailable" />')],
+				]),
+				chat: { chatId: "guest@example.com" },
+			},
+			{
+				id: "room-members-5@example.com/guest-offline2",
+				displayName: "Guest2",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Guest" }],
+				jid: borogove.JID.parse("guest2@example.com"),
+				presence: new Map(),
+				chat: { chatId: "guest2@example.com" },
+			},
+			{
+				id: "room-members-5@example.com/admin",
+				displayName: "Alpha",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alpha@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alpha@example.com" },
+			},
+		]);
 
-		expect(result).toEqual(["Zulu", "Alpha"]);
+		const normal = await persistence.getMembers(
+			"alice@example.com",
+			chat,
+			false,
+		);
+		expect(normal.map((m) => m.displayName)).toEqual(["Zulu", "Alpha"]);
 	});
 
 	test("getMembers includes moderator-visible rows", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.Channel.prototype);
-				chat.chatId = "room-members-6@example.com";
-				chat.getDisplayName = () => "Tea Room";
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-members-6@example.com/owner",
-						displayName: "Zulu",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "owner", title: "Owner" }],
-						jid: borogove.JID.parse("zulu@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "zulu@example.com" },
-					},
-					{
-						id: "room-members-6@example.com/outcast",
-						displayName: "Banned",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "outcast", title: "Banned" }],
-						jid: borogove.JID.parse("banned@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "banned@example.com" },
-					},
-					{
-						id: "room-members-6@example.com/guest-offline",
-						displayName: "Guest",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Guest" }],
-						jid: borogove.JID.parse("guest@example.com"),
-						presence: new Map([
-							[
-								"desk",
-								borogove.Stanza.parse('<presence type="unavailable" />'),
-							],
-						]),
-						chat: { chatId: "guest@example.com" },
-					},
-					{
-						id: "room-members-6@example.com/admin",
-						displayName: "Alpha",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "admin", title: "Admin" }],
-						jid: borogove.JID.parse("alpha@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "alpha@example.com" },
-					},
-				]);
-
-				const moderator = await persistence.getMembers(
-					"alice@example.com",
-					chat,
-					true,
-				);
-				return moderator.map((m) => m.displayName);
+		const chat = Object.create(borogove.Channel.prototype);
+		chat.chatId = "room-members-6@example.com";
+		chat.getDisplayName = () => "Tea Room";
+
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "room-members-6@example.com/owner",
+				displayName: "Zulu",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "owner", title: "Owner" }],
+				jid: borogove.JID.parse("zulu@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "zulu@example.com" },
 			},
-			{ borogove, persistence },
-		);
+			{
+				id: "room-members-6@example.com/outcast",
+				displayName: "Banned",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "outcast", title: "Banned" }],
+				jid: borogove.JID.parse("banned@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "banned@example.com" },
+			},
+			{
+				id: "room-members-6@example.com/guest-offline",
+				displayName: "Guest",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Guest" }],
+				jid: borogove.JID.parse("guest@example.com"),
+				presence: new Map([
+					["desk", borogove.Stanza.parse('<presence type="unavailable" />')],
+				]),
+				chat: { chatId: "guest@example.com" },
+			},
+			{
+				id: "room-members-6@example.com/admin",
+				displayName: "Alpha",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "admin", title: "Admin" }],
+				jid: borogove.JID.parse("alpha@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "alpha@example.com" },
+			},
+		]);
 
-		expect(result).toEqual(["Zulu", "Alpha", "Banned"]);
+		const moderator = await persistence.getMembers(
+			"alice@example.com",
+			chat,
+			true,
+		);
+		expect(moderator.map((m) => m.displayName)).toEqual([
+			"Zulu",
+			"Alpha",
+			"Banned",
+		]);
 	});
 
 	test("storeVoiceRequest and listVoiceRequests", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const chat = Object.create(borogove.Channel.prototype);
-				chat.chatId = "room-voice-requests@example.com";
-				chat.getDisplayName = () => "Tea Room";
-
-				await persistence.storeMembers("alice@example.com", chat.chatId, [
-					{
-						id: "room-voice-requests@example.com/occ-1",
-						displayName: "Bob",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Participant" }],
-						jid: borogove.JID.parse("bob@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "bob@example.com" },
-					},
-					{
-						id: "room-voice-requests@example.com/occ-2",
-						displayName: "Charlie",
-						photoUri: null,
-						isSelf: false,
-						roles: [{ id: "none", title: "Participant" }],
-						jid: borogove.JID.parse("charlie@example.com"),
-						presence: new Map([
-							["desk", borogove.Stanza.parse("<presence />")],
-						]),
-						chat: { chatId: "charlie@example.com" },
-					},
-				]);
-
-				await persistence.storeVoiceRequest(
-					"alice@example.com",
-					chat,
-					"bob@example.com",
-					true,
-				);
-				await persistence.storeVoiceRequest(
-					"alice@example.com",
-					chat,
-					"charlie@example.com",
-					true,
-				);
-
-				const requests1 = await persistence.listVoiceRequests(
-					"alice@example.com",
-					chat,
-				);
-
-				await persistence.storeVoiceRequest(
-					"alice@example.com",
-					chat,
-					"bob@example.com",
-					false,
-				);
-
-				const requests2 = await persistence.listVoiceRequests(
-					"alice@example.com",
-					chat,
-				);
+		const chat = Object.create(borogove.Channel.prototype);
+		chat.chatId = "room-voice-requests@example.com";
+		chat.getDisplayName = () => "Tea Room";
 
-				return {
-					requests1: requests1.map((m) => m.displayName).sort(),
-					requests2: requests2.map((m) => m.displayName).sort(),
-				};
+		await persistence.storeMembers("alice@example.com", chat.chatId, [
+			{
+				id: "room-voice-requests@example.com/occ-1",
+				displayName: "Bob",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Participant" }],
+				jid: borogove.JID.parse("bob@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "bob@example.com" },
+			},
+			{
+				id: "room-voice-requests@example.com/occ-2",
+				displayName: "Charlie",
+				photoUri: null,
+				isSelf: false,
+				roles: [{ id: "none", title: "Participant" }],
+				jid: borogove.JID.parse("charlie@example.com"),
+				presence: new Map([["desk", borogove.Stanza.parse("<presence />")]]),
+				chat: { chatId: "charlie@example.com" },
 			},
-			{ borogove, persistence },
+		]);
+
+		await persistence.storeVoiceRequest(
+			"alice@example.com",
+			chat,
+			"bob@example.com",
+			true,
+		);
+		await persistence.storeVoiceRequest(
+			"alice@example.com",
+			chat,
+			"charlie@example.com",
+			true,
+		);
+
+		const requests1 = await persistence.listVoiceRequests(
+			"alice@example.com",
+			chat,
 		);
 
-		expect(result.requests1).toEqual(["Bob", "Charlie"]);
-		expect(result.requests2).toEqual(["Charlie"]);
+		await persistence.storeVoiceRequest(
+			"alice@example.com",
+			chat,
+			"bob@example.com",
+			false,
+		);
+
+		const requests2 = await persistence.listVoiceRequests(
+			"alice@example.com",
+			chat,
+		);
+		expect(requests1.map((m) => m.displayName).sort()).toEqual([
+			"Bob",
+			"Charlie",
+		]);
+		expect(requests2.map((m) => m.displayName).sort()).toEqual(["Charlie"]);
 	});
 
 	test("getOmemoId returns no ID when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async (persistence) =>
-				persistence.getOmemoId("omemo-not-found@example.com"),
-			persistence,
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoId("omemo-not-found@example.com"),
+		).toBeNull();
 	});
 
-	test("storeOmemoId stores the ID", async ({ page, persistence }) => {
+	test("storeOmemoId stores the ID", async ({ persistence }) => {
 		const account = "omemo-existing@example.com";
 		const omemoId = 12345;
-		const result = await page.evaluate(
-			async ({ persistence, account, omemoId }) => {
-				await persistence.storeOmemoId(account, omemoId);
-				return persistence.getOmemoId(account);
-			},
-			{ persistence, account, omemoId },
-		);
 
-		expect(result).toBe(omemoId);
+		await persistence.storeOmemoId(account, omemoId);
+		expect(await persistence.getOmemoId(account)).toBe(omemoId);
 	});
 
 	test("getOmemoIdentityKey returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async (persistence) =>
-				persistence.getOmemoIdentityKey("omemo-identity-not-found@example.com"),
-			persistence,
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoIdentityKey(
+				"omemo-identity-not-found@example.com",
+			),
+		).toBeNull();
 	});
 
-	test("storeOmemoIdentityKey stores the key pair", async ({
-		page,
-		pageHelpers,
-		persistence,
-	}) => {
+	test("storeOmemoIdentityKey stores the key pair", async ({ persistence }) => {
 		const account = "omemo-identity-existing@example.com";
 		const keyPair = makeKeyPair();
 
-		const result = await page.evaluate(
-			async ({ persistence, account, keyPair, pageHelpers }) => {
-				await persistence.storeOmemoIdentityKey(
-					account,
-					pageHelpers.keyPairToBuffers(keyPair),
-				);
-				const loadedKeyPair = await persistence.getOmemoIdentityKey(account);
+		await persistence.storeOmemoIdentityKey(account, keyPair);
+		const loadedKeyPair = await persistence.getOmemoIdentityKey(account);
 
-				return pageHelpers.buffersToKeyPair(loadedKeyPair);
-			},
-			{ persistence, account, keyPair, pageHelpers },
-		);
-
-		expectKeyPair(result, keyPair);
+		expectKeyPair(loadedKeyPair, keyPair);
 	});
 
 	test("getOmemoDeviceList returns an empty list when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async (persistence) =>
-				persistence.getOmemoDeviceList("omemo-devices-not-found@example.com"),
-			persistence,
-		);
-
-		expect(result).toEqual([]);
+		expect(
+			await persistence.getOmemoDeviceList(
+				"omemo-devices-not-found@example.com",
+			),
+		).toEqual([]);
 	});
 
 	test("storeOmemoDeviceList replaces and clears the device list", async ({
-		page,
 		persistence,
 	}) => {
 		const identifier = "omemo-devices-existing@example.com";
 		const initialDeviceIds = [12345, 67890];
 		const replacementDeviceIds = [24680];
-		const result = await page.evaluate(
-			async ({
-				persistence,
-				identifier,
-				initialDeviceIds,
-				replacementDeviceIds,
-			}) => {
-				await persistence.storeOmemoDeviceList(identifier, initialDeviceIds);
-				const initial = await persistence.getOmemoDeviceList(identifier);
-				await persistence.storeOmemoDeviceList(
-					identifier,
-					replacementDeviceIds,
-				);
-				const afterReplace = await persistence.getOmemoDeviceList(identifier);
-				await persistence.storeOmemoDeviceList(identifier, []);
-				const afterClear = await persistence.getOmemoDeviceList(identifier);
-
-				return { initial, afterReplace, afterClear };
-			},
-			{
-				persistence,
-				identifier,
-				initialDeviceIds,
-				replacementDeviceIds,
-			},
-		);
 
-		expect(result.initial).toEqual(initialDeviceIds);
-		expect(result.afterReplace).toEqual(replacementDeviceIds);
-		expect(result.afterClear).toEqual([]);
+		await persistence.storeOmemoDeviceList(identifier, initialDeviceIds);
+		const initial = await persistence.getOmemoDeviceList(identifier);
+		await persistence.storeOmemoDeviceList(identifier, replacementDeviceIds);
+		const afterReplace = await persistence.getOmemoDeviceList(identifier);
+		await persistence.storeOmemoDeviceList(identifier, []);
+		const afterClear = await persistence.getOmemoDeviceList(identifier);
+		expect(initial).toEqual(initialDeviceIds);
+		expect(afterReplace).toEqual(replacementDeviceIds);
+		expect(afterClear).toEqual([]);
 	});
 
 	test("getOmemoPreKey returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
 		const identifier = "omemo-prekey-not-found@example.com";
 		const keyId = 1;
-		const result = await page.evaluate(
-			async ({ persistence, identifier, keyId }) =>
-				persistence.getOmemoPreKey(identifier, keyId),
-			{ persistence, identifier, keyId },
-		);
-
-		expect(result).toBeNull();
+		expect(await persistence.getOmemoPreKey(identifier, keyId)).toBeNull();
 	});
 
 	test("storeOmemoPreKey stores a removable pre-key", async ({
-		page,
-		pageHelpers,
 		persistence,
 	}) => {
 		const identifier = "omemo-prekey-existing@example.com";
 		const keyId = 42;
 		const keyPair = makeKeyPair();
-		const result = await page.evaluate(
-			async ({ persistence, identifier, keyId, keyPair, pageHelpers }) => {
-				await persistence.storeOmemoPreKey(identifier, keyId, {
-					...pageHelpers.keyPairToBuffers(keyPair),
-				});
-				const loadedKeyPair = await persistence.getOmemoPreKey(
-					identifier,
-					keyId,
-				);
-				await persistence.removeOmemoPreKey(identifier, keyId);
-				const afterRemove = await persistence.getOmemoPreKey(identifier, keyId);
-
-				return {
-					loadedKeyPair: pageHelpers.buffersToKeyPair(loadedKeyPair),
-					afterRemove,
-				};
-			},
-			{ persistence, identifier, keyId, keyPair, pageHelpers },
-		);
 
-		expectKeyPair(result.loadedKeyPair, keyPair);
-		expect(result.afterRemove).toBeNull();
+		await persistence.storeOmemoPreKey(identifier, keyId, {
+			...keyPair,
+		});
+		const loadedKeyPair = await persistence.getOmemoPreKey(identifier, keyId);
+		await persistence.removeOmemoPreKey(identifier, keyId);
+		const afterRemove = await persistence.getOmemoPreKey(identifier, keyId);
+		expectKeyPair(loadedKeyPair, keyPair);
+		expect(afterRemove).toBeNull();
 	});
 
-	test("getOmemoPreKeys lists stored pre-keys", async ({
-		page,
-		pageHelpers,
-		persistence,
-	}) => {
+	test("getOmemoPreKeys lists stored pre-keys", async ({ persistence }) => {
 		const identifier = "omemo-prekeys-existing@example.com";
 		const preKeys = [
 			{
@@ -2119,30 +1666,19 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 				keyPair: makeKeyPair(),
 			},
 		];
-		const result = await page.evaluate(
-			async ({ persistence, identifier, preKeys, pageHelpers }) => {
-				for (const preKey of preKeys) {
-					await persistence.storeOmemoPreKey(
-						identifier,
-						preKey.keyId,
-						pageHelpers.keyPairToBuffers(preKey.keyPair),
-					);
-				}
-				const loaded = await persistence.getOmemoPreKeys(identifier);
-				return loaded.map((preKey) => ({
-					keyId: preKey.keyId,
-					keyPair: pageHelpers.buffersToKeyPair(preKey.keyPair),
-				}));
-			},
-			{ persistence, identifier, preKeys, pageHelpers },
-		);
 
-		expect(result).toEqual(preKeys);
+		for (const preKey of preKeys) {
+			await persistence.storeOmemoPreKey(
+				identifier,
+				preKey.keyId,
+				preKey.keyPair,
+			);
+		}
+		const loaded = await persistence.getOmemoPreKeys(identifier);
+		expect(loaded).toEqual(preKeys);
 	});
 
 	test("storeOmemoSignedPreKey and getOmemoSignedPreKey", async ({
-		page,
-		pageHelpers,
 		persistence,
 	}) => {
 		const identifier = "omemo-signed-prekey-existing@example.com";
@@ -2152,127 +1688,85 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 			keyPair: makeKeyPair(),
 			signature: makeKey(),
 		};
-		const result = await page.evaluate(
-			async ({ persistence, identifier, signedPreKey, pageHelpers }) => {
-				await persistence.storeOmemoSignedPreKey(identifier, {
-					keyId: signedPreKey.keyId,
-					keyPair: pageHelpers.keyPairToBuffers(signedPreKey.keyPair),
-					signature: pageHelpers.keyToBuffer(signedPreKey.signature),
-				});
-				const loaded = await persistence.getOmemoSignedPreKey(
-					identifier,
-					signedPreKey.keyId,
-				);
-
-				return {
-					keyId: loaded.keyId,
-					keyPair: pageHelpers.buffersToKeyPair(loaded.keyPair),
-					signature: pageHelpers.bufferToKey(loaded.signature),
-				};
-			},
-			{ persistence, identifier, signedPreKey, pageHelpers },
-		);
 
-		expect(result.keyId).toBe(keyId);
-		expectKeyPair(result.keyPair, signedPreKey.keyPair);
-		expectKey(result.signature, signedPreKey.signature);
+		await persistence.storeOmemoSignedPreKey(identifier, {
+			keyId: signedPreKey.keyId,
+			keyPair: signedPreKey.keyPair,
+			signature: signedPreKey.signature,
+		});
+		const loaded = await persistence.getOmemoSignedPreKey(
+			identifier,
+			signedPreKey.keyId,
+		);
+		expect(loaded.keyId).toBe(keyId);
+		expectKeyPair(loaded.keyPair, signedPreKey.keyPair);
+		expectKey(loaded.signature, signedPreKey.signature);
 	});
 
 	test("getOmemoSignedPreKey returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) =>
-				persistence.getOmemoSignedPreKey(
-					"omemo-signed-prekey-not-found@example.com",
-					1,
-				),
-			{ persistence },
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoSignedPreKey(
+				"omemo-signed-prekey-not-found@example.com",
+				1,
+			),
+		).toBeNull();
 	});
 
 	test("getOmemoContactIdentityKey returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) =>
-				persistence.getOmemoContactIdentityKey(
-					"omemo-contact-not-found@example.com",
-					"contact@example.com/1",
-				),
-			{ persistence },
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoContactIdentityKey(
+				"omemo-contact-not-found@example.com",
+				"contact@example.com/1",
+			),
+		).toBeNull();
 	});
 
 	test("storeOmemoContactIdentityKey and getOmemoContactIdentityKey", async ({
-		page,
-		pageHelpers,
 		persistence,
 	}) => {
 		const account = "omemo-contact-existing@example.com";
 		const address = "contact@example.com/1";
 		const identityKey = makeKey();
-		const result = await page.evaluate(
-			async ({ persistence, account, address, identityKey, pageHelpers }) => {
-				await persistence.storeOmemoContactIdentityKey(
-					account,
-					address,
-					pageHelpers.keyToBuffer(identityKey),
-				);
-				const loaded = await persistence.getOmemoContactIdentityKey(
-					account,
-					address,
-				);
-				return pageHelpers.bufferToKey(loaded);
-			},
-			{ persistence, account, address, identityKey, pageHelpers },
-		);
 
-		expect(result).toEqual(identityKey);
+		await persistence.storeOmemoContactIdentityKey(
+			account,
+			address,
+			identityKey,
+		);
+		const loaded = await persistence.getOmemoContactIdentityKey(
+			account,
+			address,
+		);
+		expect(loaded).toEqual(identityKey);
 	});
 
 	test("getOmemoSession returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) =>
-				persistence.getOmemoSession(
-					"omemo-session-not-found@example.com",
-					"contact@example.com/1",
-				),
-			{ persistence },
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoSession(
+				"omemo-session-not-found@example.com",
+				"contact@example.com/1",
+			),
+		).toBeNull();
 	});
 
 	test("getOmemoMetadata returns null when none is stored", async ({
-		page,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ persistence }) =>
-				persistence.getOmemoMetadata(
-					"omemo-metadata-not-found@example.com",
-					"contact@example.com/1",
-				),
-			{ persistence },
-		);
-
-		expect(result).toBeNull();
+		expect(
+			await persistence.getOmemoMetadata(
+				"omemo-metadata-not-found@example.com",
+				"contact@example.com/1",
+			),
+		).toBeNull();
 	});
 
-	test("storeOmemoMetadata and getOmemoMetadata", async ({
-		page,
-		persistence,
-	}) => {
+	test("storeOmemoMetadata and getOmemoMetadata", async ({ persistence }) => {
 		const account = "omemo-metadata-existing@example.com";
 		const address = "contact@example.com/1";
 		const metadata = {
@@ -2280,181 +1774,151 @@ export function sharedPersistenceTests(test: PersistenceTest) {
 			lastMessageDecryptedOk: false,
 			sentKeyExchange: true,
 		};
-		const result = await page.evaluate(
-			async ({ persistence, account, address, metadata }) => {
-				await persistence.storeOmemoMetadata(account, address, metadata);
-				return persistence.getOmemoMetadata(account, address);
-			},
-			{ persistence, account, address, metadata },
-		);
 
-		expect(result).toEqual(metadata);
+		await persistence.storeOmemoMetadata(account, address, metadata);
+		expect(await persistence.getOmemoMetadata(account, address)).toEqual(
+			metadata,
+		);
 	});
 
 	test("storeOmemoSession, getOmemoSession, and removeOmemoSession", async ({
-		page,
 		persistence,
 	}) => {
 		const account = "omemo-session-existing@example.com";
 		const address = "contact@example.com/1";
 		const session = '{"sessions":{},"version":"v1"}';
-		const result = await page.evaluate(
-			async ({ persistence, account, address, session }) => {
-				await persistence.storeOmemoSession(account, address, session);
-				const loaded = await persistence.getOmemoSession(account, address);
-				await persistence.removeOmemoSession(account, address);
-				const afterRemove = await persistence.getOmemoSession(account, address);
-
-				return {
-					loaded,
-					afterRemove,
-				};
-			},
-			{ persistence, account, address, session },
-		);
 
-		expect(result.loaded).toEqual(session);
-		expect(result.afterRemove).toBeNull();
+		await persistence.storeOmemoSession(account, address, session);
+		const loaded = await persistence.getOmemoSession(account, address);
+		await persistence.removeOmemoSession(account, address);
+		const afterRemove = await persistence.getOmemoSession(account, address);
+		expect(loaded).toEqual(session);
+		expect(afterRemove).toBeNull();
 	});
 
 	test("hydrate message with incomplete replyToMessage", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const builder = new borogove.ChatMessageBuilder({
-					serverId: "parent",
-					serverIdBy: "alice@example.com",
-					localId: "loc1",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder.sortId = "a0";
-				builder.to = borogove.JID.parse("alice@example.com");
-				builder.from = borogove.JID.parse("hatter@example.com");
-				builder.recipients = [builder.to];
-				builder.replyTo = [builder.from];
-				const parentStub = builder.build();
-
-				builder.setBody(borogove.Html.text("Hello"));
-				const parentMsg = builder.build();
-
-				const builder2 = new borogove.ChatMessageBuilder({
-					serverId: "child",
-					serverIdBy: "alice@example.com",
-					localId: "loc2",
-					senderId: "hatter@example.com",
-					direction: 0,
-				});
-				builder2.sortId = "a1";
-				builder2.to = borogove.JID.parse("alice@example.com");
-				builder2.from = borogove.JID.parse("hatter@example.com");
-				builder2.recipients = [builder2.to];
-				builder2.replyTo = [builder2.from];
-				builder2.replyToMessage = parentStub;
-				const childMsg = builder2.build();
-
-				await persistence.storeMessages("alice@example.com", [parentMsg]);
-				const [childStored] = await persistence.storeMessages(
-					"alice@example.com",
-					[childMsg],
-				);
-
-				return childStored.replyToMessage.body().toPlainText();
-			},
-			{ borogove, persistence },
-		);
-
-		expect(result).toBe("Hello");
+		const builder = new borogove.ChatMessageBuilder({
+			serverId: "parent",
+			serverIdBy: "alice@example.com",
+			localId: "loc1",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder.sortId = "a0";
+		builder.to = borogove.JID.parse("alice@example.com");
+		builder.from = borogove.JID.parse("hatter@example.com");
+		builder.recipients = [builder.to];
+		builder.replyTo = [builder.from];
+		const parentStub = builder.build();
+
+		builder.setBody(borogove.Html.text("Hello"));
+		const parentMsg = builder.build();
+
+		const builder2 = new borogove.ChatMessageBuilder({
+			serverId: "child",
+			serverIdBy: "alice@example.com",
+			localId: "loc2",
+			senderId: "hatter@example.com",
+			direction: 0,
+		});
+		builder2.sortId = "a1";
+		builder2.to = borogove.JID.parse("alice@example.com");
+		builder2.from = borogove.JID.parse("hatter@example.com");
+		builder2.recipients = [builder2.to];
+		builder2.replyTo = [builder2.from];
+		builder2.replyToMessage = parentStub;
+		const childMsg = builder2.build();
+
+		await persistence.storeMessages("alice@example.com", [parentMsg]);
+		const [childStored] = await persistence.storeMessages("alice@example.com", [
+			childMsg,
+		]);
+		expect(childStored.replyToMessage.body().toPlainText()).toBe("Hello");
 	});
 
 	test("allows rescinding a custom reaction", async ({
-		page,
 		borogove,
 		persistence,
 	}) => {
-		const result = await page.evaluate(
-			async ({ borogove, persistence }) => {
-				const account = "alice@example.com";
-				const chatId = "hatter@example.com";
-				const targetLocalId = "target-message";
-				const reactionMessageId = "reaction-message";
-				const timestamp = "2026-09-09T12:00:00Z";
-
-				const targetBuilder = new borogove.ChatMessageBuilder({
-					localId: targetLocalId,
-					senderId: account,
-					direction: 1,
-				});
-				targetBuilder.sortId = "a0";
-				targetBuilder.to = borogove.JID.parse(chatId);
-				targetBuilder.from = borogove.JID.parse(account);
-				targetBuilder.recipients = [targetBuilder.to];
-				targetBuilder.replyTo = [targetBuilder.from];
-
-				await persistence.storeMessages(account, [targetBuilder.build()]);
-
-				const reaction = new borogove.CustomEmojiReaction(
-					account,
-					timestamp,
-					"tada",
-					"https://example.com/tada.png",
-					reactionMessageId,
-				);
-				const update = new borogove.ReactionUpdate(
-					reactionMessageId,
-					null,
-					null,
-					targetLocalId,
-					chatId,
-					account,
-					timestamp,
-					[reaction],
-					borogove.ReactionUpdateKind.AppendReactions,
-				);
-
-				const withReaction = await persistence.storeReaction(account, update);
-
-				const emptyBuilder = new borogove.ChatMessageBuilder({
-					localId: reactionMessageId,
-					senderId: account,
-					direction: 1,
-				});
-				emptyBuilder.sortId = "a1";
-				emptyBuilder.to = borogove.JID.parse(chatId);
-				emptyBuilder.from = borogove.JID.parse(account);
-				emptyBuilder.recipients = [emptyBuilder.to];
-				emptyBuilder.replyTo = [emptyBuilder.from];
-
-				const [stored] = await persistence.storeMessages(account, [
-					emptyBuilder.build(),
-				]);
-
-				return {
-					before: [...withReaction!.reactions.keys()],
-					after: [...stored.reactions.keys()],
-				};
-			},
-			{ borogove, persistence },
+		const account = "alice@example.com";
+		const chatId = "hatter@example.com";
+		const targetLocalId = "target-message";
+		const reactionMessageId = "reaction-message";
+		const timestamp = "2026-09-09T12:00:00Z";
+
+		const targetBuilder = new borogove.ChatMessageBuilder({
+			localId: targetLocalId,
+			senderId: account,
+			direction: 1,
+		});
+		targetBuilder.sortId = "a0";
+		targetBuilder.to = borogove.JID.parse(chatId);
+		targetBuilder.from = borogove.JID.parse(account);
+		targetBuilder.recipients = [targetBuilder.to];
+		targetBuilder.replyTo = [targetBuilder.from];
+
+		await persistence.storeMessages(account, [targetBuilder.build()]);
+
+		const reaction = new borogove.CustomEmojiReaction(
+			account,
+			timestamp,
+			"tada",
+			"https://example.com/tada.png",
+			reactionMessageId,
 		);
+		const update = new borogove.ReactionUpdate(
+			reactionMessageId,
+			null,
+			null,
+			targetLocalId,
+			chatId,
+			account,
+			timestamp,
+			[reaction],
+			borogove.ReactionUpdateKind.AppendReactions,
+		);
+
+		const withReaction = await persistence.storeReaction(account, update);
 
-		expect(result.before).toEqual(["https://example.com/tada.png"]);
-		expect(result.after).toEqual([]);
+		const emptyBuilder = new borogove.ChatMessageBuilder({
+			localId: reactionMessageId,
+			senderId: account,
+			direction: 1,
+		});
+		emptyBuilder.sortId = "a1";
+		emptyBuilder.to = borogove.JID.parse(chatId);
+		emptyBuilder.from = borogove.JID.parse(account);
+		emptyBuilder.recipients = [emptyBuilder.to];
+		emptyBuilder.replyTo = [emptyBuilder.from];
+
+		const [stored] = await persistence.storeMessages(account, [
+			emptyBuilder.build(),
+		]);
+
+		expect([...withReaction!.reactions.keys()]).toEqual([
+			"https://example.com/tada.png",
+		]);
+		expect([...stored.reactions.keys()]).toEqual([]);
 	});
 }
 
 type TestKeyPair = {
-	privKey: number[];
-	pubKey: number[];
+	privKey: ArrayBuffer;
+	pubKey: ArrayBuffer;
 };
 
-const makeKey = (): number[] => [...randomBytes(32)];
+const makeKey = (): ArrayBuffer => {
+	const bytes = new Uint8Array(32);
+	crypto.getRandomValues(bytes);
+	return bytes.buffer;
+};
 
 const makeKeyPair = (): TestKeyPair => ({
 	privKey: makeKey(),
-	pubKey: makeKey().reverse(),
+	pubKey: makeKey(),
 });
 
 function expectKeyPair(actual: TestKeyPair, expected: TestKeyPair) {
@@ -2462,6 +1926,6 @@ function expectKeyPair(actual: TestKeyPair, expected: TestKeyPair) {
 	expectKey(actual.pubKey, expected.pubKey);
 }
 
-function expectKey(actual: number[], expected: number[]) {
+function expectKey(actual: ArrayBuffer, expected: ArrayBuffer) {
 	expect(actual).toEqual(expected);
 }
diff --git a/test/sqlite.spec.ts b/test/sqlite.spec.ts
index d124d0f..d34b33f 100644
--- a/test/sqlite.spec.ts
+++ b/test/sqlite.spec.ts
@@ -1,4 +1,65 @@
-import { sqliteTest } from "./browser-test";
+import { test } from "vitest";
+import * as borogove from "../playwright/.cache/borogove.js";
+import * as sqlite from "../playwright/.cache/sqlite-wasm.js";
 import { sharedPersistenceTests } from "./persistence-tests";
 
+async function sqliteFixture() {
+	const databaseName = "sqlite";
+	const mediaStore = await borogove.persistence.MediaStoreCache(
+		`${databaseName}-media`,
+	);
+	window.sqliteWorker1Url = new URL("/sqlite-worker1.js", location.href);
+	const persistence = new sqlite.borogove_persistence_Sqlite(
+		databaseName,
+		mediaStore,
+	);
+	const storeChats = persistence.storeChats.bind(persistence);
+	// TODO: remove this wrapper
+	//
+	// storeChats is not currently awaitable. The Sqlite tests were adding
+	// manual delays that the IDB tests didn't need. I wanted to share the
+	// tests, so wrapping the implementation with a delay here lets me do
+	// that.
+	//
+	// Changing storeChats to return a Promise that could be awaited would
+	// be the better long term solution, but there's already a debounce
+	// thing in storeChats itself that maybe should be revisited and that
+	// seemed like a riskier change, so I went with this for now.
+	persistence.storeChats = (...args) => {
+		storeChats(...args);
+		return new Promise((resolve) => setTimeout(resolve, 200));
+	};
+	const createChannel = (p: any, chatId: string) =>
+		new sqlite.Channel(null, null, p, chatId);
+	const storeIncompleteMember = async (chatId: string) => {
+		await persistence.db.exec(
+			"INSERT INTO members(account_id, chat_id, member_id, display_name, photo_uri, is_self, chat, roles, presence, jid) VALUES(?, ?, ?, ?, ?, ?, ?, jsonb(?), jsonb(?), ?)",
+			[
+				"alice@example.com",
+				chatId,
+				"room-members-7@example.com/incomplete",
+				"",
+				null,
+				0,
+				"{}",
+				"[]",
+				"{}",
+				"",
+			],
+		);
+	};
+	return { borogove, persistence, createChannel, storeIncompleteMember };
+}
+
+const sqlitePersistence = sqliteFixture();
+function sqliteTest(name: string, fn: (fixture: any) => Promise<void>) {
+	test("sqlite: " + name, async () => {
+		const current = await sqlitePersistence;
+		await current.persistence.db.exec(
+			"DELETE FROM messages; DELETE FROM chats; DELETE FROM keyvaluepairs; DELETE FROM caps; DELETE FROM services; DELETE FROM accounts; DELETE FROM reactions; DELETE FROM members; DELETE FROM omemo_identity_keys; DELETE FROM omemo_devices; DELETE FROM omemo_prekeys; DELETE FROM omemo_signed_prekeys; DELETE FROM omemo_contact_identity_keys; DELETE FROM omemo_sessions; DELETE FROM omemo_sessions_meta;",
+		);
+		await fn(current);
+	});
+}
+
 sharedPersistenceTests(sqliteTest);
diff --git a/vitest.config.mts b/vitest.config.mts
new file mode 100644
index 0000000..c006a39
--- /dev/null
+++ b/vitest.config.mts
@@ -0,0 +1,39 @@
+import { defineConfig } from "vitest/config";
+import { playwright } from "@vitest/browser-playwright";
+import { createReadStream } from "node:fs";
+
+export default defineConfig({
+	root: "..",
+	plugins: [
+		{
+			name: "serve-sqlite-worker",
+			configureServer(server) {
+				server.middlewares.use("/sqlite-worker1.js", (_req, res) => {
+					res.setHeader("Content-Type", "text/javascript; charset=utf-8");
+					createReadStream(
+						new URL("playwright/.cache/sqlite-worker1.js", import.meta.url),
+					).pipe(res);
+				});
+			},
+		},
+	],
+	test: {
+		include: ["test/idb.spec.ts", "test/sqlite.spec.ts"],
+		browser: {
+			enabled: true,
+			provider: playwright(),
+			headless: true,
+			instances: [
+				{
+					browser: "chromium",
+					include: ["test/idb.spec.ts", "test/sqlite.spec.ts"],
+				},
+				{
+					browser: "firefox",
+					include: ["test/idb.spec.ts", "test/sqlite.spec.ts"],
+				},
+				{ browser: "webkit", include: ["test/idb.spec.ts"] },
+			],
+		},
+	},
+});