tutorial

Monitoring Vitest Pipelines with Vigilmon: Know When Your Fast Tests Stop Running

Use Vigilmon heartbeat monitors to detect when Vitest test suites silently stop running — CI drift, scheduled test failures, and pipelines that go dark without a single error message.

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 include glob pattern change that accidentally matches zero files
  • A CI environment variable typo that breaks the test command before Vitest starts
  • A --project filter 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

  1. Log in to Vigilmon and click New Monitor → Heartbeat.
  2. Name it: Vitest Unit — main or Vitest Integration — nightly.
  3. Set the Expected interval to match your pipeline. Push-triggered suites: 2 hours. Nightly suites: 25 hours.
  4. Set the Grace period to 20 minutes for fast push suites, 1 hour for nightly jobs.
  5. Save and copy the Ping URLhttps://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 #engineering or #ci channel

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.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →