diff --git a/app/client/hooks/__tests__/use-server-events.test.tsx b/app/client/hooks/__tests__/use-server-events.test.tsx
index f5a52a19..f6f8baf2 100644
--- a/app/client/hooks/__tests__/use-server-events.test.tsx
+++ b/app/client/hooks/__tests__/use-server-events.test.tsx
@@ -53,8 +53,8 @@ const createTestQueryClient = () =>
},
});
-const ConnectionConsumer = () => {
- useServerEvents();
+const ConnectionConsumer = ({ enabled = true }: { enabled?: boolean }) => {
+ useServerEvents({ enabled });
return null;
};
@@ -129,4 +129,24 @@ describe("useServerEvents", () => {
expect(MockEventSource.instances[0]?.close).toHaveBeenCalledTimes(1);
});
+
+ test("waits to subscribe until enabled", () => {
+ const queryClient = createTestQueryClient();
+ const view = render(
+
+
+ ,
+ );
+
+ expect(MockEventSource.instances).toHaveLength(0);
+
+ view.rerender(
+
+
+ ,
+ );
+
+ expect(MockEventSource.instances).toHaveLength(1);
+ expect(MockEventSource.instances[0]?.url).toBe("/api/v1/events");
+ });
});
diff --git a/app/client/hooks/use-file-browser.ts b/app/client/hooks/use-file-browser.ts
index e51baad1..81244638 100644
--- a/app/client/hooks/use-file-browser.ts
+++ b/app/client/hooks/use-file-browser.ts
@@ -1,4 +1,5 @@
import { useCallback, useMemo, useState } from "react";
+import { logger } from "~/client/lib/logger";
import type { FileEntry } from "../components/file-tree";
export type FetchFolderResult = {
@@ -135,7 +136,7 @@ export const useFileBrowser = (props: UseFileBrowserOptions) => {
setFetchedFolders((prev) => new Set(prev).add(folderPath));
} catch (error) {
- console.error("Failed to fetch folder contents:", error);
+ logger.error("Failed to fetch folder contents:", error);
} finally {
setLoadingFolders((prev) => {
const next = new Set(prev);
@@ -190,7 +191,7 @@ export const useFileBrowser = (props: UseFileBrowserOptions) => {
});
}
} catch (error) {
- console.error("Failed to load more files:", error);
+ logger.error("Failed to load more files:", error);
setFolderPagination((prev) => {
const next = new Map(prev);
next.set(folderPath, { ...pagination, isLoadingMore: false });
diff --git a/app/client/hooks/use-server-events.ts b/app/client/hooks/use-server-events.ts
index 62cb4ac8..b4001501 100644
--- a/app/client/hooks/use-server-events.ts
+++ b/app/client/hooks/use-server-events.ts
@@ -1,5 +1,6 @@
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect, useRef } from "react";
+import { logger } from "~/client/lib/logger";
import { serverEventNames, type ServerEventPayloadMap } from "~/schemas/server-events";
type LifecycleEventPayloadMap = {
@@ -64,7 +65,7 @@ const refreshQueriesForEvent = (eventName: ServerEventType) => {
void sharedState.queryClient.invalidateQueries().catch((error) => {
if (!isAbortError(error)) {
- console.error(`[SSE] Failed to refresh queries after ${eventName}:`, error);
+ logger.error(`[SSE] Failed to refresh queries after ${eventName}:`, error);
}
});
};
@@ -80,7 +81,7 @@ const connectEventSource = (queryClient: QueryClient) => {
eventSource.addEventListener("connected", (event) => {
const data = parseEventData<"connected">(event);
- console.info("[SSE] Connected to server events");
+ logger.info("[SSE] Connected to server events");
emit("connected", data);
});
@@ -91,7 +92,7 @@ const connectEventSource = (queryClient: QueryClient) => {
for (const eventName of serverEventNames) {
eventSource.addEventListener(eventName, (event) => {
const data = parseEventData(event);
- console.info(`[SSE] ${eventName}:`, data);
+ logger.info(`[SSE] ${eventName}:`, data);
refreshQueriesForEvent(eventName);
@@ -107,7 +108,7 @@ const connectEventSource = (queryClient: QueryClient) => {
}
eventSource.onerror = (error) => {
- console.error("[SSE] Connection error:", error);
+ logger.error("[SSE] Connection error:", error);
};
};
@@ -116,7 +117,7 @@ const disconnectEventSource = () => {
return;
}
- console.info("[SSE] Disconnecting from server events");
+ logger.info("[SSE] Disconnecting from server events");
sharedState.eventSource.close();
sharedState.eventSource = null;
sharedState.queryClient = null;
@@ -159,12 +160,16 @@ const addSharedEventListener = (
* Hook to listen to Server-Sent Events (SSE) from the backend
* Automatically handles cache invalidation for backup and volume events
*/
-export function useServerEvents() {
+export function useServerEvents({ enabled = true }: { enabled?: boolean } = {}) {
const queryClient = useQueryClient();
const addEventListener = useCallback(addSharedEventListener, []);
const hasMountedRef = useRef(false);
useEffect(() => {
+ if (!enabled) {
+ return;
+ }
+
connectEventSource(queryClient);
if (!hasMountedRef.current) {
sharedState.subscribers += 1;
@@ -182,7 +187,7 @@ export function useServerEvents() {
disconnectEventSource();
}
};
- }, [queryClient]);
+ }, [enabled, queryClient]);
return { addEventListener };
}
diff --git a/app/client/lib/datetime.ts b/app/client/lib/datetime.ts
index d9bb7d60..587544ad 100644
--- a/app/client/lib/datetime.ts
+++ b/app/client/lib/datetime.ts
@@ -2,6 +2,8 @@ import { formatDistanceToNow, isValid } from "date-fns";
type DateInput = Date | string | number | null | undefined;
+const getLocales = () => (typeof navigator !== "undefined" ? navigator.languages : undefined);
+
function formatValidDate(date: DateInput, formatter: (date: Date) => string): string {
if (!date) return "Never";
@@ -14,7 +16,7 @@ function formatValidDate(date: DateInput, formatter: (date: Date) => string): st
// 1/10/2026, 2:30 PM
export function formatDateTime(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
month: "numeric",
day: "numeric",
year: "numeric",
@@ -27,7 +29,7 @@ export function formatDateTime(date: DateInput): string {
// Jan 10, 2026
export function formatDateWithMonth(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
month: "short",
day: "numeric",
year: "numeric",
@@ -38,7 +40,7 @@ export function formatDateWithMonth(date: DateInput): string {
// 1/10/2026
export function formatDate(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
month: "numeric",
day: "numeric",
year: "numeric",
@@ -49,7 +51,7 @@ export function formatDate(date: DateInput): string {
// 1/10
export function formatShortDate(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
month: "numeric",
day: "numeric",
}).format(validDate),
@@ -59,7 +61,7 @@ export function formatShortDate(date: DateInput): string {
// 1/10, 2:30 PM
export function formatShortDateTime(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
month: "numeric",
day: "numeric",
hour: "numeric",
@@ -71,7 +73,7 @@ export function formatShortDateTime(date: DateInput): string {
// 2:30 PM
export function formatTime(date: DateInput): string {
return formatValidDate(date, (validDate) =>
- Intl.DateTimeFormat(navigator.languages, {
+ Intl.DateTimeFormat(getLocales(), {
hour: "numeric",
minute: "numeric",
}).format(validDate),
diff --git a/app/client/lib/logger.ts b/app/client/lib/logger.ts
new file mode 100644
index 00000000..8b703acb
--- /dev/null
+++ b/app/client/lib/logger.ts
@@ -0,0 +1,7 @@
+const PREFIX = "[APP]";
+
+export const logger = {
+ error: (...args: unknown[]) => console.error(PREFIX, ...args),
+ warn: (...args: unknown[]) => console.warn(PREFIX, ...args),
+ info: (...args: unknown[]) => console.info(PREFIX, ...args),
+};
diff --git a/app/client/modules/auth/routes/login.tsx b/app/client/modules/auth/routes/login.tsx
index 58663c1b..9e9aa9f3 100644
--- a/app/client/modules/auth/routes/login.tsx
+++ b/app/client/modules/auth/routes/login.tsx
@@ -9,6 +9,7 @@ import { Input } from "~/client/components/ui/input";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "~/client/components/ui/input-otp";
import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
import { decodeLoginError, getLoginErrorDescription } from "~/client/lib/sso-errors";
import { ResetPasswordDialog } from "../components/reset-password-dialog";
import { useNavigate } from "@tanstack/react-router";
@@ -65,7 +66,7 @@ export function LoginPage({ error }: LoginPageProps = {}) {
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Login failed", { description: error.message });
return;
}
@@ -103,7 +104,7 @@ export function LoginPage({ error }: LoginPageProps = {}) {
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Verification failed", { description: error.message });
setTotpCode("");
return;
diff --git a/app/client/modules/auth/routes/onboarding.tsx b/app/client/modules/auth/routes/onboarding.tsx
index e477837e..c13954a6 100644
--- a/app/client/modules/auth/routes/onboarding.tsx
+++ b/app/client/modules/auth/routes/onboarding.tsx
@@ -15,6 +15,7 @@ import { AuthLayout } from "~/client/components/auth-layout";
import { Input } from "~/client/components/ui/input";
import { Button } from "~/client/components/ui/button";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
import { useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { normalizeUsername } from "~/lib/username";
@@ -78,7 +79,7 @@ export function OnboardingPage() {
toast.success("Admin user created successfully!");
void navigate({ to: "/download-recovery-key" });
} else if (error) {
- console.error(error);
+ logger.error(error);
const errorMessage = error.message ?? "Unknown error";
toast.error("Failed to create admin user", { description: errorMessage });
}
diff --git a/app/client/modules/settings/components/backup-codes-dialog.tsx b/app/client/modules/settings/components/backup-codes-dialog.tsx
index 6f111332..f996e2d6 100644
--- a/app/client/modules/settings/components/backup-codes-dialog.tsx
+++ b/app/client/modules/settings/components/backup-codes-dialog.tsx
@@ -13,6 +13,7 @@ import {
import { Input } from "~/client/components/ui/input";
import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
type BackupCodesDialogProps = {
open: boolean;
@@ -45,7 +46,7 @@ export const BackupCodesDialog = ({ open, onOpenChange }: BackupCodesDialogProps
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Failed to generate backup codes", { description: error.message });
return;
}
diff --git a/app/client/modules/settings/components/two-factor-disable-dialog.tsx b/app/client/modules/settings/components/two-factor-disable-dialog.tsx
index 738e672b..c45d3539 100644
--- a/app/client/modules/settings/components/two-factor-disable-dialog.tsx
+++ b/app/client/modules/settings/components/two-factor-disable-dialog.tsx
@@ -12,6 +12,7 @@ import {
import { Input } from "~/client/components/ui/input";
import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
type TwoFactorDisableDialogProps = {
open: boolean;
@@ -44,7 +45,7 @@ export const TwoFactorDisableDialog = ({ open, onOpenChange, onSuccess }: TwoFac
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Failed to disable 2FA", { description: error.message });
return;
}
diff --git a/app/client/modules/settings/components/two-factor-setup-dialog.tsx b/app/client/modules/settings/components/two-factor-setup-dialog.tsx
index 5266fdc9..b6c54de5 100644
--- a/app/client/modules/settings/components/two-factor-setup-dialog.tsx
+++ b/app/client/modules/settings/components/two-factor-setup-dialog.tsx
@@ -14,6 +14,7 @@ import { Input } from "~/client/components/ui/input";
import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from "~/client/components/ui/input-otp";
import { Label } from "~/client/components/ui/label";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
type TwoFactorSetupDialogProps = {
open: boolean;
@@ -52,7 +53,7 @@ export const TwoFactorSetupDialog = ({ open, onOpenChange, onSuccess }: TwoFacto
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Failed to enable 2FA", { description: error.message });
return;
}
@@ -81,7 +82,7 @@ export const TwoFactorSetupDialog = ({ open, onOpenChange, onSuccess }: TwoFacto
});
if (error) {
- console.error(error);
+ logger.error(error);
toast.error("Verification failed", { description: error.message });
setVerificationCode("");
return;
diff --git a/app/client/modules/settings/routes/settings.tsx b/app/client/modules/settings/routes/settings.tsx
index 908fb3a7..a968cd09 100644
--- a/app/client/modules/settings/routes/settings.tsx
+++ b/app/client/modules/settings/routes/settings.tsx
@@ -19,6 +19,7 @@ import { Input } from "~/client/components/ui/input";
import { Label } from "~/client/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/client/components/ui/tabs";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
import { type AppContext } from "~/context";
import { TwoFactorSection } from "../components/two-factor-section";
import { useNavigate, useSearch } from "@tanstack/react-router";
@@ -55,7 +56,7 @@ export function SettingsPage({ appContext, initialMembers, initialSsoSettings, i
void navigate({ to: "/login", replace: true });
},
onError: ({ error }) => {
- console.error(error);
+ logger.error(error);
toast.error("Logout failed", { description: error.message });
},
},
diff --git a/app/client/modules/sso/components/sso-login-section.tsx b/app/client/modules/sso/components/sso-login-section.tsx
index b7629000..e4896d23 100644
--- a/app/client/modules/sso/components/sso-login-section.tsx
+++ b/app/client/modules/sso/components/sso-login-section.tsx
@@ -2,6 +2,7 @@ import { useMutation, useSuspenseQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { Button } from "~/client/components/ui/button";
import { authClient } from "~/client/lib/auth-client";
+import { logger } from "~/client/lib/logger";
import { getPublicSsoProvidersOptions } from "~/client/api-client/@tanstack/react-query.gen";
export function SsoLoginSection() {
@@ -25,7 +26,7 @@ export function SsoLoginSection() {
window.location.href = data.url;
},
onError: (error) => {
- console.error(error);
+ logger.error(error);
toast.error("SSO Login failed", { description: error.message });
},
});
diff --git a/app/lib/auth-routes.ts b/app/lib/auth-routes.ts
new file mode 100644
index 00000000..982e7f2d
--- /dev/null
+++ b/app/lib/auth-routes.ts
@@ -0,0 +1,7 @@
+export function isAuthRoute(pathname: string) {
+ if (pathname === "/onboarding") return true;
+ if (pathname === "/login") return true;
+ if (pathname.match(/^\/login\/[^/]+$/)) return true;
+ if (pathname.match(/^\/login\/[^/]+\/error$/)) return true;
+ return false;
+}
diff --git a/app/middleware/auth.ts b/app/middleware/auth.ts
index 94e49201..697c4381 100644
--- a/app/middleware/auth.ts
+++ b/app/middleware/auth.ts
@@ -3,14 +3,7 @@ import { redirect } from "@tanstack/react-router";
import { auth } from "~/server/lib/auth";
import { getRequestHeaders } from "@tanstack/react-start/server";
import { authService } from "~/server/modules/auth/auth.service";
-
-function isAuthRoute(pathname: string): boolean {
- if (pathname === "/onboarding") return true;
- if (pathname === "/login") return true;
- if (pathname.match(/^\/login\/[^/]+$/)) return true;
- if (pathname.match(/^\/login\/[^/]+\/error$/)) return true;
- return false;
-}
+import { isAuthRoute } from "~/lib/auth-routes";
export const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const headers = getRequestHeaders();
diff --git a/app/router.tsx b/app/router.tsx
index 3cb7f74b..b70c2340 100644
--- a/app/router.tsx
+++ b/app/router.tsx
@@ -2,6 +2,7 @@ import { createRouter } from "@tanstack/react-router";
import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
import { routeTree } from "./routeTree.gen";
import { MutationCache, QueryClient } from "@tanstack/react-query";
+import { logger } from "./client/lib/logger";
import { client } from "./client/api-client/client.gen";
import type { BreadcrumbItemData } from "./client/components/app-breadcrumb";
@@ -22,7 +23,7 @@ export function getRouter() {
void queryClient.invalidateQueries();
},
onError: (error) => {
- console.error("Mutation error:", error);
+ logger.error("Mutation error:", error);
void queryClient.invalidateQueries();
},
}),
diff --git a/app/routes/__root.tsx b/app/routes/__root.tsx
index c0875ab7..7938de8c 100644
--- a/app/routes/__root.tsx
+++ b/app/routes/__root.tsx
@@ -1,4 +1,4 @@
-import { Outlet, HeadContent, Scripts, createRootRouteWithContext } from "@tanstack/react-router";
+import { Outlet, HeadContent, Scripts, createRootRouteWithContext, useRouterState } from "@tanstack/react-router";
import appCss from "../app.css?url";
import { apiClientMiddleware } from "~/middleware/api-client";
import type { QueryClient } from "@tanstack/react-query";
@@ -9,6 +9,7 @@ import { useEffect } from "react";
import { ThemeProvider, THEME_COOKIE_NAME } from "~/client/components/theme-provider";
import { createServerFn } from "@tanstack/react-start";
import { getCookie } from "@tanstack/react-start/server";
+import { isAuthRoute } from "~/lib/auth-routes";
const fetchTheme = createServerFn({ method: "GET" }).handler(async () => {
const themeCookie = getCookie(THEME_COOKIE_NAME);
@@ -46,9 +47,10 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
errorComponent: (e) => {e.error.message}
,
});
-function RootLayout() {
+export function RootLayout() {
const { theme } = Route.useLoaderData();
- useServerEvents();
+ const pathname = useRouterState({ select: (state) => state.location.pathname });
+ useServerEvents({ enabled: !isAuthRoute(pathname) });
useEffect(() => {
document.body.setAttribute("data-app-ready", "true");
return () => {
diff --git a/e2e/0001-auth.setup.ts b/e2e/0001-auth.setup.ts
index caf679e3..61fe9379 100644
--- a/e2e/0001-auth.setup.ts
+++ b/e2e/0001-auth.setup.ts
@@ -1,5 +1,5 @@
import fs from "fs";
-import { test, expect } from "@playwright/test";
+import { test, expect } from "./test";
import { resetDatabase } from "./helpers/db";
import path from "node:path";
import { gotoAndWaitForAppReady } from "./helpers/page";
diff --git a/e2e/0002-backup-restore.spec.ts b/e2e/0002-backup-restore.spec.ts
index 817c4061..65760e57 100644
--- a/e2e/0002-backup-restore.spec.ts
+++ b/e2e/0002-backup-restore.spec.ts
@@ -1,7 +1,8 @@
import fs from "node:fs";
import { randomUUID } from "node:crypto";
import path from "node:path";
-import { expect, test, type Page, type TestInfo } from "@playwright/test";
+import { type Page, type TestInfo } from "@playwright/test";
+import { expect, test } from "./test";
import { gotoAndWaitForAppReady } from "./helpers/page";
const testDataPath = path.join(process.cwd(), "playwright", "temp");
diff --git a/e2e/0003-oidc.spec.ts b/e2e/0003-oidc.spec.ts
index 14629e8e..c0623042 100644
--- a/e2e/0003-oidc.spec.ts
+++ b/e2e/0003-oidc.spec.ts
@@ -1,4 +1,6 @@
-import { expect, test, type Browser, type Page } from "@playwright/test";
+import { type Browser, type Page } from "@playwright/test";
+import { expect, test } from "./test";
+import { trackBrowserErrors } from "./helpers/browser-errors";
import { gotoAndWaitForAppReady, waitForAppReady } from "./helpers/page";
const dexOrigin = process.env.E2E_DEX_ORIGIN ?? "http://dex:5557";
@@ -205,6 +207,7 @@ async function withOidcLoginAttempt(
origins: [],
},
});
+ const browserErrorTracker = trackBrowserErrors(context);
const page = await context.newPage();
try {
@@ -221,6 +224,7 @@ async function withOidcLoginAttempt(
}
await assertions(page);
+ browserErrorTracker.assertNoBrowserErrors();
} finally {
await context.close().catch(() => undefined);
}
diff --git a/e2e/helpers/browser-errors.ts b/e2e/helpers/browser-errors.ts
new file mode 100644
index 00000000..7a1fdfda
--- /dev/null
+++ b/e2e/helpers/browser-errors.ts
@@ -0,0 +1,63 @@
+import type { BrowserContext, ConsoleMessage, Page } from "@playwright/test";
+
+const IGNORED_PATTERNS: RegExp[] = [
+ /^\[APP\]/,
+ /Failed to load resource: the server responded with a status of 4\d{2}/,
+];
+
+const isIgnoredError = (text: string) => IGNORED_PATTERNS.some((pattern) => pattern.test(text));
+
+const formatConsoleMessage = (message: ConsoleMessage) => {
+ const location = message.location();
+ const hasLocation = !!location.url;
+ const formattedLocation = hasLocation
+ ? ` (${location.url}:${location.lineNumber + 1}:${location.columnNumber + 1})`
+ : "";
+
+ return `console.error${formattedLocation}\n${message.text()}`;
+};
+
+export const trackBrowserErrors = (context: BrowserContext) => {
+ const browserErrors: string[] = [];
+ const trackedPages = new WeakSet();
+
+ const trackPage = (page: Page) => {
+ if (trackedPages.has(page)) {
+ return;
+ }
+
+ trackedPages.add(page);
+
+ page.on("console", (message) => {
+ if (message.type() !== "error") {
+ return;
+ }
+
+ if (isIgnoredError(message.text())) {
+ return;
+ }
+
+ browserErrors.push(formatConsoleMessage(message));
+ });
+
+ page.on("pageerror", (error) => {
+ browserErrors.push(`pageerror\n${error.stack ?? error.message}`);
+ });
+ };
+
+ for (const page of context.pages()) {
+ trackPage(page);
+ }
+
+ context.on("page", trackPage);
+
+ return {
+ assertNoBrowserErrors() {
+ if (browserErrors.length === 0) {
+ return;
+ }
+
+ throw new Error(`Browser console errors detected:\n\n${browserErrors.join("\n\n")}`);
+ },
+ };
+};
diff --git a/e2e/test.ts b/e2e/test.ts
new file mode 100644
index 00000000..8847fade
--- /dev/null
+++ b/e2e/test.ts
@@ -0,0 +1,14 @@
+import { expect, test as base } from "@playwright/test";
+import { trackBrowserErrors } from "./helpers/browser-errors";
+
+export const test = base.extend({
+ context: async ({ context }, use) => {
+ const browserErrorTracker = trackBrowserErrors(context);
+
+ await use(context);
+
+ browserErrorTracker.assertNoBrowserErrors();
+ },
+});
+
+export { expect };