# Website Performance & Visual Regression Monitor for CI (`produkdigitalali/ci-web-regression-monitor`) Actor

Run local Lighthouse audits, visual screenshot regression tests, DOM/SEO checks, and deterministic CI pass/fail monitoring with persistent baselines. Compare production with staging or monitor websites over time.

- **URL**: https://apify.com/produkdigitalali/ci-web-regression-monitor.md
- **Developed by:** [ProdukDigitalAli](https://apify.com/produkdigitalali) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $19.00 / 1,000 url + viewport auditeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Performance & Visual Regression Monitor for CI

Turn public web pages into a deterministic release gate: **visual screenshot diff + bundled local Lighthouse lab scores + DOM/SEO deltas + persistent accepted baselines + machine-readable PASS/REGRESSION output**.

This Actor is designed for CI/CD, release validation, scheduled monitoring, and staging-vs-production checks. It does not try to be another generic site crawler.

### What you get

For every audited URL/viewport the Actor can return:

- `BASELINE_CREATED`, `PASS`, `REGRESSION`, `RECOVERED`, or `ERROR`
- HTTP status and final URL
- visual changed-pixel percentage and a highlighted diff PNG
- current and accepted-baseline screenshots
- Lighthouse category scores from the bundled local provider by default; optional Google PageSpeed provider
- Lighthouse lab LCP, CLS, TBT, FCP, and Speed Index values when available
- score deltas vs the accepted baseline
- title, meta description, canonical, robots, language, H1, and selected DOM text/count changes
- normalized regression reasons and regression types
- a compact run summary in the default key-value store

> **Metric note:** LCP and CLS are Core Web Vitals metrics. TBT is a Lighthouse lab responsiveness diagnostic, not itself a Core Web Vital. This Actor describes these values as Lighthouse **lab metrics** rather than field-user measurements.

### Three high-value use cases

#### 1. Fail a deployment when a key page regresses

Monitor a release candidate and configure limits such as:

- performance score drop > 5 points
- visual difference > 1%
- LCP > 4,000 ms
- CLS > 0.25
- SEO title/canonical/robots changes

Your CI step reads the returned `ciPassed` boolean and the structured `reasons` array.

#### 2. Compare staging and production in one run

Use `mode: "compare"` with a `baselineUrl` and candidate `url`. The Actor captures both pages at the same viewport, creates a visual diff, compares SEO/DOM fields and available Lighthouse metrics, and returns one release-gate record.

#### 3. Monitor important pages on a schedule

Use `mode: "monitor"`. The first successful run creates a persistent accepted baseline. Later runs compare against that baseline and can report recovery after a previous regression.

### Quick start: monitor a saved baseline

```json
{
  "targets": [
    {
      "url": "https://example.com",
      "label": "homepage"
    }
  ],
  "mode": "monitor",
  "device": "desktop",
  "runLighthouse": true,
  "lighthouseProvider": "local",
  "maxScoreDrop": 5,
  "maxVisualDiffPercent": 1,
  "baselineUpdatePolicy": "on_pass",
  "stateNamespace": "production"
}
```

First run:

```text
BASELINE_CREATED
```

A later healthy run:

```text
PASS
```

A later threshold violation:

```text
REGRESSION
```

If the page then returns within the configured thresholds:

```text
RECOVERED
```

### Compare staging vs production

```json
{
  "targets": [
    {
      "url": "https://staging.example.com",
      "baselineUrl": "https://example.com",
      "label": "homepage"
    }
  ],
  "mode": "compare",
  "device": "desktop",
  "runLighthouse": true,
  "lighthouseProvider": "local",
  "maxScoreDrop": 5,
  "maxVisualDiffPercent": 1,
  "failOnSeoChanges": true
}
```

`compare` mode is stateless: the reference and candidate are audited in the same run.

### Strict mobile release gate

```json
{
  "targets": [
    {
      "url": "https://example.com",
      "label": "homepage"
    }
  ],
  "mode": "monitor",
  "device": "mobile",
  "runLighthouse": true,
  "lighthouseProvider": "local",
  "requireLighthouse": true,
  "minPerformanceScore": 80,
  "minSeoScore": 90,
  "maxLcpMs": 4000,
  "maxCls": 0.25,
  "maxTbtMs": 600,
  "maxVisualDiffPercent": 1,
  "failOnSeoChanges": true,
  "domSelectors": ["main", "h1"],
  "stateNamespace": "strict-mobile"
}
```

Mobile defaults to a 390x844 Playwright viewport even if the Apify UI materializes the desktop width/height defaults. Explicit custom mobile dimensions are still respected.

### Visual-noise controls

Pixel diffs become noisy when pages contain clocks, animated carousels, ad slots, random recommendations, cursors, or live counters. The Actor includes four controls:

1. CSS animations and transitions are disabled before capture.
2. `hideSelectors` hides elements entirely before screenshot capture.
3. `maskSelectors` covers selected elements with a fixed mask color.
4. `pixelThreshold` ignores small per-channel pixel differences.

Example:

```json
{
  "hideSelectors": [".live-clock", ".ad-slot"],
  "maskSelectors": ["[data-user-avatar]"],
  "pixelThreshold": 0.1,
  "maxVisualDiffPercent": 0.5
}
```

Changing screenshot/DOM normalization settings automatically creates a separate baseline profile, preventing configuration changes from becoming permanent false regressions.

### Lighthouse providers

When `runLighthouse` is enabled, the default provider is **local Lighthouse** bundled inside the Actor. It launches Lighthouse against the same public URL using the Actor's Chromium runtime, so normal Store users do **not** need a Google API key.

`lighthouseProvider` options:

- `local` — default and recommended. Runs bundled Lighthouse inside the Actor.
- `pagespeed` — uses Google PageSpeed Insights v5. `pagespeedApiKey` is an optional encrypted secret and is never logged.
- `auto` — tries local Lighthouse first and falls back to PageSpeed only when a PageSpeed API key is supplied.

`requireLighthouse: false` allows visual/DOM monitoring to continue if the selected provider is temporarily unavailable. `requireLighthouse: true` turns provider unavailability into `LIGHTHOUSE_UNAVAILABLE` and a CI regression.

Provider identity is part of the baseline profile fingerprint. Switching from local Lighthouse to PageSpeed (or changing Lighthouse categories) creates a separate baseline instead of comparing unlike score sources.

Google PageSpeed remains optional because API quota/rate limiting is outside the Actor's control. Cloud staging on 2026-08-30 returned HTTP 429 with a zero keyless daily quota, which is why local Lighthouse is now the publication default.

### Regression types

Possible `regressionTypes` include:

- `HTTP_ERROR` — final page response is HTTP 4xx/5xx
- `PERFORMANCE_REGRESSION` — score drop, absolute score threshold, or lab-metric ceiling failed
- `VISUAL_REGRESSION` — changed-pixel percentage exceeds the configured maximum
- `SEO_REGRESSION` — SEO score or configured SEO field assertions failed
- `DOM_CHANGED` — configured DOM assertions changed
- `LIGHTHOUSE_UNAVAILABLE` — only a failure when `requireLighthouse` is enabled

Multiple regression types can be returned in the same result.

### Baseline semantics

`mode: "monitor"` stores accepted baselines in the named key-value store:

```text
ci-web-regression-state-v1
```

`baselineUpdatePolicy` controls movement of that accepted baseline:

| Policy | Behavior |
|---|---|
| `on_pass` | Default. Update only after `PASS` or `RECOVERED`; regressions do not overwrite the accepted baseline. |
| `never` | Keep the original baseline after it is created. |
| `always` | Move the baseline after every completed audit, including a regression. |

An initial run can still return `REGRESSION` if absolute thresholds, required Lighthouse availability, or HTTP health fail. The capture is stored so a later healthy run can produce `RECOVERED`.

Use `stateNamespace` to isolate projects, branches, environments, or clients.

#### Stable identity rule

If a target has an explicit `label`, the label is its baseline identity. This lets a stable label such as `homepage` survive deployment URL changes. If no label is provided, the normalized URL is used instead, so simply reordering an input list cannot attach the wrong saved baseline.

### Selected DOM / SEO checks

The Actor always records:

- `<title>`
- meta description
- canonical link
- robots meta
- `<html lang>`
- up to 10 H1 texts + H1 count

You can additionally track CSS selectors:

```json
{
  "domSelectors": ["main h1", "[data-testid=pricing]", ".release-version"],
  "failOnDomChanges": true
}
```

For every selector the Actor stores element count and the first element's normalized text.

### Artifacts

The result contains KVS record keys for:

- current screenshot
- accepted/reference baseline screenshot
- visual diff image

The diff PNG highlights changed pixels in red.

### `emitOnlyFailures`

Set:

```json
{
  "emitOnlyFailures": true
}
```

to suppress `PASS` and `RECOVERED` dataset rows while still performing the audit and updating state according to the baseline policy. `BASELINE_CREATED`, `REGRESSION`, and `ERROR` remain visible. The run summary still counts all processed results, not only emitted rows.

### Example output

```json
{
  "recordType": "CI_REGRESSION_RESULT",
  "mode": "monitor",
  "label": "homepage",
  "url": "https://example.com",
  "device": "desktop",
  "status": "REGRESSION",
  "ciPassed": false,
  "primaryRegressionType": "PERFORMANCE_REGRESSION",
  "httpStatus": 200,
  "finalUrl": "https://example.com/",
  "performanceScore": 78,
  "seoScore": 100,
  "lcpMs": 3210,
  "cls": 0.05,
  "tbtMs": 380,
  "visualDiffPercent": 1.84,
  "regressionTypes": [
    "PERFORMANCE_REGRESSION",
    "VISUAL_REGRESSION"
  ],
  "reasons": [
    "performance score dropped 9.00 points, above allowed 5.00.",
    "Visual diff 1.8400% exceeds maximum 1.0000%."
  ],
  "scoreDeltas": {
    "performance": -9,
    "accessibility": 0,
    "bestPractices": 0,
    "seo": 0
  },
  "errorType": null,
  "errorMessage": null
}
```

### CI integration pattern

The Actor deliberately returns a structured `ciPassed` boolean instead of crashing the Actor run whenever a regression is found. This keeps the evidence dataset and screenshots available. Your pipeline can fail its own release step when any returned row has:

```text
ciPassed == false
```

or when the run summary has:

```text
ciPassed == false
```

### Safety and target restrictions

This Actor is for **public HTTP(S) web pages**.

It rejects:

- localhost
- literal private/loopback/link-local/reserved IP targets
- hostnames that resolve to non-public IP addresses at the initial target gate

Use it only on pages you are authorized to test and monitor. Pages behind login/private networks are intentionally out of scope for the September MVP.

### Pricing model

The source defines one PPE event:

```text
url-viewport-audited
```

at an indicative source price of:

```text
$0.0099 per successfully audited URL/viewport
```

A `monitor` target records one audit event after successful page capture/evaluation. A `compare` target audits two pages (reference + candidate) and therefore records two events. Failed captures/invalid targets are not charged by the custom event.

The publication price should be re-checked after Apify Cloud staging confirms real browser/local-Lighthouse cost and margin.

### Limitations

- Lighthouse scores are lab measurements and naturally vary. Avoid thresholds so tight that normal lab variance becomes release noise.
- Local Lighthouse is the default to avoid external quota dependencies. If you explicitly select PageSpeed, Google API quota/rate limiting remains outside the Actor's control.
- Pixel comparison is intentionally literal. Dynamic content should be hidden/masked or given an appropriate threshold.
- Full-page screenshots are slower and more sensitive to lazy-loaded/dynamic content than viewport screenshots.
- Logged-in pages, private staging networks, VPN-only services, and local URLs are not supported in this MVP.
- The Actor audits explicitly supplied URLs; it is not a full-domain crawler or broken-link scanner.

### API / MCP-friendly design

The input is bounded to 10 targets per run, secrets are encrypted, output field names are stable, and statuses/regression types are explicit. This makes the Actor suitable for Apify API, schedules, OpenAPI/MCP calling surfaces, GitHub/GitLab CI wrappers, Make/Zapier, and internal release tooling.

### Recommended task examples

1. **Fail a deployment when Lighthouse performance drops**
2. **Compare staging and production screenshots automatically**
3. **Monitor Core Web Vitals lab metrics and SEO regressions on key pages**

### Support checklist

If you report an issue, include:

- public reproduction URL(s)
- monitor vs compare mode
- device/viewport
- thresholds
- noise-control selectors
- whether Lighthouse is required
- Actor run ID

Never include a PageSpeed API key in an issue or log excerpt.

# Actor input Schema

## `targets` (type: `array`):

Up to 20 targets. In monitor mode, url is checked against a saved baseline. In compare mode, add baselineUrl for the reference page.

## `mode` (type: `string`):

monitor compares each page with a persistent accepted baseline; compare checks baselineUrl vs url in the same run.

## `device` (type: `string`):

Desktop/mobile emulation for screenshot capture and Lighthouse strategy, or custom viewport dimensions.

## `viewportWidth` (type: `integer`):

Used for desktop/custom capture. Mobile defaults to 390 unless overridden.

## `viewportHeight` (type: `integer`):

Used for desktop/custom capture. Mobile defaults to 844 unless overridden.

## `fullPage` (type: `boolean`):

Capture the full scroll height. Off is faster and usually less noisy for CI.

## `pixelThreshold` (type: `number`):

0-1 per-channel sensitivity before a pixel counts as visually different. 0.10 is a practical default.

## `maxVisualDiffPercent` (type: `number`):

Fail when changed pixels exceed this percentage after noise controls are applied.

## `hideSelectors` (type: `array`):

CSS selectors hidden before screenshots, useful for clocks, ads, random banners, and live counters.

## `maskSelectors` (type: `array`):

CSS selectors covered with a stable mask color before visual comparison.

## `domSelectors` (type: `array`):

Optional CSS selectors whose first visible text and element count are tracked between runs.

## `runLighthouse` (type: `boolean`):

Run Lighthouse locally inside the Actor by default. Google PageSpeed Insights remains available as an optional provider.

## `lighthouseProvider` (type: `string`):

Local runs Lighthouse inside the Actor with bundled Node/Chromium and requires no API key. PageSpeed uses Google PageSpeed Insights. Auto tries local first and falls back to PageSpeed only when an API key is provided.

## `requireLighthouse` (type: `boolean`):

If enabled, any selected Lighthouse provider failure becomes a CI regression. Leave off for graceful visual/DOM fallback.

## `lighthouseCategories` (type: `array`):

Lighthouse categories to run for either local Lighthouse or PageSpeed Insights.

## `pagespeedApiKey` (type: `string`):

Used only for the PageSpeed provider or Auto fallback. Encrypted and never logged.

## `maxScoreDrop` (type: `number`):

Relative score drop allowed versus baseline before failing. Applied to available Lighthouse categories.

## `minPerformanceScore` (type: `number`):

Absolute minimum 0-100. Set 0 to disable.

## `minAccessibilityScore` (type: `number`):

Absolute minimum 0-100. Set 0 to disable.

## `minBestPracticesScore` (type: `number`):

Absolute minimum 0-100. Set 0 to disable.

## `minSeoScore` (type: `number`):

Absolute minimum 0-100. Set 0 to disable.

## `maxLcpMs` (type: `number`):

Optional absolute Lighthouse lab LCP ceiling. Set 0 to disable.

## `maxCls` (type: `number`):

Optional Lighthouse lab CLS ceiling. Set -1 to disable.

## `maxTbtMs` (type: `number`):

Optional Lighthouse Total Blocking Time ceiling. Set 0 to disable.

## `failOnSeoChanges` (type: `boolean`):

Fail when title, meta description, canonical, robots, or html lang differs from baseline.

## `failOnDomChanges` (type: `boolean`):

Fail when H1 or configured domSelectors differ from baseline.

## `baselineUpdatePolicy` (type: `string`):

on\_pass accepts only passing/recovered captures, never freezes the first baseline, always moves baseline even after regressions.

## `stateNamespace` (type: `string`):

Separates independent baselines such as production, staging, project, branch, or client.

## `stabilizationDelayMs` (type: `integer`):

Extra delay after load before DOM/screenshot capture.

## `navigationTimeoutSeconds` (type: `integer`):

Per-page Playwright navigation timeout.

## `pagespeedTimeoutSeconds` (type: `integer`):

Maximum time allowed for a local Lighthouse or PageSpeed audit.

## `emitOnlyFailures` (type: `boolean`):

Suppress PASS and RECOVERED rows. Baseline-created and error/regression rows remain visible.

## Actor input object example

```json
{
  "targets": [
    {
      "url": "https://example.com",
      "label": "homepage"
    }
  ],
  "mode": "monitor",
  "device": "desktop",
  "viewportWidth": 1365,
  "viewportHeight": 768,
  "fullPage": false,
  "pixelThreshold": 0.1,
  "maxVisualDiffPercent": 1,
  "hideSelectors": [],
  "maskSelectors": [],
  "domSelectors": [],
  "runLighthouse": true,
  "lighthouseProvider": "local",
  "requireLighthouse": false,
  "lighthouseCategories": [
    "performance",
    "accessibility",
    "best-practices",
    "seo"
  ],
  "maxScoreDrop": 5,
  "minPerformanceScore": 0,
  "minAccessibilityScore": 0,
  "minBestPracticesScore": 0,
  "minSeoScore": 0,
  "maxLcpMs": 0,
  "maxCls": -1,
  "maxTbtMs": 0,
  "failOnSeoChanges": false,
  "failOnDomChanges": false,
  "baselineUpdatePolicy": "on_pass",
  "stateNamespace": "default",
  "stabilizationDelayMs": 800,
  "navigationTimeoutSeconds": 45,
  "pagespeedTimeoutSeconds": 90,
  "emitOnlyFailures": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "targets": [
        {
            "url": "https://example.com",
            "label": "homepage"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("produkdigitalali/ci-web-regression-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "targets": [{
            "url": "https://example.com",
            "label": "homepage",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("produkdigitalali/ci-web-regression-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "targets": [
    {
      "url": "https://example.com",
      "label": "homepage"
    }
  ]
}' |
apify call produkdigitalali/ci-web-regression-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,produkdigitalali/ci-web-regression-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Fzja6HHjWBfm0TeZP/builds/qoV3tpJAATlTV3jeH/openapi.json
