Automated Tests
Converse uses the Vitest test runner in
browser mode (via Playwright/Chromium) to run its
tests in a real browser. The suite was originally written for Jasmine,
so a small compatibility shim (vitest/setup.jasmine-shim.js) maps the Jasmine API
(spyOn, jasmine.*, the toEqualStanza matcher, fdescribe/fit, …) onto Vitest
primitives — the spec files themselves are unchanged.
In addition, we use ESLint to run a static analysis (aka linting) of the source files and report errors.
Whenever a commit is pushed to the Converse GitHub repo, all linting checks and tests are run in our continuous integration system.
Running tests
Section titled “Running tests”The primary way to run tests is using npm scripts:
npm test # Run main UI tests (headless Chromium, single run)npm run test:browser # Run main tests in a visible Chrome window (watch mode)npm run test:all # Run both headless and main testsnpm run test:headless # Run headless (core XMPP) tests onlynpm run test:headless:browser # Run headless tests in a visible Chrome windownpm run lint # Run ESLintmake check # Run linting, type checking, and all tests (full CI suite)Unlike the old Karma setup, npm test is a single run by default (it maps to
vitest run); there is no --single-run flag to remember.
First time? Browser mode needs the Playwright Chromium binary. Install it once with
npx playwright install chromium.
Always build before testing. Tests run against the pre-built dist/converse.js bundle,
not source files directly. Run npm run dev first if you’ve made changes.
npm run dev && npm testHeadless vs browser mode
Section titled “Headless vs browser mode”npm testandnpm run test:headlessrun in headless Chromium — fast, no visible browser, ideal for CInpm run test:browserandnpm run test:headless:browseropen a visible Chrome window (in watch mode) — useful for debugging
When running in browser mode, a Vite dev server starts and a Chrome window opens. Vitest
keeps watching for file changes and re-runs affected tests; press q in the terminal to quit.
Running individual tests
Section titled “Running individual tests”Converse has many tests, and it can take a while to run through all of them.
When developing on Converse, it’s often preferable to have a more rapid turnaround time between editing a file and checking whether the most relevant tests have passed.
Tests are described by it functions and the test names are written to
be read as plain English sentences that start with the word it.
For example:
it("is rejected if it's an unencapsulated forwarded message",Tests are grouped by describe functions, and contained in test files inside
the tests/ subdirectories of each plugin.
Running specific test files
Section titled “Running specific test files”The fastest way to focus during development is to pass a file path (or path substring) to Vitest:
npx vitest run --project main src/plugins/chatview/tests/messages.jsnpx vitest run --project headless smacks # any file whose path matches "smacks"New test files placed in a tests/ directory are auto-discovered via the include
globs in vitest.config.js — there is no files array to update (as there was with Karma).
Focusing tests with fdescribe / fit
Section titled “Focusing tests with fdescribe / fit”To run only a single test, you can replace it( with fit( for the particular
test that you want to run. To run only a group of tests, replace describe( with
fdescribe(. (The compat shim aliases these to Vitest’s it.only/describe.only.)
Note that under Vitest a focused test only narrows within its own file, so when running
the whole suite, combine fit/fdescribe with a file path (see above).
Always revert fdescribe/fit back to describe/it before committing.
Jasmine compatibility vs. native Vitest APIs
Section titled “Jasmine compatibility vs. native Vitest APIs”The suite was written for Jasmine, so vitest/setup.jasmine-shim.js installs a thin
compatibility layer (spyOn, jasmine.*, fdescribe/fit, the toEqualStanza matcher,
extra matchers like toBeTrue/toHaveSize). That layer exists so the ~540 existing spy
call-sites didn’t have to be rewritten — it is purely additive.
That means new tests can (and should) use native Vitest APIs directly. Both styles work, and can be mixed in the same file, because:
globals: trueinjectsvi,expect,describe,it,beforeEach, … — no imports needed.expectis extended, not replaced, so native matchers (toBe,toEqual,toHaveBeenCalledWith,await expect(...).rejects.toThrow(), …) coexist with the Jasmine ones.spyOn()returns the underlying Vitest mock, so the same spy answers to both the Jasmine facade (spy.and.returnValue(x),spy.calls.count()) and the native API (spy.mockReturnValue(x),expect(spy).toHaveBeenCalledOnce()).
// Jasmine style (existing tests)const s = spyOn(obj, 'm').and.returnValue(5);expect(s.calls.count()).toBe(1);jasmine.clock().install();// Native Vitest (prefer for new tests)const s = vi.spyOn(obj, 'm').mockReturnValue(5);expect(s).toHaveBeenCalledOnce();vi.useFakeTimers();Two caveats that apply regardless of style:
vi.mock()can’t stub Converse internals. Tests import the prebuiltdist/converse.jsbundle, where every internal module is inlined. There’s no separate module path forvi.mockto intercept. Stub at runtime instead:vi.spyOn(converse.env.X, …),vi.spyOn(SomeClass.prototype, …), orvi.stubGlobal('Notification', …).- No
.concurrent. The suite relies on serial execution and shared browser state (fileParallelism: false,isolate: false), sodescribe.concurrent/test.concurrentwould break the cumulative IndexedDB/localStorage assumptions.
Headless tests
Section titled “Headless tests”Converse has a separate test suite in src/headless/tests/ for core XMPP
functionality that doesn’t require UI components:
npm run test:headless # Run headless tests (headless Chromium)npm run test:headless:browser # Run headless tests in a visible Chrome windowThese run as a separate Vitest project (headless) defined in the root
vitest.config.js, rooted at src/headless/ and importing the prebuilt
src/headless/dist/converse-headless.js bundle.
Which suite to run? Tests for plugins under
src/headless/(e.g. smacks, roster, status, presence) live in the headless suite and will not be picked up bynpm test. Usenpm run test:headlesswhen working in that area.npm testonly covers the UI plugins undersrc/plugins/.
Debugging tests
Section titled “Debugging tests”For better debugging, you can:
- Use
npm run test:browserto run tests in a visible Chrome window, then open the browser’s devtools on the Vitest page - Pass a file path to narrow the run to the failing spec (see above)
- Add
debugger;statements in your test code (they pause when devtools is open) - Use
console.log()statements to inspect values - Set
loglevel: 'debug'in test configurations for verbose XMPP stanza logging
Test structure
Section titled “Test structure”Tests follow this pattern:
/*global mock, converse */
const { api } = converse;const u = converse.env.utils;
describe('My Feature', function () { it( 'does something', mock.initConverse(['chatBoxesFetched'], { view_mode: 'fullscreen' }, async function (_converse) { // Test implementation }), );});The mock.initConverse helper sets up a test environment with specific
configuration and waits for certain promises to resolve before running the test.
Thank you to our sponsors
If you'd like to sponsor this project, please visit Github, Patreon, Liberapay or contact us.