# Static Website Visual Change Monitor - Desktop & Mobile (`maydit/website-visual-change-monitor`) Actor

Capture desktop/mobile static-page screenshots, compare saved pixels, and export before/after/diff artifacts. JavaScript disabled; fixed viewports, source coverage and HTML reports.

- **URL**: https://apify.com/maydit/website-visual-change-monitor.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $12.00 / 1,000 saved desktop/mobile pairs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Static Website Visual Change Monitor - Desktop & Mobile

**Two viewport captures, a saved visual baseline, and a clear comparison report. JavaScript is disabled.**

Monitor public static HTML/CSS pages: documentation, marketing pages, published references and server-rendered sites. Each saved observation includes a desktop screenshot (1440×900) and mobile-width screenshot (390×844), both at pixel ratio1. These are fixed viewport captures, not full-page screenshots or mobile-device emulation. JavaScript-dependent pages may be incomplete or fail honestly.

### Start in a minute

1. Add a public page URL or keep the small example.
2. Start with comparison disabled. Open **Visual report index** in Output.
3. Inspect both PNGs. Enable comparison and choose a history name to establish your baseline. Repeat with the same URL and settings to receive pixel differences.

```json
{"urls":["https://example.com/"],"comparePrevious":false}
```

One row represents one complete desktop/mobile pair. JSON preserves nested viewport details; CSV exposes useful overview fields. Artifact URLs follow your Apify storage permissions and retention. No token is embedded in URLs.

### What you receive

- Paired desktop/mobile-width evidence from one configured page.
- Optional previous images, current images and colored difference PNGs.
- Per-viewport differing pixel count and percentage with explicit tolerance and alert threshold.
- A separate HTML comparison report for each URL and a readable run index.
- Stable identity, observation timestamps, final URLs and resource-coverage counters.

| Field | Meaning |
|---|---|
| recordId | Hash of URL and the complete rendering/comparison scope |
| comparisonStatus | not_requested, baseline_created, unchanged or changed |
| desktopChangedPercent / mobileChangedPercent | Differing pixels as a viewport percentage, or null without history |
| views | Dimensions, screenshot URL/hash, prior/diff URLs and exact pixel counts |
| reportUrl | Per-observation HTML report |
| coverage | Requests, response bytes, origins and intentionally excluded resources |
| previousObservedAt | Timestamp of the previous confirmed baseline |
| sourceUrl | Configured public page URL, identical to url; final viewport URLs are in views |

Pixel differences are not semantic change detection. Cookie banners, personalization, images and font substitution can affect pixels. A meaningful text change can occupy a small percentage. Pixelmatch color tolerance defaults to0.1; a viewport is flagged above0.5% changed pixels. Anti-aliasing is excluded. Review the evidence before deciding whether a change matters.

### Saved history and pricing

Comparison is opt-in. The first confirmed saved pair establishes history. Later confirmed pairs replace it: comparison is with the last saved observation, not an immutable approved design. Different URLs, thresholds, viewports or renderer versions use distinct scopes. Use one writer per history name; do not overlap runs against the same baseline. Comparison labels describe the prior images available when preparing a row; baseline persistence is separate. Check SUMMARY.historyUpdated and failedSources after storage failures.

Run artifacts are staged before output. A confirmed zero-row spending-limit rejection triggers cleanup when the work budget allows it. If a dataset call throws or times out after being attempted, its acknowledgement is unknown: the row may have reached the dataset. Its screenshots and report are retained, later source work stops, and its baseline is not advanced. SUMMARY distinguishes confirmed rowsSaved from rowsPossiblySaved and outputAcknowledgementUnknown. Inspect the actual dataset and settled charges before retrying an uncertain write. Named baseline images and metadata advance only after confirmed dataset output. Apify dataset and key-value writes are not one atomic transaction; a timed-out storage request may still complete remotely.

**Base price: $0.02 per saved desktop/mobile URL pair, plus $0.00005 per start at the fixed1GB memory.** One pair costs$0.02005 at that rate. Unchanged confirmed saved pairs bill the same. Failures before dataset output, explicit zero-row rejections and diagnostics produce no result event. For an unknown dataset acknowledgement, the actual dataset and settled billing determine whether a result charge occurred. Review current Store pricing and set a maximum charge. No source account, paid proxy or extra subscription is needed.

### Scope and limits

- JavaScript disabled. Scripts, XHR, media, nested frames and WebSockets are excluded. This is a static renderer, not a dynamic-app testing service.
- No login, cookies, browser code, custom headers, credential URLs, scrolling, clicks, CAPTCHA solving or challenge bypass.
- Public HTTP(S), ports80/443 only. DNS-pinned Node transport fetches resources and fulfills requests into Chromium. Direct browser HTTP requests are not allowed.
- robots.txt checked for every main navigation; redirects and each resource undergo address checks. Disallowed pages or unavailable policy are skipped.
- At most5 input URLs,100 rendered-resource requests,12 rendered-resource origins and20MiB rendered-resource response bodies per pair; each resource response is at most4MiB. Robots-policy lookups are additional, separately capped at512000bytes per response and four redirects, and share the viewport deadline. Each viewport has20 seconds. The maximum120-second run budget reserves5seconds for SDK shutdown; its work deadline leaves10seconds for browser cleanup and final reports. Work storage calls wait at most4seconds within remaining work time; final report/summary writes wait at most2.5seconds each. Cleanup is deferred when its budget ends, so some unreferenced artifacts or old baseline images can remain.
- Fixed visible pixels only. No claims about content below the viewport, real mobile behavior, accessibility, SEO, other browser engines or semantic correctness.
- Visible access challenges, empty static bodies, failed resources and incomplete pairs are reported, not charged as usable observations.

### Practical workflows

Capture documentation after deployment, review desktop/mobile layout drift on server-rendered marketing pages, or keep dated evidence of a static reference page. Start with the three recipes in examples.json, inspect images, then save your own Actor task and attach an Apify Schedule. Keep baseline settings unchanged. No external notification or delivery is automatically enabled.

### Source and support

Independent Mayd IT tool, not affiliated with target sites. Respect source access terms and use public pages you are authorized to capture. Open an Actor issue with a run ID and expected result; never include credentials.

[Product guide](https://mayd-it.com/data-tools/actors/website-visual-change-monitor/) · [Workflow](https://mayd-it.com/data-tools/actors/website-visual-change-monitor/workflow.md) · [All data tools](https://mayd-it.com/data-tools/) · [Use with AI](https://mayd-it.com/data-tools/ai/)

# Actor input Schema

## `urls` (type: `array`):

One to five public HTML pages. JavaScript disabled. No login, cookies, private addresses or credential URLs.

## `comparePrevious` (type: `boolean`):

First saved pair establishes history. Reuse the same history name/settings; one writer per baseline.

## `baselineKey` (type: `string`):

Independent names separate history. Baseline images persist in your own named store.

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

Pixelmatch color tolerance; distinct from the changed-page percentage threshold.

## `changePercentThreshold` (type: `number`):

A viewport is changed when its differing-pixel percentage is strictly above this threshold.

## `maxRunSeconds` (type: `integer`):

All URLs share this budget. Each viewport has at most20 seconds; incomplete pairs are not billed.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/"
  ],
  "comparePrevious": false,
  "baselineKey": "default",
  "pixelThreshold": 0.1,
  "changePercentThreshold": 0.5,
  "maxRunSeconds": 120
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

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

No description

## `report` (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 = {
    "urls": [
        "https://example.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/website-visual-change-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 = { "urls": ["https://example.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/website-visual-change-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 '{
  "urls": [
    "https://example.com/"
  ]
}' |
apify call maydit/website-visual-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/website-visual-change-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/CogcQZj7hAWvINEVW/builds/D4Mk3Jv7467IVKolL/openapi.json
