Vitest is fast — blazingly fast. Your unit test suite completes in under a second. That speed is a double-edged sword: when Vitest stops running entirely, nobody notices right away because the absence of a fast result looks a lot like the absence of anything at all.
A misconfigured workspace glob, a broken CI secret, or an accidentally disabled schedule trigger can silence your Vitest suite completely. No failures. No alerts. Just silence — until a regression ships.
This tutorial shows you how to instrument Vitest pipelines with Vigilmon heartbeat monitors so silence itself becomes an alert signal.
What You'll Cover
- Heartbeat monitoring for Vitest suites
- Using Vitest's global setup/teardown hooks for reliable ping delivery
- Detecting suites that stop running or running zero tests
- Integrating with Vite projects and CI/CD pipelines
- Multi-workspace and multi-project monitoring
Prerequisites
- A project using Vitest (any Vite-based or standalone setup)
- A free account at vigilmon.online
The Problem: What Vitest's Reporter Doesn't Cover
Vitest's reporters (dot, verbose, JSON) are excellent at describing test results. They can't tell you about runs that never happen:
- A
includeglob pattern change that accidentally matches zero files - A CI environment variable typo that breaks the test command before Vitest starts
- A
--projectfilter that narrows the run to an empty set - Scheduled Vitest runs that GitHub quietly stopped triggering
- A Vite config error that fails the build phase before tests execute
Vigilmon's heartbeat pattern catches all of these by requiring a positive "I finished" signal — not just the absence of a failure signal.
Step 1: Create a Heartbeat Monitor in Vigilmon
- Log in to Vigilmon and click New Monitor → Heartbeat.
- Name it:
Vitest Unit — mainorVitest Integration — nightly. - Set the Expected interval to match your pipeline. Push-triggered suites: 2 hours. Nightly suites: 25 hours.
- Set the Grace period to 20 minutes for fast push suites, 1 hour for nightly jobs.
- Save and copy the Ping URL —
https://vigilmon.online/api/heartbeat/<unique-id>.
Step 2: Add a Global Teardown to Ping on Success
Vitest supports globalSetup and globalTeardown hooks in vitest.config.ts. Create a teardown file:
// vitest.teardown.ts
import https from "node:https";
export async function teardown() {
const heartbeatUrl = process.env.VIGILMON_HEARTBEAT_URL;
if (!heartbeatUrl) {
return;
}
await new Promise<void>((resolve, reject) => {
const url = new URL(heartbeatUrl);
const req = https.request(
{
hostname: url.hostname,
path: url.pathname + url.search,
method: "POST",
timeout: 10_000,
},
(res) => {
res.resume();
resolve();
}
);
req.on("error", reject);
req.on("timeout", () => {
req.destroy();
reject(new Error("Vigilmon heartbeat timed out"));
});
req.end();
});
}
Register it in vitest.config.ts:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globalSetup: [],
// Vitest's globalSetup array accepts teardown exports
globalSetup: ["./vitest.teardown.ts"],
},
});
How this works: Vitest's globalSetup modules can export both setup() and teardown() functions. The teardown() export is called after all tests complete — but only if Vitest exits cleanly with no failures. A failed test suite exits non-zero and skips teardown, which means no ping reaches Vigilmon.
Step 3: Integrate with GitHub Actions
# .github/workflows/test.yml
name: Vitest
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "30 2 * * *" # nightly 02:30 UTC
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Vitest
env:
VIGILMON_HEARTBEAT_URL: ${{ secrets.VIGILMON_VITEST_HEARTBEAT_URL }}
run: npx vitest run --reporter=verbose
Store the secret in GitHub → Settings → Secrets and variables → Actions → New repository secret:
- Name:
VIGILMON_VITEST_HEARTBEAT_URL - Value: ping URL from Step 1
Alternatively, handle the ping at the workflow level for simpler setups:
- name: Run Vitest
run: npx vitest run --reporter=verbose
- name: Ping Vigilmon heartbeat
if: success()
run: |
curl -fsS -X POST "${{ secrets.VIGILMON_VITEST_HEARTBEAT_URL }}" \
--max-time 10 \
--retry 3 \
--retry-delay 2
Step 4: Vitest Workspace Monitoring
Vitest's workspace feature lets you run multiple projects (e.g., a client and server package) in one command. You may want separate heartbeat monitors per workspace project:
// vitest.workspace.ts
import { defineWorkspace } from "vitest/config";
export default defineWorkspace([
{
test: {
name: "client",
include: ["packages/client/**/*.test.ts"],
globalSetup: ["./vitest.teardown.client.ts"],
environment: "jsdom",
},
},
{
test: {
name: "server",
include: ["packages/server/**/*.test.ts"],
globalSetup: ["./vitest.teardown.server.ts"],
environment: "node",
},
},
]);
Each teardown file reads a different env var:
// vitest.teardown.client.ts
export async function teardown() {
await pingHeartbeat(process.env.VIGILMON_VITEST_CLIENT_HEARTBEAT_URL);
}
// vitest.teardown.server.ts
export async function teardown() {
await pingHeartbeat(process.env.VIGILMON_VITEST_SERVER_HEARTBEAT_URL);
}
In CI, pass both secrets:
- name: Run Vitest workspaces
env:
VIGILMON_VITEST_CLIENT_HEARTBEAT_URL: ${{ secrets.VIGILMON_VITEST_CLIENT_HEARTBEAT_URL }}
VIGILMON_VITEST_SERVER_HEARTBEAT_URL: ${{ secrets.VIGILMON_VITEST_SERVER_HEARTBEAT_URL }}
run: npx vitest run --workspace=vitest.workspace.ts
Step 5: Integrate with GitLab CI
# .gitlab-ci.yml
vitest:unit:
image: node:20-alpine
stage: test
script:
- npm ci
- npx vitest run --reporter=verbose
after_script:
- |
if [ "$CI_JOB_STATUS" = "success" ]; then
curl -fsS -X POST "$VIGILMON_VITEST_HEARTBEAT_URL" --max-time 10 --retry 3
fi
only:
- main
- merge_requests
- schedules
Set VIGILMON_VITEST_HEARTBEAT_URL in GitLab → Settings → CI/CD → Variables (mark it as masked).
Step 6: Catch Zero-Test Runs
Vitest exits 0 when no files match the include pattern (unless you configure it otherwise). This means a misconfigured glob silently passes CI.
Add a guard to your test script:
{
"scripts": {
"test": "vitest run --passWithNoTests=false"
}
}
--passWithNoTests=false (the default in strict mode) causes Vitest to exit with an error when no test files are found, which prevents the teardown from firing and blocks the heartbeat ping. Vigilmon alerts you.
Alternatively, enforce a minimum test count in your teardown:
// vitest.teardown.ts
import { expect } from "vitest";
export async function teardown(context: { testResults: any }) {
const total = context.testResults?.reduce(
(sum: number, r: any) => sum + r.result?.testResults?.length ?? 0,
0
) ?? 0;
if (total === 0) {
throw new Error("Zero tests ran — not pinging heartbeat");
}
await pingHeartbeat(process.env.VIGILMON_HEARTBEAT_URL);
}
Step 7: Alert Channels
In Vigilmon, go to Notifications → New Channel and configure:
- Email — immediate alert when a heartbeat is missed
- Slack webhook — ping your
#engineeringor#cichannel
When Vitest stops pinging:
🔴 MISSED HEARTBEAT: Vitest Unit — main
Last ping: 2 hours 48 minutes ago
Expected interval: 2 hours
When it resumes:
✅ HEARTBEAT RECOVERED: Vitest Unit — main
Gap: 2 hours 48 minutes
What You're Now Catching
| Silent failure mode | How Vigilmon detects it |
|---|---|
| include glob matches zero files | Vitest exits (0 or 1 depending on config) — configure --passWithNoTests=false to block ping |
| CI job skipped by branch filter | No ping within interval → alert |
| Vite config error before tests start | Process exits non-zero → no teardown → no ping |
| Scheduled suite disabled or skipped | No ping → alert after grace period |
| Test suite fails (non-zero exit) | Teardown skipped → no ping → alert |
| Workspace project silently excluded | Per-project teardown doesn't fire → alert |
Vitest is fast enough that you might not notice when it stops running. Vigilmon gives you the confirmation signal you need: an explicit heartbeat after every successful run, and an alert when that heartbeat goes quiet.
Start monitoring your Vitest pipelines today — register free at vigilmon.online.