Skip to content

👷 Migrate unit tests from Karma/Jasmine to Vitest - #4196

Draft
mormubis wants to merge 39 commits into
mainfrom
adlrb/vitest
Draft

👷 Migrate unit tests from Karma/Jasmine to Vitest#4196
mormubis wants to merge 39 commits into
mainfrom
adlrb/vitest

Conversation

@mormubis

@mormubis mormubis commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Motivation

Karma is deprecated. This migrates the full unit test suite to Vitest 4.x with browser mode (Playwright).

Changes

Most of the diff is mechanical spec file replacements (jasmine.createSpy()vi.fn(), etc.). The interesting parts:

mockClock had to be rewritten because Vitest's vi.useFakeTimers fakes performance by default, which breaks every perf-related test. The toFake list now explicitly excludes it.

allJsonSchemas was using webpack's require.context to load schema files. Replaced with Vite's import.meta.glob.

BrowserStack needed bs-local.com as server host because Safari replaces localhost with its own domain, breaking cookie access in vitest's iframe. Same issue we already have with ServiceWorker tests in E2E. Cookie tests are still skipped on Safari because this alone doesn't fully fix it.

The unit-bs CI job needs Playwright installed with system deps (--with-deps) and 4 CPUs to handle 5 concurrent remote browsers without timing out.

vite@8.0.8 is pinned in the developer extension because @vitejs/plugin-react can't resolve vite/internal in newer patch versions.

3784 unit tests pass, 18628 BrowserStack tests across 5 browsers.

Test instructions

yarn test:unit
yarn typecheck
yarn lint

Checklist

  • Tested locally
  • Tested on staging
  • Added unit tests for this change.
  • Added e2e/integration tests for this change.
  • Updated documentation and/or relevant AGENTS.md file

@github-actions

github-actions Bot commented Feb 17, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Feb 17, 2026

Copy link
Copy Markdown

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 172.61 KiB 172.61 KiB 0 B 0.00%
Rum Profiler 8.22 KiB 8.22 KiB 0 B 0.00%
Rum Recorder 21.14 KiB 21.14 KiB 0 B 0.00%
Logs 54.58 KiB 54.58 KiB 0 B 0.00%
Rum Slim 130.44 KiB 130.44 KiB 0 B 0.00%
Worker 22.96 KiB 22.96 KiB 0 B 0.00%

@mormubis
mormubis force-pushed the adlrb/vitest branch 5 times, most recently from 319576e to 02e30e1 Compare February 20, 2026 10:19
@mormubis
mormubis force-pushed the adlrb/vitest branch 18 times, most recently from 051c55a to 23d1a16 Compare April 30, 2026 21:16
Comment thread vitest.config.ts
},

test: {
browser: {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browser mode with Playwright. Locally it runs headless Chromium. On BrowserStack, Playwright connects to remote browsers via WebSocket (wss://cdp.browserstack.com/playwright), so the old browser versions (Chrome 63, Edge 80, etc.) are real browsers hosted by BrowserStack, not local Playwright binaries. The aliases mirror tsconfig.base.json so vitest resolves packages the same way TypeScript does.

// AND so performance.timing.navigationStart remains accessible.
// When performance IS faked, @sinonjs/fake-timers replaces the object and our
// override silently fails (the fake performance.now() starts at 0).
vi.useFakeTimers({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

performance is excluded from toFake. If we fake it, performance.now() returns fake time but the browser's internal performance observer still uses real time. Breaks every perf-related test.

mostRecent(): { args: Parameters<F>; returnValue: ReturnType<F> }
}

export function collectAsyncCalls<F extends (...args: any[]) => any>(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vitest equivalent of the old Jasmine calls.all() pattern. Wraps a vi.fn() and resolves a promise once the expected number of calls is reached.

/// <reference types="vite/client" />
// Load all JSON schema files from the rum-events-format submodule.
// Uses Vite's import.meta.glob (replaces webpack's require.context).
const schemaModules = import.meta.glob('../../../rum-events-format/schemas/**/*.json', { eager: true })

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require.contextimport.meta.glob. Same result, Vite API instead of webpack.

…convert new debugger specs to Vitest

- Add @datadog/js-core/time alias to vitest.config.ts
- Move time-related imports from @datadog/browser-core to @datadog/js-core/time across ~70 files
- Convert display.spec.ts, probes.spec.ts (new from main) to Vitest
- Re-convert deliveryApi.spec.ts and api.spec.ts from main (complete rewrite)
- Fix allJsonSchemas.js require.context (removed .js/.d.ts, kept .ts with import.meta.glob)
- Fix getTimeStamp → toTimeStamp rename
- Fix getNavigationStart → getTimeOrigin rename
- Remove supportPerformanceTimingEvent guard (removed on main)
mormubis added 10 commits June 9, 2026 16:24
…vite 7 compatibility

Vitest brings vite 7.x which conflicts with @vitejs/plugin-react 6.x
(requires vite 8). Pin to 5.2.0 via Yarn resolution for wxt compatibility.
Aliases were pointing to old directory names (packages/core/, packages/rum-core/,
etc.) instead of the renamed directories (packages/browser-core/,
packages/browser-rum-core/, etc.). Also adds the missing @datadog/js-core/time
alias. The old paths worked by accident via yarn workspace symlink fallback.
# Conflicts:
#	packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts
#	yarn.lock
Four spec files had Jasmine patterns after the merge with main:
- addEventListener.spec.ts: jasmine.createSpy().and.throwError
- cookieAccess.spec.ts: jasmine.createSpy().and.throwError
- reportObservable.spec.ts: jasmine.Spy cast + .and.throwError
- eventBridge.spec.ts: toBeTrue/toBeFalse/spyOn (new wildcard tests)
Main added instrumentConstructor utility (#4714) with Jasmine tests.
Converted all patterns: jasmine.createSpy → vi.fn, jasmine.any → expect.any,
.calls.mostRecent().args → .mock.calls, toBeTrue/toBeFalse → toBe(true/false),
toHaveBeenCalledOnceWith → toHaveBeenCalledOnce + toHaveBeenCalledWith.
Vitest supports ctx.skip(condition, reason) to explain why a test is
skipped. Added descriptive reasons to all 34 bare ctx.skip() calls
across the test suite.
# Conflicts:
#	packages/browser-core/src/domain/contexts/accountContext.spec.ts
#	packages/browser-core/src/domain/contexts/tabContext.spec.ts
#	packages/browser-core/src/tools/abstractHooks.spec.ts
#	packages/browser-logs/src/domain/assembly.spec.ts
#	packages/browser-rum-core/src/domain/action/actionCollection.spec.ts
#	packages/browser-rum-core/src/domain/contexts/ciVisibilityContext.spec.ts
#	packages/browser-rum-core/src/domain/contexts/displayContext.spec.ts
#	packages/browser-rum-core/src/domain/contexts/pageStateHistory.spec.ts
#	packages/browser-rum-core/src/domain/contexts/sessionContext.spec.ts
#	packages/browser-rum-core/src/domain/contexts/sourceCodeContext.spec.ts
#	packages/browser-rum-core/src/domain/view/viewCollection.spec.ts
#	packages/browser-rum/src/boot/profilerApi.spec.ts
# Conflicts:
#	developer-extension/src/panel/flushEvents.spec.ts
#	package.json
#	packages/browser-core/src/browser/addEventListener.spec.ts
#	packages/browser-core/src/browser/cookieAccess.spec.ts
#	packages/browser-core/src/browser/pageMayExitObservable.spec.ts
#	packages/browser-core/src/browser/xhrObservable.spec.ts
#	packages/browser-core/src/domain/configuration/configuration.spec.ts
#	packages/browser-core/src/domain/context/storeContextManager.spec.ts
#	packages/browser-core/src/domain/report/reportObservable.spec.ts
#	packages/browser-debugger/src/domain/probes.spec.ts
#	packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts
#	packages/browser-rum-core/src/browser/performanceObservable.spec.ts
#	packages/browser-rum-core/src/browser/viewportObservable.spec.ts
#	packages/browser-rum-core/src/domain/contexts/ciVisibilityContext.spec.ts
#	packages/browser-rum-core/src/domain/view/bfCacheSupport.spec.ts
#	packages/browser-rum-core/src/domain/view/viewMetrics/trackFirstContentfulPaint.spec.ts
#	packages/browser-rum/src/domain/deflate/deflateEncoder.spec.ts
#	packages/browser-rum/src/domain/record/trackers/trackFocus.spec.ts
CI has a newer eslint-plugin-unicorn version that supports this option.
Local version didn't, causing the initial removal.
…Hook API changes

- package.json: re-added vitest deps/scripts/resolutions on top of main's changes
- allJsonSchemas.ts: updated glob path from submodule to node_modules/@datadog/rum-events-format
- profiler.spec.ts: took main's removal of findTrackedSessionSpy, converted toBeTrue
- tsconfig.default.json: removed jasmine types, added vitest.bs.config.ts exclusion
- vitest configs: updated js-core/time alias to src/entries/time
mormubis added 6 commits June 16, 2026 11:51
# Conflicts:
#	package.json
#	packages/browser-rum-core/test/allJsonSchemas.js
#	packages/browser-rum/src/domain/profiling/profiler.spec.ts
#	tsconfig.default.json
#	yarn.lock
- Add toHaveLength() guards alongside expect.arrayContaining() where
  jasmine.arrayWithExactContents was converted (trackClickActions,
  configuration specs)
- Convert bare return to ctx.skip() in serializeNode.stylesheet.spec.ts
- bs-wrapper: anchor failure regex to Vitest summary format (`/\d+ failed |/`)
  to avoid false positives from test console output like '3 failed retries'
- registerCleanupTask: await async cleanup tasks instead of fire-and-forget
- allJsonSchemas: throw if no schemas loaded (catches silent glob path breakage)
- Remove dead browserstack.keepalive.ts (never wired into setupFiles)
- Fix TaskContext → TestContext type import in stylesheet spec
- reportObservable: double-registration would fire spy twice silently
- deliveryApi: double-logging of probe failures would go undetected
- deflateEncoder: test named 'do not notify twice' now asserts count
Matches vitest.config.ts — prevents CJS modules that check `global`
from silently breaking on BrowserStack only.
Resolves conflicts from js-core/assembly, js-core/util, js-core/monitor
entry points and profiler transport extraction. Converts new spec files
from Jasmine to Vitest API. Adds missing vitest aliases for new
js-core subpath imports.
Resolves conflicts from createBatch API refactor (reportError string
signature, forceFlush), prototype pollution guards in mergeInto,
debugger init version tests, and dep bumps. Converts new Jasmine
patterns to Vitest API in auto-merged files.
Resolves conflicts from endpointBuilder/intakeSites move to
@datadog/js-core/transport. Adds js-core/transport alias to both
vitest configs. Converts import paths in 8 spec files + test helper.
@mormubis
mormubis force-pushed the adlrb/vitest branch 6 times, most recently from aa625fe to 4e06cf3 Compare July 2, 2026 09:37
bs-wrapper.ts: ANSI escape codes from FORCE_COLOR broke failure
detection — the regex /\d+ failed \|/ didn't match colored output
like '9 failed\x1b[39m |'. Strip ANSI before matching.

cookieAccess.spec.ts: CookieStore API tests crashed with TypeError
when globalObject.cookieStore was undefined (Jasmine's pending()
inside setup() skipped the enclosing it(), but Vitest doesn't).
Added beforeEach ctx.skip for the CookieStore describe block.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant