This document explains how code coverage instrumentation works in Rocket.Chat's build and CI pipeline.
Coverage is collected during E2E test runs (API, UI, Livechat) to measure how much of the server-side code is exercised by tests. The instrumentation uses Istanbul-compatible tooling, which injects counters (__coverage__) into the compiled code at build time.
The coverage pipeline has three components:
- Build-time instrumentation - injects coverage counters into the code during Meteor build
- Runtime collection - the
rocketchat:coverageMeteor package collects__coverage__data on process exit - CI reporting - test workflows merge coverage data and upload reports
Build (SWC + plugin) --> Run tests --> Process exit triggers report --> Merge & upload
Rocket.Chat uses Meteor's modern build stack with SWC as the transpiler. For coverage builds, the swc-plugin-coverage-instrument plugin is injected into .swcrc at build time.
This is configured in .github/actions/meteor-build/action.yml:
env:
BABEL_ENV: ${{ inputs.type }} # "production" or "coverage"When BABEL_ENV=coverage, the build script:
- Adds
rocketchat:coverageto.meteor/packages - Injects
swc-plugin-coverage-instrumentinto.swcrcvia a node script:
const swcrc = JSON.parse(fs.readFileSync('./apps/meteor/.swcrc', 'utf8'));
swcrc.jsc.experimental = swcrc.jsc.experimental || {};
swcrc.jsc.experimental.plugins = swcrc.jsc.experimental.plugins || [];
swcrc.jsc.experimental.plugins.push(['swc-plugin-coverage-instrument', {}]);
fs.writeFileSync('./apps/meteor/.swcrc', JSON.stringify(swcrc, null, 2) + '\n');This approach ensures the same build pipeline (SWC) is used for both regular and coverage builds, avoiding behavioral differences between build modes.
Before the modern build stack, coverage was handled via babel-plugin-istanbul configured in .babelrc:
{
"env": {
"coverage": {
"plugins": [
["istanbul", { "exclude": ["**/*.spec.js", "**/*.test.js"] }]
]
}
}
}This section is still present in .babelrc as a fallback for files that fall back to Babel compilation (e.g., SWC-incompatible code).
The rocketchat:coverage Meteor package (apps/meteor/packages/rocketchat-coverage/) is only added to the build during coverage runs. It:
- Registers a
process.on('exit')handler - Reads
globalThis['__coverage__'](populated by the instrumentation) - Generates a coverage report using
istanbul-lib-coverageandistanbul-reports
Configuration via environment variables:
| Variable | Description | Example |
|---|---|---|
COVERAGE_DIR |
Output directory for reports | /tmp/coverage/api |
COVERAGE_FILE_NAME |
Report filename | api-1.json |
COVERAGE_REPORTER |
Istanbul reporter format | json, lcov |
Coverage is collected in the ci-test-e2e.yml workflow:
- Build:
meteor-buildaction runs withtype: coverage, producing a Docker image with instrumented code - Test: E2E tests run against the instrumented server. On each test shard:
COVERAGE_DIR,COVERAGE_FILE_NAME, andCOVERAGE_REPORTERare set- When the Rocket.Chat process exits after tests, the coverage plugin writes a JSON report
- Merge:
nyc mergecombines per-shard JSON reports into a single coverage file - Upload: Coverage data is uploaded to Codecov
To build with coverage locally:
cd apps/meteor
# Inject the SWC coverage plugin into .swcrc
node -e "
const fs = require('fs');
const swcrc = JSON.parse(fs.readFileSync('.swcrc', 'utf8'));
swcrc.jsc.experimental = { plugins: [['swc-plugin-coverage-instrument', {}]] };
fs.writeFileSync('.swcrc', JSON.stringify(swcrc, null, 2));
"
# Add the coverage package
echo -e "rocketchat:coverage\n" >> .meteor/packages
# Set env vars and run
COVERAGE_DIR=/tmp/coverage COVERAGE_FILE_NAME=local.json COVERAGE_REPORTER=lcov yarn devRemember to restore .swcrc and .meteor/packages after testing.
| Package | Purpose |
|---|---|
swc-plugin-coverage-instrument |
SWC plugin for Istanbul-compatible instrumentation |
istanbul-lib-coverage |
Coverage map creation (used by rocketchat:coverage) |
istanbul-lib-report |
Report context creation |
istanbul-reports |
Report formatters (json, lcov, etc.) |