Skip to content

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

Draft
mormubis wants to merge 30 commits intomainfrom
adlrb/vitest
Draft

👷 Migrate unit tests from Karma/Jasmine to Vitest#4196
mormubis wants to merge 30 commits intomainfrom
adlrb/vitest

Conversation

@mormubis
Copy link
Contributor

@mormubis mormubis commented Feb 17, 2026

Motivation

Migrate the entire unit test suite from Karma + Jasmine to Vitest 4.x with browser mode (Playwright). This modernizes the test infrastructure, provides faster test execution, better error messages, and aligns with the broader ecosystem move away from Karma (which is deprecated).

Changes

  • Vitest config (vitest.config.ts): Browser mode with headless Chromium via @vitest/browser-playwright, path aliases matching tsconfig, globals enabled
  • 230 spec files migrated: Jasmine API → Vitest API (jasmine.createSpyvi.fn, .and.callFake.mockImplementation, etc.)
  • Core test utilities rewritten: mockClock, mockFlushController, mockRequestIdleCallback, collectAsyncCalls, registerCleanupTask, etc.
  • mockClock redesign: Excludes performance from fake timers, uses performance.timing.navigationStart as integer timeOrigin, overrides performance.now on the real object
  • Native getter spying: Object.defineProperty instead of vi.spyOn for DOM/CSSOM native getters (CSSImportRule, Node.parentNode)
  • Runtime conditional skips: Replaced return // skip pattern with Vitest's ctx.skip() so conditionally skipped tests are properly reported as "skipped" instead of silently passing
  • import.meta.glob replaces webpack's require.context for JSON schema loading
  • Setup file (test/unit/vitest.setup.ts) replaces packages/core/test/forEach.spec.ts
  • Migration script (scripts/migrate-to-vitest.ts) for automated Jasmine → Vitest conversion
  • AGENTS.md updated with Vitest commands

Results

  • 3139 tests pass, 0 failures (226/227 files pass, 1 skipped)
  • 12 individual tests skipped (runtime feature detection + startRecording needing built worker string)
  • 2 files excluded: trackRuntimeError.spec.ts (intentional throws crash browser page), taskQueue.spec.ts (pre-existing browser crash)

Test instructions

yarn vitest run          # Full suite (~14s)
yarn vitest run --ui     # Interactive UI

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

🤖 Generated with Claude Code

@github-actions
Copy link

github-actions bot commented Feb 17, 2026

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

@cit-pr-commenter-54b7da
Copy link

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

Bundles Sizes Evolution

📦 Bundle Name Base Size Local Size 𝚫 𝚫% Status
Rum 171.96 KiB 171.96 KiB 0 B 0.00%
Rum Profiler 4.67 KiB 4.67 KiB 0 B 0.00%
Rum Recorder 24.88 KiB 24.88 KiB 0 B 0.00%
Logs 56.29 KiB 56.29 KiB 0 B 0.00%
Flagging 944 B 944 B 0 B 0.00%
Rum Slim 127.73 KiB 127.73 KiB 0 B 0.00%
Worker 23.63 KiB 23.63 KiB 0 B 0.00%
🚀 CPU Performance
Action Name Base CPU Time (ms) Local CPU Time (ms) 𝚫%
RUM - add global context 0.0086 0.0041 -52.33%
RUM - add action 0.0267 0.0133 -50.19%
RUM - add error 0.0205 0.0126 -38.54%
RUM - add timing 0.0047 0.0025 -46.81%
RUM - start view 0.0159 0.0119 -25.16%
RUM - start/stop session replay recording 0.001 0.0006 -40.00%
Logs - log message 0.0215 0.0141 -34.42%
🧠 Memory Performance
Action Name Base Memory Consumption Local Memory Consumption 𝚫
RUM - add global context 25.94 KiB 26.78 KiB +858 B
RUM - add action 112.96 KiB 114.94 KiB +1.98 KiB
RUM - add timing 27.34 KiB 26.11 KiB -1.23 KiB
RUM - add error 115.92 KiB 115.59 KiB -335 B
RUM - start/stop session replay recording 25.90 KiB 25.92 KiB +20 B
RUM - start view 506.44 KiB 503.74 KiB -2.70 KiB
Logs - log message 46.62 KiB 45.03 KiB -1.59 KiB

🔗 RealWorld

@mormubis mormubis force-pushed the adlrb/vitest branch 4 times, most recently from 2885079 to 7fbf28d Compare February 19, 2026 15:06
mormubis and others added 24 commits February 20, 2026 11:18
- STACK.md - Technologies and dependencies
- ARCHITECTURE.md - System design and patterns
- STRUCTURE.md - Directory layout
- CONVENTIONS.md - Code style and patterns
- TESTING.md - Test structure
- INTEGRATIONS.md - External services
- CONCERNS.md - Technical debt and issues
Install vitest, @vitest/browser, @vitest/browser-playwright, and
@vitest/coverage-istanbul. Create vitest.config.ts with browser mode
(Playwright/Chromium), global test APIs, TypeScript path aliases, and
__BUILD_ENV__ defines.

Create test/unit/vitest.setup.ts replacing forEach.spec.ts.

Migrate all ~15 core test utility files from Jasmine to Vitest API:
- jasmine.createSpy() → vi.fn()
- jasmine.clock() → vi.useFakeTimers()/vi.advanceTimersByTime()
- spyOn/spyOnProperty → vi.spyOn()
- jasmine.isSpy → vi.isMockFunction
- getCurrentJasmineSpec → Vitest beforeEach/afterEach context
- .and.callFake() → .mockImplementation()
- .calls.all()/.calls.count() → .mock.calls/.mock.results
Automated migration of all spec files across all packages:
- jasmine.createSpy() → vi.fn()
- spy.and.callFake() → spy.mockImplementation()
- spy.and.returnValue() → spy.mockReturnValue()
- spy.calls.mostRecent().args → spy.mock.lastCall
- spy.calls.argsFor(n) → spy.mock.calls[n]
- spy.calls.count() → spy.mock.calls.length
- spy.calls.reset() → spy.mockClear()
- spy.calls.all() → spy.mock.calls
- jasmine.objectContaining → expect.objectContaining
- jasmine.any() → expect.any()
- jasmine.anything() → expect.anything()
- jasmine.stringContaining → expect.stringContaining
- spyOn() → vi.spyOn()
- spyOnProperty() → vi.spyOn()
- pending() → return // skip
- fail() → throw new Error()
- toHaveBeenCalledOnceWith(args) → split into two assertions
- jasmine.Spy<T> → Mock<T>

Also add pako to optimizeDeps.include to prevent Vite reload flakiness.
Include migration script in scripts/migrate-to-vitest.ts.
- Add @datadog/browser-core/test and @datadog/browser-rum-core/test
  aliases to vitest.config.ts (fixes 89 import failures)
- Migrate packages/rum-core/test/createFakeClick.ts from Jasmine to Vitest
- Fix .mock.lastCall?.args[0] → .mock.lastCall?.[0] in 4 files
- Fix .calls.all() → .mock.calls in 3 files
- Update package.json scripts: test:unit → vitest run, test:unit:watch → vitest
Rewrite packages/rum-core/test/allJsonSchemas from CJS (require.context)
to ESM (import.meta.glob) for Vite compatibility. Delete old .js and .d.ts
files.
- Fix malformed throw patterns from migration (2 files)
- Fix bare 'packages/' imports → '@datadog/' aliases (4 files)
- tsconfig.default.json: "types": ["jasmine"] → "types": ["vitest/globals"]
- AGENTS.md: Update test framework docs and commands for Vitest
- Remove bail:1 from vitest.config.ts (interferes with full suite runs)
- Fix spyOn not defined in mockGlobalPerformanceBuffer.ts (80 failures)
- Replace toBeTrue/toBeFalse/toHaveSize with Vitest equivalents (38 failures)
- Add restoreMocks: true to auto-restore spies between tests (~40 failures)
- Fix toContain with objects/arrays → toContainEqual for deep equality
- Fix toContain(expect.arrayContaining) in tracer.spec.ts
- endpointBuilder.spec.ts: Convert regex-in-string toMatch to toContain
  assertions (Vitest toMatch with strings does substring match, not regex)
- viewportObservable.spec.ts: toContain → toContainEqual for deep equality
- packages/rum-core/test/emulate/mockDocumentReadyState.ts: spyOnProperty → vi.spyOn
- packages/rum-core/src/domain/resource/retrieveInitialDocumentResourceTiming.spec.ts:
  Convert done() callbacks to Promise-based (Vitest 4.x deprecation)
- mockClock.ts: Remove redundant vi.spyOn(performance, 'now') since
  vi.useFakeTimers() already handles it (fixes restoreMocks conflict)
- disableJasmineUncaughtExceptionTracking: Use direct assignment instead
  of vi.spyOn for window.onerror (which is null, not a function)
- trackRuntimeError.spec.ts: Guard window.onerror spy with function check
- recorderApi.spec.ts: .and.resolveTo() → .mockResolvedValue()
- Remove remaining .and.callThrough() patterns (5 files)
- Fix garbled vi.fn() calls in sanitize.spec.ts
- consoleCollection.spec.ts: Fix whatever() asymmetric matcher
- profiler.spec.ts: Fix double-src in @datadog/browser-rum-core/src/ alias path
…s mode

- Switch browser to headless Chromium for faster test execution
- Fix mockClock.ts: capture performance.timing.navigationStart BEFORE
  vi.useFakeTimers() to prevent fake timers from resetting performance.now()
- Convert 80 done() callbacks to Promise-based tests across 10 spec files
  (Vitest 4.x removed done() callback support)
- Fix consoleCollection.spec.ts: use toMatchObject to avoid cross-realm
  expect.any(Number) matcher issue in browser mode
- Skip startRecording.spec.ts when deflate worker is not built

Results: 210/229 files passing (91.7%), 3085/3150 tests passing (97.9%)
- Fix mockClock: exclude performance from fake timers so override sticks
  on real object; use performance.timing.navigationStart as integer
  timeOrigin to avoid floating-point precision and timing race issues
- Fix headless: move headless:true to top-level browser property (provider
  overrides launchOptions.headless)
- Fix native getter spying: use Object.defineProperty instead of vi.spyOn
  for CSSImportRule.styleSheet and Node.parentNode (Illegal invocation)
- Fix fetch.spec.ts: mock resolved value to prevent real HTTP requests
- Fix viewportObservable: remove async waitAfterNextPaint from cleanup
- Fix sessionState: wrap string expire with Number() for comparison
- Relax cssText stats assertions for browser-injected styles
- Exclude trackRuntimeError (crashes browser) and taskQueue (pre-existing)
Replace `return // skip` pattern with Vitest's `ctx.skip()` so
conditionally skipped tests are properly reported as "skipped"
instead of silently passing.
Add explicit `import { describe, it, expect, ... } from 'vitest'`
to all 229 spec files and test helpers. Remove `globals: true` from
vitest.config.ts and `vitest/globals` types from tsconfig.default.json.

This eliminates global type pollution and makes dependencies explicit.
- Convert new spec files from main (eventTracker, trackManualResources)
  from Jasmine to Vitest (jasmine.any → expect.any, toBeTrue → toBe(true),
  toHaveSize → toHaveLength)
- Add __BUILD_ENV__SDK_VERSION__ to vitest defines (new compile-time usage)
- Fix findActionId() assertions (returns array after API change)
- Remove unused test/vi imports from spec files
- Fix import order in vitest.config.ts
- Add vitest.config.ts to no-default-export eslint override
- Add spec files and test utils to no-extraneous-dependencies override
- Export leakDetection from core test index (fix protected directory import)
- Auto-fix formatting across 37 files
- Add ! to .mock.lastCall accesses (possibly undefined in strict mode)
- Fix API signature mismatches (expireSession, mockReturnValueOnce)
- Remove Jasmine-only matchers (.withContext, message args to toBe)
- Replace DoneFn type with () => void, fail() with throw new Error()
- Add vite/client reference for import.meta.glob typing
- Widen Mock types with as any for mock implementations
- Fix lint errors in migrate-to-vitest.ts: wrap in runMain(), fix JSDoc
  indentation, remove unused imports
- Fix only-throw-error violations: replace throw 'string' with throw new Error()
  in configuration, sessionStore, sessionInLocalStorage, urlPolyfill, recorderApi specs
- Fix no-unreachable: remove dead return after throw in sessionStore.spec.ts
- Fix no-empty-function: use null instead of () => {} for window.onerror
- Fix no-unused-vars: remove unused whatever() function in consoleCollection.spec.ts
- Fix unsafe-return/unsafe-call: add eslint-disable comments in allJsonSchemas.ts,
  startWorker.spec.ts, and mutationPayloadValidator.ts
- Delete forEach.spec.ts (replaced by vitest.setup.ts, excluded from vitest)
- Fix pre-existing typecheck errors in trackRuntimeError.spec.ts: add non-null
  assertions to spy.mock.lastCall usages
- Add vitest packages to LICENSE-3rdparty.csv: @vitest/browser,
  @vitest/browser-playwright, @vitest/coverage-istanbul, vitest

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- trackRuntimeError.spec.ts: remove unnecessary spy! non-null assertions
- telemetry.spec.ts, startCustomerDataTelemetry.spec.ts,
  trackCumulativeLayoutShift.spec.ts: remove unused afterEach import
- eventCollection.spec.ts: replace remaining jasmine.any() with expect.any()
- longTaskCollection.spec.ts: remove stale longTaskContexts.findLongTasks tests
  (long task contexts were moved to profiler in main)
- startRecording.spec.ts: remove unused mockExperimentalFeatures import
- longTaskHistory.spec.ts: add explicit vitest imports (new file from main)
- profiler.spec.ts: fix LongTaskContext import (use ./longTaskHistory source,
  not @datadog/browser-rum-core), remove non-existent LONG_TASK_ID_HISTORY_TIME_OUT_DELAY

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…n result

That helper can return undefined when onunhandledrejection is unsupported,
so spy! is necessary on line 316.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Port the fix from main (67e5142) to vitest.setup.ts: add
resetExperimentalFeatures() to afterEach so that flags set by one test
don't leak into others when running in shuffled order.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- .gitlab-ci.yml: add 'yarn playwright install chromium' before unit tests
  (Vitest browser mode requires Playwright's Chromium binary in CI)
- Fix Prettier formatting in longTaskCollection.spec.ts, startRecording.spec.ts,
  migrate-to-vitest.ts
- packages/rum/src/types/profiling.ts: commit generated file with /* eslint-disable */
  to avoid check-schemas drift
- mutationPayloadValidator.ts: replace static vitest import with globalThis.expect
  so E2E tests (CJS) can require() the file without crashing

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
mormubis and others added 6 commits February 20, 2026 11:35
- serializeMutations.spec.ts: add explicit vitest imports (new file from main
  was using globals without importing them)
- mutationPayloadValidator.ts: accept expect at createMutationPayloadValidator()
  creation time instead of defaulting to globalThis.expect at validate() call
  time (window.expect is not set in vitest browser globals mode)
- trackMutation.spec.ts: pass { expect } to createMutationPayloadValidator(),
  fix shadowRoot! non-null assertion (used before assigned in TS control flow)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- trackMutation.spec.ts: run Prettier (formatting after sed-based edits)
- mutationPayloadValidator.ts: add ! to expectedExpect default to satisfy
  TS2722 (expect is always provided by caller at creation or call time)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The 3 'view events' tests used clock.tick(VIEW_DURATION - relativeNow())
to advance to absolute time VIEW_DURATION. In Vitest, all test files share
one browser page — by the time the test runs, the page can be alive for
several seconds, making relativeNow() > VIEW_DURATION and the tick negative
('Negative ticks are not supported').

Fix: replace with vi.setSystemTime(performance.timing.navigationStart +
VIEW_DURATION), which directly sets the absolute fake time without relying
on the delta from current relativeNow().

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Creates the Vitest BrowserStack config required by the 'unit-bs' CI job.
Mirrors karma.bs.conf.js: uses @vitest/browser-playwright with connectOptions
to route each browser session through BrowserStack's CDP endpoint.

Also fixes startRum.spec.ts flaky tests: use vi.setSystemTime(navigationStart
+ VIEW_DURATION) instead of clock.tick(VIEW_DURATION - relativeNow()) to
advance the clock to the correct absolute time without risk of negative ticks.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
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

Comments