Testland
Browse all skills & agents

synthetic-monitor-author

Drafts a synthetic monitor configuration for one critical user journey - picks the platform (Datadog Synthetics, Pingdom, Checkly, New Relic, etc.), authors the scripted-transaction body (Playwright-style for browser checks; HTTP-step for API checks), wires the cadence (typical 1-15 min), defines per-step assertions (DOM presence, API status, response shape) and aggregate alert thresholds (consecutive-failure count + on-call routing). Includes the RUM-coverage gap method for deciding which journeys to monitor: score real-user journeys from RUM / CrUX data by session volume times business value, diff against the existing monitor inventory, and emit a ranked gap list. Use when a critical journey needs continuous-in-production verification per ISTQB-canonical shift-right ("a test approach to test a system continuously in production"), or when synthetic coverage was never systematically derived from real usage data.

Install with skills.sh (any agent)

npx skills add testland/qa --skill synthetic-monitor-author
View source

synthetic-monitor-author

Overview

Synthetic monitoring is "a monitoring technique that is done by using a simulation or scripted recordings of transactions" (synthetic-mon-wiki (opens in new window)); the scripts "run continuously at set intervals to measure performance metrics like functionality, availability, and response time - without requiring actual traffic." Per the ISTQB Glossary V4.7.1, shift right is "a test approach to test a system continuously in production," and synthetic monitors are its load-bearing primitive. This skill builds the configuration: which journey, how often, what to assert, when to page.

When to use

  • A critical user journey needs production-side coverage that doesn't depend on real user traffic (low-traffic SaaS, pre-launch, off-peak verification).
  • A SLO depends on a specific user-facing flow being available; the monitor is the SLO-evidence source.
  • An incident postmortem identified "we should have caught this in production faster" - the monitor is the prevention.
  • A regulatory requirement (uptime SLA, healthcare availability) needs continuous active verification.

If real-user traffic is high and well-instrumented, real-user monitoring (RUM) is the complement.

Step 1 - Pick the journey

Synthetic monitors should target the highest-business-value journey the team would page on at 3am if it broke. Examples:

  • E-commerce: search → add to cart → checkout → confirmation.
  • SaaS: log in → access primary feature → save change.
  • Financial: authenticate → fetch account balance → return.
  • Healthcare: log in → view a patient record → log out.

Target commonly used paths and critical business processes. Don't monitor every flow - pick the 3-5 hero flows that map to the team's SLOs.

Which journeys to monitor - the RUM-coverage gap method

Synthetic monitors verify journeys the team chose to script; Real User Monitoring records journeys users actually take. The gap between the two sets is where production breakage goes undetected: a journey with 40 k sessions per day but no monitor can fail silently for hours. When RUM is instrumented, derive the journey list from it instead of gut feel:

  1. Collect the RUM journey inventory. Pull the top ~50 view paths (or transaction names) by session volume from Datadog RUM, Sentry Performance, or GA4 + CrUX. Per-source queries are in references/rum-source-queries.md.

  2. Score each journey: coverage_priority = session_volume_score x business_value_score, each on a 1-5 scale (range 1-25):

    ScoreDaily sessionsJourney type (business value)
    5> 10 kRevenue-generating (checkout, upgrade); authentication (login, SSO, MFA)
    41 k - 10 kPrimary feature (core read/write); onboarding
    3100 - 1 kSupport / self-service (docs, status)
    210 - 100Informational (marketing pages, help)
    1< 10Admin / internal tooling

    The business-value column is editorial - align it with product stakeholders before the first run and record the agreed values.

  3. Build the existing-monitor inventory. Datadog: GET /api/v1/synthetics/tests; Checkly: monitors/*.spec.ts + *.yml in the repo; New Relic: GET /v2/monitors.json. Normalize each monitor to a canonical URL path pattern (strip query strings, replace ID segments with {id}, lowercase).

  4. Diff and rank. Journeys whose normalized path matches no monitor pattern form the gap list, sorted by coverage_priority descending. Emit one row per gap: path, sessions/day, business value, score, recommended monitor type (score >= 20 with interactions: browser check; pure API endpoint: API check; score < 10: defer - monitor sprawl costs more than the coverage is worth).

  5. Feed the ranked gap list back into this step as the journey input.

Hard-reject rule: no RUM source, no gap analysis. If neither Datadog RUM, Sentry Performance, nor CrUX data exists for the target, halt and say so. Do not estimate journey volume from a sitemap - it contains every URL, not the ones users visit, and produces a monitor list biased by developer assumptions. Two data caveats: CrUX only captures publicly discoverable pages (use Datadog RUM or Sentry for post-login journeys), and Datadog RUM session retention is 30 days, so pick a representative date range.

Step 2 - Pick the platform

PlatformNotes
Datadog SyntheticsNamed provider. Browser + API. Good for teams already on Datadog APM.
ChecklyPlaywright-native browser checks; API checks; CI-as-code via checkly CLI.
PingdomMature; well-known; uptime + transaction.
New Relic SyntheticsSynthetics-as-Code via JS scripts.
AWS CloudWatch SyntheticsSelenium-based; fits AWS-native stacks.
Smokescreen (open-source)Self-hosted; for compliance-restricted environments.
F5 Distributed Cloud SyntheticNamed provider; browser + API.

The platform decision typically follows the existing observability stack (Datadog APM → Datadog Synthetics; New Relic → New Relic Synthetics).

Step 3 - Author the script (browser check)

For browser checks, Playwright-style is the de-facto standard (Checkly natively, Datadog Synthetics increasingly). Drive the journey step by step with accessibility-first locators, then assert a confirmation state:

// monitors/checkout-journey.spec.ts (Checkly-style, excerpt)
import { test, expect } from '@playwright/test';

test('checkout journey - happy path', async ({ page }) => {
  await page.goto('https://example.com/');
  await page.getByRole('textbox', { name: 'Search' }).fill('BOOK-001');
  // ...search, add to cart, sign in with a synthetic account,
  //    place order with a test-mode card...
  await expect(page.getByRole('heading', { name: /Order confirmed/i })).toBeVisible();
});

Full browser and API templates: references/monitor-templates.md.

Use accessibility-first locators (not CSS classes); synthetic monitors that depend on CSS classes break on every UI refactor.

Critical: synthetic monitors hit production with real APIs. Use dedicated synthetic test accounts (not real customer data) and test-mode payment processors so the script doesn't trigger real charges / orders.

Step 4 - Author the script (API check)

For API checks, HTTP-step format chains requests and asserts on each step - status code, response shape, and response time:

# monitors/api-orders-flow.yml (Checkly-style, excerpt)
- name: 2. List orders
  method: GET
  url: https://api.example.com/orders
  headers: { Authorization: "Bearer {{TOKEN}}" }
  assertions:
    - { source: STATUS_CODE, comparison: EQUALS, target: 200 }
    - { source: RESPONSE_TIME, comparison: LESS_THAN, target: 500 }
    - { source: JSON_BODY, property: $.orders, comparison: IS_ARRAY }

Full multi-step auth + list + fetch template: references/monitor-templates.md.

Per-step assertions distinguish "the API returned" from "the API returned the right thing" - distinguish status code, response shape, and response time.

Step 5 - Cadence

Default: 5 min - matches most user journeys and fits within a 99.9% uptime SLO budget (5-min monitor with 2-failure alert rule gives ~10 min to detection, well within ~9 hours/year of allowed downtime). Use 1 min for the highest-criticality flows (auth, payment, primary read) or when the SLO is 99.99%+. Use 15 min for expensive E2E browser checks. Use 1 hour for transactions that have side effects. Use daily for compliance / audit verification flows.

CadenceUse
1 minHighest-criticality flows (auth, payment, primary read).
5 minMost user journeys (default).
15 minLower-priority or expensive (full E2E browser checks).
1 hourSynthetic transactions that have side effects (only as a sanity check).
DailyCompliance / audit verification flows.

Match the cadence to the SLO.

Step 6 - Alert thresholds

A single failure isn't an alert; a single failure is noise. Pattern:

  • Page if N consecutive failures (typical N = 2 or 3).
  • Page if M-of-K window (e.g., 3 of last 5 failed) - catches flapping monitors.
  • Per-region: alert per geographic region; a single-region failure is often a CDN issue, not the application.
  • Per-step: distinguish "the journey failed at step 1 (login)" from "the journey failed at step 4 (checkout)" - different on-call routing.
# Alert config (Checkly-style)
alerts:
  channels:
    - id: pagerduty-checkout
      filters:
        steps: [4, 5]   # only checkout/confirmation steps
    - id: slack-eng
      filters:
        consecutiveFailures: 1   # any failure → Slack notify
  escalation:
    runBased: true
    consecutiveFailures: 2
    cooldownPeriod: 1h

Step 7 - Locations

Run from multiple geographic regions (3-5 minimum):

  • us-east, us-west, eu-west, ap-southeast, sa-east.

Response time varies dramatically by region; multi-region monitoring catches CDN / DNS / TLS issues that single-region misses.

Step 8 - As-code lifecycle

Treat monitors as code:

monitors/
├── checkout-journey.spec.ts       # browser check
├── api-orders-flow.yml             # API check
├── auth-flow.spec.ts
├── checkly.config.ts               # global config
└── README.md

CI pipeline (Checkly example):

- run: npm ci
- run: npx checkly test --reporter ci   # smoke check before deploy
- run: npx checkly deploy --force        # push the configs

Versioning the monitors in git means: PR review on changes, rollback if a monitor becomes flaky after a change, audit trail for why a monitor was added / removed.

Anti-patterns

Anti-patternWhy it failsFix
Real customer data in synthetic monitorsPII leakage; real charges; data corruption.Dedicated synthetic test accounts (Step 3).
Production payments triggered by monitorsReal charges every minute add up; refunds are a nightmare.Test-mode payment processor in production (Step 3).
Single-region monitoringCDN / DNS / TLS / regional issues invisible.3-5 regions (Step 7).
Page on first failureFlake = page; on-call burnout.N consecutive failures (Step 6).
Single one-step alert for the whole journey"Checkout failed" - but where? Triage takes longer than fix.Per-step alerts (Step 6).
Brittle CSS-class selectors in browser checksMonitor breaks on every UI refactor; team disables.Accessibility-first locators (Step 3).
Monitor that asserts only status_code = 200"200 OK" with empty body / wrong shape passes; bug ships.Assert response shape too (Step 4).
One-hour cadence on a 99.99% SLOSLO breach detected after the budget is gone.Cadence matches SLO (Step 5 table).

Limitations

  • Production load. Synthetic monitors generate traffic; at very small scale this matters (10 monitors × 1-min cadence = 14,400 requests/day per monitor).
  • Doesn't cover real-user diversity. Synthetic monitors test what they're scripted to test; real users find what no one scripted. Pair with RUM.
  • Maintenance burden. Monitors need updating when the product changes; broken monitors page on-call without product impact.
  • Per-platform proprietary scripting. Datadog Synthetics scripts aren't Checkly scripts; lock-in is real. Prefer platforms that support Playwright-style scripts (more portable).

References

  • synthetic-mon-wiki (opens in new window) - Synthetic monitoring definition, active vs proactive vs real-user monitoring distinction, common metrics (Time to First Byte, Speed Index, Time to Interactive, Page Complete), named providers (Datadog, F5).
  • references/rum-source-queries.md - per-source queries for the RUM-coverage gap method (Datadog RUM Explorer, Sentry Performance throughput, GA4 + CrUX field data), with the Datadog retention and CrUX public-discoverability caveats cited at the point of use.
  • ISTQB Glossary V4.7.1 - https://glossary.istqb.org/en_US/term/shift-right defines shift right as "A test approach to test a system continuously in production." (Per workspace memory: ISTQB glossary is JS-rendered; navigate via Playwright or real browser.)
  • feature-flag-experiment-validator - sibling skill: validates A/B experiments running behind flags.
  • prod-canary-validator - sibling: catches regressions in canary stage before full rollout.

Full monitor templates

View source (opens in new window)

Full monitor templates

The complete browser-check and API-check scripts the skill's Step 3 and Step 4 sketches are drawn from. Both are Checkly-style; adapt the syntax per platform.

Browser check (Playwright-style)

// monitors/checkout-journey.spec.ts (Checkly-style)
import { test, expect } from '@playwright/test';

test('checkout journey - happy path', async ({ page }) => {
  // 1. Land on home page
  await page.goto('https://example.com/');
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();

  // 2. Search and add to cart
  await page.getByRole('textbox', { name: 'Search' }).fill('BOOK-001');
  await page.getByRole('button', { name: 'Search' }).click();
  await page.getByRole('link', { name: 'BOOK-001' }).click();
  await page.getByRole('button', { name: 'Add to cart' }).click();

  // 3. Complete checkout (with synthetic test account)
  await page.getByRole('link', { name: 'Cart' }).click();
  await page.getByRole('button', { name: 'Checkout' }).click();

  // (Use a dedicated synthetic-test account; never user real customer data)
  await page.getByLabel('Email').fill(process.env.SYNTHETIC_USER_EMAIL!);
  await page.getByLabel('Password').fill(process.env.SYNTHETIC_USER_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();

  // 4. Place order with Stripe test card (in test mode in production!)
  await page.getByLabel('Card number').fill('4242 4242 4242 4242');
  await page.getByRole('button', { name: 'Place order' }).click();

  // 5. Assert confirmation
  await expect(page.getByRole('heading', { name: /Order confirmed/i })).toBeVisible();
});

Use accessibility-first locators (not CSS classes); synthetic monitors that depend on CSS classes break on every UI refactor. Use dedicated synthetic test accounts and test-mode payment processors so the script doesn't trigger real charges or orders.

API check (HTTP-step)

# monitors/api-orders-flow.yml (Checkly-style; adapt per platform)
name: orders API journey
runtimeId: 2024.02
type: API
request:
  - name: 1. Get auth token
    method: POST
    url: https://api.example.com/auth/token
    headers:
      Content-Type: application/json
    body: |
      {"email": "{{SYNTHETIC_USER_EMAIL}}", "password": "{{SYNTHETIC_USER_PASSWORD}}"}
    assertions:
      - source: STATUS_CODE
        comparison: EQUALS
        target: 200
      - source: JSON_BODY
        property: $.access_token
        comparison: NOT_EMPTY
    setup: |
      // Save token for next request
      vars.set('TOKEN', response.body.access_token);

  - name: 2. List orders
    method: GET
    url: https://api.example.com/orders
    headers:
      Authorization: Bearer {{TOKEN}}
    assertions:
      - source: STATUS_CODE
        comparison: EQUALS
        target: 200
      - source: RESPONSE_TIME
        comparison: LESS_THAN
        target: 500   # ms
      - source: JSON_BODY
        property: $.orders
        comparison: IS_ARRAY

  - name: 3. Get specific order
    method: GET
    url: https://api.example.com/orders/{{TEST_ORDER_ID}}
    headers:
      Authorization: Bearer {{TOKEN}}
    assertions:
      - source: STATUS_CODE
        comparison: EQUALS
        target: 200
      - source: JSON_SCHEMA
        target: schemas/order.json

Per-step assertions distinguish "the API returned" from "the API returned the right thing" - assert status code, response shape, and response time.

RUM source queries

Per-source instructions for pulling the top-N journey inventory for the RUM-coverage gap method in synthetic-monitor-author. Aim for the top 50 view paths (or transaction names) by session volume to avoid chasing long-tail pages with negligible traffic.

Datadog RUM

In the RUM Explorer (https://app.datadoghq.com/rum/explorer):

  1. Set event type to Views.
  2. Group by @view.url_path (or @view.name for SPAs with named routes). Per Datadog RUM Explorer docs (opens in new window), "aggregate into groups based on the value of one or several event facets" and "extract the count of events per group" to get session volume per path.
  3. Sort descending by count. Export as CSV or copy the top-50 rows.
  4. For each path, also note the p75 LCP / p75 CLS available in the Performance Overviews dashboard (Datadog RUM Dashboards (opens in new window): "See a global view of your website/app performance and demographics").

Query syntax shorthand: @view.url_path:* | count by @view.url_path | sort desc. RUM Explorer supports key:value pairs where custom attributes require a created facet first (Datadog RUM Search (opens in new window)).

Sentry Performance

Open the Performance module and use the Trace Explorer to slice by transaction name. Per Sentry Transaction Summary docs (opens in new window), the platform surfaces throughput as TPM (transactions per minute) and TPS (transactions per second) per named transaction. Sort by Total throughput to surface highest-volume journeys. Export the table.

GA4 + CrUX (public-facing sites)

For public pages, the Chrome User Experience Report provides origin-level and URL-level field data. Per CrUX methodology (opens in new window), pages must be publicly discoverable (HTTP 200, no noindex) and meet a minimum visitor threshold for statistical confidence; exact threshold is undisclosed. Access via:

  • CrUX API (https://chromeuxreport.googleapis.com/v1/records:queryRecord) for per-URL LCP, INP, CLS distributions.
  • BigQuery (chrome-ux-report.all.<YYYYMM>) for bulk URL-level data.
  • PageSpeed Insights API for per-URL field vs. lab comparison.

Per web.dev Core Web Vitals (opens in new window), the three stable metrics are:

  • LCP (Largest Contentful Paint): good threshold 2.5 s.
  • INP (Interaction to Next Paint, replaced FID in 2024): good threshold 200 ms.
  • CLS (Cumulative Layout Shift): good threshold 0.1.

All thresholds apply at the 75th percentile of page loads (web.dev CWV (opens in new window)). CrUX field data is "the Google dataset of the Web Vitals program" (CrUX docs (opens in new window)).

Related skills

feature-flag-experiment-validator

Validates the statistical significance of an A/B / feature-flag experiment result - computes per-metric effect size + p-value (chi-square for proportions, Welch's t-test for continuous metrics), applies a multiple-comparison correction (Bonferroni / Benjamini-Hochberg) when N>1 metric, surfaces practical-vs-statistical-significance distinction, and emits a ship/don't-ship verdict per metric. Use when an experiment has finished and someone is about to ship the winning variant off a dashboard readout, when a result rests on a small sample, or when more than one metric was compared - the rigorous version of "the variant looks better in the dashboard."

prod-canary-validator

Builds a canary-validation workflow that compares a canary deploy's metrics against the baseline (current main) - picks the metric set (error rate, p50/p95/p99 latency, business KPIs like checkout-completion), defines per-metric thresholds (absolute + relative-to-baseline), runs a statistical-comparison check (effect size + significance) over the canary's observation window, and emits a promote/rollback verdict. Use as the gate between canary deploy and full rollout - the deterministic version of "the on-call eyeballs the dashboard for 30 min.

release-runbook-author

Turns one service's release into a written six-phase runbook: pre-flight checks, a smoke gate, a canary observation window, a named human promote gate, progressive rollout, and post-release verification. Fixes each phase's pass criteria as a delta against a recorded baseline rather than a bare absolute number, gives canary and rollout separate windows and separate thresholds, and emits a per-phase evidence table that becomes the release record. The multi-team cutover-sequence procedure - dependency-ordered gates with one named owner each, hard timeboxes, written rollback triggers, and the reverse-order rollback path - is worked in references for windows where several teams cut over interdependent services. Use when a single service is about to ship and its release steps exist only as tribal knowledge or a chat thread, or when a shared release window needs its cutover order, gate owners, and rollback path written down.