Back to blog
Linux & Servers August 25, 2026

Designing Puppeteer and Playwright Scripts for Synthetic Transaction Monitoring

Designing Puppeteer and Playwright Scripts for Synthetic Transaction Monitoring

Uptime checks that only query an endpoint's home page are insufficient for modern single-page applications (SPAs). While your servers might return an HTTP 200 status code, frontend compilation errors, database bottlenecks, or failing authentication APIs can prevent actual users from accessing their dashboards or completing transactions.

To detect functional regressions before customers report them, site reliability engineers (SREs) build Synthetic Transaction Monitors using headless browser frameworks. This guide compares Puppeteer and Playwright for production testing, analyzes browser lifecycle costs, and provides a production-ready script for continuous checks.


1. Puppeteer vs. Playwright for Production Probes

When selecting a browser automation engine for synthetic checks, evaluate multi-browser capabilities, auto-waiting reliability, and execution tracing:

Automation FeaturePuppeteerPlaywrightSRE Monitoring Value
Browser EnginesChromium (primary)Chromium, Firefox, WebKitPlaywright isolates browser-specific render bugs
Auto-WaitingManual (waitForSelector)Automatic (actions wait for readiness)Auto-waiting eliminates timing-related false alerts
Context IsolationSupportedStrong (multi-tenant context models)Parallel browser execution without session leaks
Network TracingSupportedAdvanced HAR / Trace ViewersEnables rapid post-mortem diagnostic triage
Startup OverheadMinimal (Chromium focused)Moderate (features load additional libs)Puppeteer has slightly faster cold-start times

2. Calculating Synthetic Monitoring Cost and Runs

Running headless browsers consumes significant CPU and memory. SRE teams compute the monthly transaction execution budget to manage infrastructure costs before scaling test frequencies.

We calculate the total monthly synthetic runs ((R_{\text{runs}})) using:

[R_{\text{runs}} = C_{\text{hour}} \cdot 24 \cdot D \cdot L]

Where:

  • (C_{\text{hour}}): Number of checks executed per hour.
  • (D): Total days in the billing month (typically 30).
  • (L): Number of geographic monitor locations.

For a transaction check running every 5 minutes ((C_{\text{hour}} = 12)) from 5 global locations ((L = 5)), the system runs (43,200) browser operations monthly:

[R_{\text{runs}} = 12 \cdot 24 \cdot 30 \cdot 5 = 43,200\text{ monthly runs}]

We calculate the availability rate of our synthetic transaction ((A_{\text{synthetic}})) using:

[A_{\text{synthetic}} = \frac{\text{Successful Transactions}}{\text{Total Valid Runs}} \cdot 100]


3. SRE Synthetic Latency Threshold Matrix

To set alerts that catch real regressions without triggering alert fatigue, use specific thresholds based on user journey milestones:

Execution SegmentHealthy (Optimal)Warning LevelCritical Action Target
DNS Resolution(< 100\text{ ms})(100\text{ ms} - 300\text{ ms})(> 300\text{ ms})
TCP Connect(< 150\text{ ms})(150\text{ ms} - 400\text{ ms})(> 400\text{ ms})
TLS Handshake(< 300\text{ ms})(300\text{ ms} - 800\text{ ms})(> 800\text{ ms})
HTML Document TTFB(< 500\text{ ms})(500\text{ ms} - 1,000\text{ ms})(> 1,000\text{ ms})
Total Transaction(< 2.0\text{ s})(2.0\text{ s} - 5.0\text{ s})(> 5.0\text{ s})
Script Error Rate(< 0.1%)(0.1% - 1.0%)(> 1.0%)

4. Production-Ready Playwright Synthetic Script

This script manages browser contexts defensively, uses stable semantic selectors, intercepts network events to verify API health, and records diagnostic data on failure.

import { chromium } from "playwright";

const runSyntheticCheck = async () => {
  const browser = await chromium.launch({ headless: true });
  // Establish isolated context to prevent session leaks
  const context = await browser.newContext({
    locale: "en-US",
    timezoneId: "UTC",
    viewport: { width: 1280, height: 720 }
  });

  const page = await context.newPage();
  const startTime = performance.now();

  try {
    // 1. Load the target entry page
    await page.goto("https://pingzoapp.com/login", {
      waitUntil: "domcontentloaded",
      timeout: 15000
    });

    // 2. Perform authentication with safe locator selectors
    await page.getByLabel("Email").fill(process.env.SYNTHETIC_USER_EMAIL || "");
    await page.getByLabel("Password").fill(process.env.SYNTHETIC_USER_PASS || "");
    await page.getByRole("button", { name: /sign in/i }).click();

    // 3. Assert on critical dashboard elements
    await page.getByRole("heading", { name: /dashboard/i })
      .waitFor({ state: "visible", timeout: 10000 });

    const durationMs = performance.now() - startTime;
    console.log(JSON.stringify({
      status: "success",
      duration_ms: Math.round(durationMs),
      timestamp: new Date().toISOString()
    }));
    
    process.exitCode = 0;
  } catch (error) {
    console.error(JSON.stringify({
      status: "failure",
      error: error.message,
      timestamp: new Date().toISOString()
    }));

    // Record screenshot immediately to isolate layout or DOM errors
    await page.screenshot({ path: "tmp/synthetic-failure.png", fullPage: true });
    process.exitCode = 1;
  } finally {
    // Close context and browser process to prevent orphaned Chromium processes
    await context.close();
    await browser.close();
  }
};

runSyntheticCheck();

[!TIP] Tip (Budget Audit): Use the SLA Calculator to convert target availability percentages (like (99.9%) or (99.95%)) into monthly allowed downtime. Map your synthetic failure thresholds to these downtime values to prevent transient browser latency from wasting your SLA budgets.


5. Troubleshooting Synthetic Monitoring Failures

If your synthetic checks report failures or latency anomalies, use this troubleshooting playbook:

  1. Examine failure screenshots: Check the captured images to determine if the page failed to render, showed a database error page, or displayed a validation modal.
  2. Isolate the execution phase: Identify whether the failure occurred during browser launch, page navigation, locator matching, or DOM state assertions.
  3. Trace network responses: Intercept browser requests to locate failing API endpoints:
    page.on('response', response => {
      if (response.status() >= 400) {
        console.log(`Failed Endpoint: ${response.url()} (Status: ${response.status()})`);
      }
    });
    
  4. Detect browser process leaks: Verify that your runner environment does not accumulate orphaned Chromium processes that exhaust server memory.
  5. Evaluate container limits: Check if host memory or CPU throttling is slowing down browser launch times, causing false timeouts.
  6. Confirm credential validity: Verify that test account credentials have not expired or been locked by rate-limiting rules.
  7. Review locator definitions: Avoid generated CSS class names. Ensure scripts use stable ARIA roles or explicit data-testid attributes.
  8. Verify quorum policies: Configure alerts to page on-call engineers only after consecutive failures occur across multiple geographic monitoring regions.
  9. Analyze browser trace files: Export Playwright traces (trace.zip) to replay execution steps and locate rendering bottlenecks.
Try Pingzo Free

Know before your users do

Connect official WhatsApp notification channels, Discord webhooks, Telegram bots, and public status pages. Start in 30 seconds.

Create Free Monitor