Why Browser Mode is the default
Why Browser Mode is the default
@equinor/fusion-framework-vitest-plugin-react-app runs component and hook tests in headless
Chromium by default. A real browser gives these tests more accurate DOM behavior, but it is
slower than happy-dom or jsdom.
This guide explains that tradeoff and shows how to use a different browser or renderer when a
test does not need Chromium.
Performance
A real browser is slower than DOM emulation. happy-dom and jsdom run in the same Node process
as the test. Browser Mode starts Chromium through Playwright and communicates with that browser
process.
Choose Browser Mode for fidelity, not speed. Keep fast unit tests on Node or DOM emulation when
they do not need browser behavior.
Why use a real browser
- React compatibility: the previous React test setup failed after its React 19 peer
dependency update. The repository then moved those tests to Vitest Browser Mode. See thepackages/utils/observable8.4.4 changelog and migration commit059aefae5d. - Real framework execution: tests control external boundaries and seed module state through
Module mocks, while the actual module configuration, lifecycle, providers,
and rendering behavior still run. - Browser fidelity: Chromium provides real layout,
ResizeObserver, custom elements, and
Shadow DOM — behaviorhappy-domandjsdomonly approximate.
Warning
A DOM-emulation gap is usually patched one of two ways: a polyfill standing in for the missing
browser API, or a component replacement that avoids exercising it at all. Both are test-only
code with no equivalent in production, and both defeat the point of the test: a polyfill can
drift from real browser behavior unnoticed, and a mocked component only proves the mock
renders correctly, never the real one. A passing test built on either can still fail — or
silently lie — against the real component. Treat every DOM-emulation workaround as debt to
remove, not a pattern to reach for.
A real browser does not eliminate every workaround: AG Grid license messages and Lit dev-mode
warnings are console noise from real Chromium runs, not DOM-emulation gaps, and suppressing them
carries none of the risk above (see Module mocks for the supported way to
seed module state instead of replacing a component). Remove a DOM-emulation-only workaround only
after confirming the affected test still passes against the real component in Browser Mode.
Choose a different runtime
You can change either the browser provider or the renderer:
- Stay in Browser Mode and replace Playwright with another Vitest browser provider.
- Keep the Fusion module setup but render with
happy-domorjsdom.
Use another browser provider
Pass test.browser.provider to defineProject to replace Playwright while staying in Browser
Mode. This example uses WebdriverIO:
import { defineProject } from '@equinor/fusion-framework-vitest-plugin-react-app/config';
import { webdriverio } from '@vitest/browser-webdriverio';
export default defineProject({
test: { browser: { provider: webdriverio() } },
});See Configuration for the complete defineProject behavior.
Use happy-dom or jsdom
Caution
Rendering with happy-dom or jsdom reintroduces the DOM-emulation tradeoff above: any gap
between the emulation and a real browser has to be closed with a polyfill or a component
replacement, not fixed. Only take this path for a component or hook that provably does not
need browser fidelity — otherwise stay in Browser Mode.
The Fusion module setup does not depend on Browser Mode. Build the same provider tree and pass it
to another renderer. The example below uses @testing-library/react on happy-dom:
import { render } from '@testing-library/react';
import { mockFramework } from '@equinor/fusion-framework/mock';
import { enableAppManifestMock, mockAppModules } from '@equinor/fusion-framework-app/mock';
import { FrameworkProvider } from '@equinor/fusion-framework-react';
import { ModuleProvider } from '@equinor/fusion-framework-react-module';
import type { AppModule } from '@equinor/fusion-framework-module-app';
import type { ReactElement } from 'react';
const env = {
manifest: {
appKey: 'test-app',
displayName: 'Test App',
description: 'Test app',
type: 'standalone',
},
} as const;
async function renderWithHappyDom(ui: ReactElement) {
const framework = await mockFramework<[AppModule]>((configurator) =>
enableAppManifestMock(configurator, env),
);
const app = await mockAppModules(undefined, env, framework);
return render(ui, {
wrapper: ({ children }) => (
<FrameworkProvider value={framework}>
<ModuleProvider value={app}>{children}</ModuleProvider>
</FrameworkProvider>
),
});
}Run the test in a Vitest project with environment: 'happy-dom' instead ofbrowser.enabled. Use a separate project if the same app also has Browser Mode tests.
Call the same enable*Mock functions from the mockAppModules configuration callback. These
mocks configure Fusion modules and do not depend on the renderer. renderHook from@testing-library/react can use the same wrapper for hook tests.
Use Browser Mode when browser behavior matters. Use happy-dom, jsdom, or no DOM for focused
tests that only need application logic or simple rendering.