ScreenshotNeo

BlogEngineering

Monitoring Websites with a Browser Automation API

Build reliable synthetic monitors with Playwright, scheduled browser checks, failure artifacts, and a managed screenshot API option.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: monitor a website with browser automation by running a scheduled synthetic check that launches Chromium, Firefox, or WebKit, navigates to the site, performs the important user actions, and asserts the expected result. Use URL or API checks for basic reachability and contract validation; use a real browser for JavaScript workflows such as login, search, navigation, checkout, and visual or performance assertions.

1. Choose the right monitoring layer

Need Best check Why
Endpoint responds URL or HTTP check Cheap and fast; validates status, headers, and response content.
JSON contract remains valid API check Asserts status, schema fields, and business values without rendering a page.
Client-side workflow works Browser check Runs JavaScript, cookies, redirects, and user interactions.
Page looks correct Browser check plus screenshot or visual assertion Captures the rendered result and records layout regressions.
Performance from user regions Browser check from multiple locations Measures navigation and workflow timing where users are located.

A practical setup runs a cheap URL or API check frequently, then reserves full browser execution for journeys that require it. Each check should represent one customer-critical path and stop at a clear assertion so an alert identifies the failing step.

2. Build a browser monitor with Playwright

Install the runner

mkdir synthetic-monitor
cd synthetic-monitor
npm init -y
npm install playwright
npx playwright install chromium

Complete Node.js monitor

const { chromium } = require('playwright');

const target = process.env.TARGET_URL || 'https://example.com';
const expectedTitle = process.env.EXPECTED_TITLE || 'Example Domain';

(async () => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1440, height: 900 },
    locale: 'en-US',
    timezoneId: 'UTC'
  });
  const page = await context.newPage();
  page.setDefaultTimeout(15000);

  try {
    const started = Date.now();
    const response = await page.goto(target, {
      waitUntil: 'domcontentloaded',
      timeout: 30000
    });

    if (!response || !response.ok()) {
      throw new Error(`Navigation failed: HTTP ${response ? response.status() : 'no response'}`);
    }

    await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
    await page.waitForSelector('body', { state: 'visible' });

    const title = await page.title();
    if (title !== expectedTitle) {
      throw new Error(`Unexpected title: ${JSON.stringify(title)}`);
    }

    console.log(JSON.stringify({
      ok: true,
      url: page.url(),
      title,
      duration_ms: Date.now() - started
    }));
  } catch (error) {
    await page.screenshot({ path: 'failure.png', fullPage: true }).catch(() => {});
    console.error(error.stack || error);
    process.exitCode = 1;
  } finally {
    await browser.close();
  }
})();

Run it with TARGET_URL=https://your-site.example EXPECTED_TITLE='Your title' node monitor.js. Keep assertions tied to user-visible outcomes: a title, authenticated navigation, confirmation text, or completed transaction state. An HTTP 200 alone does not prove that the workflow works.

Python version

from os import getenv
from playwright.sync_api import sync_playwright

url = getenv("TARGET_URL", "https://example.com")
expected_title = getenv("EXPECTED_TITLE", "Example Domain")

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page(viewport={"width": 1440, "height": 900})
    page.set_default_timeout(15_000)
    try:
        response = page.goto(url, wait_until="domcontentloaded", timeout=30_000)
        if response is None or not response.ok:
            raise RuntimeError(f"Navigation failed: HTTP {response.status if response else 'no response'}")
        try:
            page.wait_for_load_state("networkidle", timeout=15_000)
        except Exception:
            pass
        page.locator("body").wait_for(state="visible")
        title = page.title()
        if title != expected_title:
            raise RuntimeError(f"Unexpected title: {title!r}")
        print({"ok": True, "url": page.url, "title": title})
    except Exception:
        page.screenshot(path="failure.png", full_page=True)
        raise
    finally:
        browser.close()

Run an interaction journey

await page.goto('https://shop.example/checkout', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Email').fill(process.env.MONITOR_EMAIL);
await page.getByRole('button', { name: 'Continue' }).click();
await page.getByRole('heading', { name: 'Order confirmed' }).waitFor();

Use stable roles, labels, and test identifiers instead of brittle CSS paths. Give every step a specific timeout and include the step name in your error output.

3. Schedule the check and preserve evidence

  1. Package the script in your monitoring platform or CI runner.
  2. Run it from locations that represent your users. Multi-region runs help separate a global regression from a regional network or CDN problem.
  3. Choose a frequency based on impact and cost. Run URL checks more often than full browser journeys when possible.
  4. Capture a screenshot, trace, and video on failure. These artifacts shorten diagnosis time.
  5. Alert only after a useful condition, such as one failure with a retry or two consecutive failures, to avoid transient noise.

Checkly provides URL monitors, API checks, browser checks, Playwright suites, multistep checks, shared locations, schedules, alert channels, screenshots, video replays, and traces. Its documentation describes synthetic checks as a real browser or endpoint run on a schedule from outside your infrastructure; it advertises execution from 22+ global locations. Existing Playwright specs can be reused when the standard Playwright runner is supported.

4. Make checks safe for production

  • Create a dedicated monitoring account with the minimum permissions needed.
  • Use test fixtures and predictable data. Never depend on a real customer’s order or an expiring promotion.
  • Make writes idempotent: a retry should not create duplicate orders, users, or tickets.
  • Clean up records created by the check, or use a disposable tenant.
  • Store credentials in the monitoring platform’s secret store, never in source control or screenshots.
  • Mask sensitive values in traces, videos, and logs.
  • Keep journeys short. One check for login, one for search, and one for checkout makes failures easier to locate than one large script.

Checkly’s guidance summarizes the production rule: a test that writes to production needs a dedicated account, cleanup, and idempotent steps.

5. Managed browser APIs

A managed browser API runs the browser infrastructure for you. Browserless provides REST, GraphQL, WebSocket, and CDP interfaces, and existing Playwright or Puppeteer code can connect to its managed sessions. Its REST surface includes screenshots, PDFs, content scraping, and custom browser functions. Browserless documents both cloud and self-hosted deployment.

Choose this model when you need browser execution without maintaining Chromium workers, sandboxing, patching, scaling, or regional capacity. Compare providers on browser and protocol support, workflow complexity, execution regions, schedule frequency, authentication and secret handling, screenshots/traces/video, alert routing, infrastructure-as-code support, hosted versus self-hosted operation, quotas, total cost, and whether existing Playwright tests can be reused unchanged.

6. Reliability and performance

Reduce false positives

  • Wait for a meaningful selector or state, not an arbitrary long delay.
  • Use domcontentloaded first, then wait for the specific application state.
  • Allow a bounded retry for DNS, connection resets, and transient third-party failures.
  • Record the final URL, response status, browser console errors, and the failing step.
  • Separate site failures from monitor failures by checking browser launch, DNS, TLS, and network errors independently.

Keep runs fast

  • Block analytics, ads, and nonessential third-party resources when they are not part of the user journey.
  • Reuse a browser process where your runner supports it, while creating a fresh context per check for isolation.
  • Use API setup for fixtures and reserve UI actions for the behavior you actually need to validate.
  • Set a hard overall deadline so a hung page cannot consume the schedule.

Control cost

Browser minutes, concurrent sessions, storage for videos and traces, and multi-region frequency usually drive cost. Start with one region and a short journey, measure alert value, then add locations or frequency where the result changes an operational decision. Keep artifacts for failures and a small sample of successful runs.

7. Troubleshooting common failures

Symptom Likely cause Fix
Timeout waiting for a selector Wrong locator, slow app state, or consent dialog blocking the page Inspect the failure screenshot, use a role or test id, and wait for the actual ready state.
HTTP response is 200 but the check fails Client-side rendering or an error screen returned inside a successful response Assert visible content, title, URL, or a business confirmation.
Works locally, fails in monitoring Missing secret, different timezone, location, browser, or outbound IP policy Compare context settings and add explicit locale, timezone, headers, and credentials.
Intermittent navigation errors DNS, TLS, CDN, or third-party dependency instability Capture status and console logs, retry once, and alert on consecutive failures.
Duplicate records Retries replay a non-idempotent action Use a dedicated account, unique test identifiers, cleanup, and idempotent endpoints.
Trace contains secrets Form values or headers were recorded Mask fields, redact logs, and use synthetic credentials with limited access.
Browser launch fails Missing browser binary, sandbox restriction, or incompatible image Install the pinned browser version and use the provider’s supported runtime image.

8. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and every response reports the result in X-Page-Verdict and X-Billed headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options. The basic call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo supports full-page capture with lazy images, CSS element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and page options, HTML/CSS rendering, custom JavaScript and CSS, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameter names used by other screenshot APIs also work for easier migration.

Free includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account and start with the 1,000 monthly shots.

9. FAQ

Can browser automation replace uptime monitoring?

No. Keep a cheap URL or API check for reachability and use browser automation for workflows that need JavaScript, cookies, redirects, or interaction.

Should every monitor run a full checkout?

No. Use a safe, dedicated account and a minimal journey that proves the risk you care about. Keep destructive or expensive actions out of production checks.

Which browser should run in production?

Use the browser your customers use, then add another engine when compatibility risk justifies it. Chromium is a common starting point; Playwright also supports Firefox and WebKit.

When should I use a managed browser API?

Use one when operating browser workers, patching binaries, scaling concurrency, or serving multiple regions would distract from the monitoring problem.

What should an alert contain?

Include the journey and step, region, final URL, status or exception, duration, and links to the screenshot, trace, and video.