tutorial

Monitoring Playwright Test Suites with Vigilmon: Catch Silent Automation Failures

Use Vigilmon heartbeat monitors to detect silent Playwright failures — scheduled test suite drift, stuck browser workers, and test runs that stop executing entirely.

Your Playwright smoke suite ran on every deployment for two months, then silently stopped when a GitHub Actions runner hit its billing limit. The alert only came when a critical payment flow broke in production three days later. Playwright had no errors to surface — it just wasn't being run.

This is the silent test automation failure problem. HTTP monitors can't help because there's no endpoint to poll for test completion. The fix is heartbeat monitoring: Playwright pings a unique URL at the end of every successful run, and if Vigilmon doesn't receive that ping within the expected window, you get alerted.

This tutorial shows you how to instrument Playwright with Vigilmon heartbeat monitors so you always know when your test automation stops running.

What You'll Cover

  • Heartbeat monitoring for scheduled Playwright test runs
  • Catching test pipelines that stop triggering entirely
  • Alerting on stuck or hanging browser workers
  • Multi-project and multi-browser heartbeat monitoring
  • Detecting test drift in CI/CD environments

Prerequisites

  • A Playwright test suite (@playwright/test v1.20+)
  • A free account at vigilmon.online

The Problem: What Standard Playwright Monitoring Misses

Playwright's built-in reporters track test results — but they won't alert you when:

  • The CI job invoking Playwright is accidentally removed or disabled
  • A shard configuration change causes some browsers to be skipped silently
  • A playwright.config.ts change sets testMatch to an empty pattern, running zero tests
  • Browser download failures cause the runner to exit 0 with no tests executed
  • The scheduled pipeline pauses without any visible error

Vigilmon's heartbeat pattern closes all of these gaps.


Step 1: Create a Heartbeat Monitor in Vigilmon

A heartbeat monitor expects a ping on a regular interval. No ping → alert.

  1. Log in to Vigilmon and click New Monitor → Heartbeat.
  2. Give it a name like Playwright — Nightly Regression.
  3. Set the Expected interval to match your test schedule. For a nightly run, use 24 hours. For pre-merge smoke tests, use 2 hours with a 20-minute grace period.
  4. Set the Grace period to 1 hour for long cross-browser suites.
  5. Save and copy the Ping URL — it looks like https://vigilmon.online/api/heartbeat/<unique-id>.

Step 2: Add a Global Teardown to Playwright

Playwright's globalTeardown hook runs after all tests complete and receives the full results object — the ideal place for a heartbeat ping:

// playwright/global-teardown.ts
import { FullResult } from "@playwright/test/reporter";
import * as https from "https";

function pingVigilmon(url: string): Promise<void> {
  return new Promise((resolve, reject) => {
    const req = https.request(url, { method: "POST" }, () => resolve());
    req.on("error", reject);
    req.setTimeout(10000, () => {
      req.destroy(new Error("Vigilmon ping timed out"));
    });
    req.end();
  });
}

async function globalTeardown(config: unknown, result: FullResult) {
  const heartbeatUrl = process.env.VIGILMON_HEARTBEAT_URL;
  if (!heartbeatUrl) return;

  // Only ping when every test passed
  if (result.status === "passed") {
    try {
      await pingVigilmon(heartbeatUrl);
      console.log("Vigilmon heartbeat sent");
    } catch (e) {
      console.error("Vigilmon heartbeat failed:", (e as Error).message);
    }
  }
}

export default globalTeardown;

Register it in playwright.config.ts:

// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  globalTeardown: require.resolve("./playwright/global-teardown"),

  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
  ],

  reporter: [["html", { open: "never" }], ["list"]],
});

The result.status is "passed" only when every test across every project passes — failures, timeouts, or interrupted runs suppress the ping.


Step 3: Run Playwright with the Heartbeat Environment Variable

Local and shell

VIGILMON_HEARTBEAT_URL="https://vigilmon.online/api/heartbeat/<id>" \
  npx playwright test

GitHub Actions

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  schedule:
    - cron: "0 1 * * *"  # 01:00 UTC nightly
  push:
    branches: [main]

jobs:
  playwright:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        env:
          VIGILMON_HEARTBEAT_URL: ${{ secrets.VIGILMON_PW_HEARTBEAT_URL }}
        run: npx playwright test

      # Heartbeat fires automatically via globalTeardown on full success.

Store VIGILMON_PW_HEARTBEAT_URL under Settings → Secrets and variables → Actions.

GitLab CI

playwright:nightly:
  stage: test
  image: mcr.microsoft.com/playwright:v1.44.0-jammy
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
  variables:
    VIGILMON_HEARTBEAT_URL: $VIGILMON_PW_HEARTBEAT_URL
  script:
    - npm ci
    - npx playwright test

Step 4: Monitor Individual Playwright Projects

For large suites spanning multiple browsers and devices, create one heartbeat per project:

// playwright/global-teardown.ts (multi-project version)
async function globalTeardown(config: unknown, result: FullResult) {
  if (result.status !== "passed") return;

  const projectHeartbeats: Record<string, string | undefined> = {
    chromium: process.env.VIGILMON_CHROMIUM_HEARTBEAT_URL,
    firefox: process.env.VIGILMON_FIREFOX_HEARTBEAT_URL,
    webkit: process.env.VIGILMON_WEBKIT_HEARTBEAT_URL,
  };

  await Promise.allSettled(
    Object.entries(projectHeartbeats)
      .filter(([, url]) => !!url)
      .map(([name, url]) =>
        pingVigilmon(url!).then(() =>
          console.log(`Vigilmon heartbeat sent for ${name}`)
        )
      )
  );
}

Map monitors in Vigilmon:

| Project | Monitor name | Interval | |---|---|---| | chromium (nightly) | Playwright — Chrome — Nightly | 25 h | | firefox (nightly) | Playwright — Firefox — Nightly | 25 h | | webkit (nightly) | Playwright — Safari — Nightly | 25 h | | Mobile Chrome (smoke) | Playwright — Mobile — Smoke | 4 h |


Step 5: Custom Reporter for Per-Test Heartbeats

For high-frequency monitoring needs, write a custom Playwright reporter that pings after specific critical test files pass:

// playwright/vigilmon-reporter.ts
import type {
  Reporter,
  FullConfig,
  Suite,
  TestCase,
  TestResult,
  FullResult,
} from "@playwright/test/reporter";
import * as https from "https";

class VigilmonReporter implements Reporter {
  private criticalTests = new Map<string, boolean>();
  private heartbeatUrls: Record<string, string> = {};

  onBegin(config: FullConfig, suite: Suite) {
    // Map test file → heartbeat URL via env vars
    this.heartbeatUrls = {
      "checkout.spec.ts": process.env.VIGILMON_CHECKOUT_HEARTBEAT || "",
      "auth.spec.ts": process.env.VIGILMON_AUTH_HEARTBEAT || "",
    };
  }

  onTestEnd(test: TestCase, result: TestResult) {
    const file = test.location.file.split("/").pop() || "";
    if (this.heartbeatUrls[file]) {
      const passed = result.status === "passed";
      this.criticalTests.set(file, (this.criticalTests.get(file) ?? true) && passed);
    }
  }

  async onEnd(result: FullResult) {
    const pings = Object.entries(this.heartbeatUrls)
      .filter(([file, url]) => url && this.criticalTests.get(file) === true)
      .map(([, url]) => this.ping(url));

    await Promise.allSettled(pings);
  }

  private ping(url: string): Promise<void> {
    return new Promise((resolve) => {
      const req = https.request(url, { method: "POST" }, () => resolve());
      req.on("error", () => resolve());
      req.end();
    });
  }
}

export default VigilmonReporter;

Register in config:

reporter: [
  ["list"],
  ["./playwright/vigilmon-reporter.ts"],
],

Step 6: Alert Channels

In Vigilmon, go to Notifications → New Channel and configure:

  • Email — immediate alert when a heartbeat is missed
  • Slack webhook — ping your #playwright-alerts or #qa channel

When a Playwright suite stops pinging:

🔴 MISSED HEARTBEAT: Playwright — Nightly Regression
Last ping: 25 hours ago
Expected interval: 24 hours

When it recovers:

✅ HEARTBEAT RECOVERED: Playwright — Nightly Regression
Gap: 25 hours

What You're Now Catching

| Silent failure mode | How Vigilmon detects it | |---|---| | CI job invoking Playwright disabled | No ping arrives → alert after grace period | | testMatch glob returns zero specs | No ping on 0-test run → alert | | Browser binary download fails silently | No tests run → no ping → alert | | Runner machine hits billing/quota limit | No ping → alert after grace period | | Shard configuration skips entire browser | No ping for that project → alert | | Suite hangs on a network-flaky test | Runner timeout → no ping → alert |


Playwright is fast, reliable, and cross-browser — until it quietly stops being invoked. Vigilmon's heartbeat monitors give you the external signal that Playwright itself can't provide: a definitive alert when your test automation hasn't run in longer than expected.

Add heartbeat monitoring to your Playwright suite 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 →