# Website Visual Monitor (`void_error/website-visual-monitor`) Actor

takes screenshots and compare them visually to baselines, and generating diff images with change statistics.

- **URL**: https://apify.com/void\_error/website-visual-monitor.md
- **Developed by:** [Kareem Amr](https://apify.com/void_error) (community)
- **Categories:** Agents, Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

### What does Website Visual Monitor do?

**Website Visual Monitor** is an [Apify Actor](https://apify.com/actors) that automatically monitors websites for **visual changes**. It takes screenshots of your target URLs, compares them against previously stored baselines, generates visual diff images highlighting exactly what changed, and returns detailed change statistics.

Simply provide a list of URLs and run the Actor. On the first run it creates baselines. On every subsequent run it detects visual differences, calculates the percentage change, and flags pages that exceed your configured threshold. Perfect for regression testing, website monitoring, competitor change tracking, and automated visual QA.

### Why use Website Visual Monitor?

- **Visual regression testing** — Automatically detect unwanted UI changes before they reach production
- **Competitor monitoring** — Track when competitors make visual changes to their landing pages, pricing pages, or product catalog
- **Content freshness** — Verify that your own website content is rendering correctly across environments
- **SLA compliance** — Monitor third-party integrations and embedded content for unexpected visual changes
- **Apify platform advantages** — Schedule runs via cron, access results via REST API, integrate with Zapier/Make/GitHub, and store screenshots in the built-in key-value store

### How to use Website Visual Monitor

1. **Install dependencies** — Run `pip install -r requirements.txt` and `playwright install chromium`
2. **Run locally** — Execute `apify run` or `python -m my_actor` to test with sample input
3. **Configure input** — Edit the default input in `.actor/input_schema.json` or provide input via the Apify Console
4. **Deploy to Apify** — Run `apify push` to deploy and build the Actor on the Apify platform
5. **Schedule monitoring** — Use Apify Console to schedule periodic runs (daily, hourly, etc.)
6. **Review results** — Check the Output tab for change statistics and the key-value store for diff images

### Input

The Actor accepts the following input fields:

| Field | Type | Default | Description |
|---|---|---|---|
| `urls` | array of strings | — | List of website URLs to monitor (required) |
| `fullPage` | boolean | `true` | Capture full-page or viewport-only screenshots |
| `mobile` | boolean | `false` | Emulate mobile viewport (390x844) or desktop (1366x768) |
| `delay` | integer | `0` | Extra wait time in milliseconds after page load |
| `threshold` | number | `1.0` | Minimum pixel difference percentage to flag as changed |

#### Sample input JSON

```json
{
    "urls": [
        "https://example.com",
        "https://apify.com"
    ],
    "fullPage": true,
    "mobile": false,
    "delay": 2000,
    "threshold": 2.0
}
````

### Output

The Actor pushes one dataset item per URL with the following structure:

```json
{
    "url": "https://example.com",
    "changed": true,
    "differencePercent": 12.45,
    "threshold": 1.0,
    "timestamp": "2026-06-17T12:00:00+00:00",
    "baselineCreated": false,
    "status": "changed",
    "screenshotKey": "d41d8cd98f00b204e9800998ecf8427e-current.png",
    "diffKey": "d41d8cd98f00b204e9800998ecf8427e-diff.png"
}
```

#### Field descriptions

| Field | Description |
|---|---|
| `url` | The monitored URL |
| `changed` | Boolean indicating if the page changed beyond the threshold |
| `differencePercent` | Percentage of pixels that differ between baseline and current screenshot |
| `threshold` | The threshold used for this comparison |
| `timestamp` | ISO 8601 timestamp of the comparison |
| `baselineCreated` | `true` on first run when no previous baseline existed |
| `status` | One of `baseline_created`, `changed`, `unchanged`, or `error` |
| `screenshotKey` | Key in the key-value store to access the current screenshot |
| `diffKey` | Key in the key-value store to access the diff image (null if baseline was just created) |
| `error` | Error message if processing failed for this URL |

#### Screenshots and diff images

All images are stored in the Actor's default key-value store:

- `{md5(url)}-current.png` — Most recent screenshot
- `{md5(url)}-previous.png` — Previous screenshot (used as comparison baseline)
- `{md5(url)}-diff.png` — Visual diff highlighting changed pixels in red

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel from the Apify Console.

### Pricing / Cost estimation

Website Visual Monitor uses **Playwright** (full browser) for each URL, so each URL consumes compute time proportional to page load and screenshot processing. For most static websites, each URL takes 5-15 seconds. The Actor runs within the [Apify free tier](https://apify.com/pricing) for light usage. Monitor up to 10 URLs on a daily schedule and stay well within the free tier's monthly compute grant.

### Tips and advanced options

- **Set an appropriate threshold** — Start with `1.0` and adjust. Dynamic content (ads, live counters, dynamic backgrounds) may need a higher threshold. Static pages can use a lower one.
- **Use delay for dynamic pages** — Pages with animations or lazy-loaded images may need a 2-3 second delay to render fully.
- **Full page vs viewport** — Full-page captures are more thorough but produce larger images. Use viewport-only for faster runs if you only care about the above-the-fold area.
- **Baseline auto-update** — After each comparison, the current screenshot replaces the previous baseline automatically. This means the Actor tracks incremental changes, not changes since the very first run.
- **Concurrent monitoring** — For large numbers of URLs, consider splitting across multiple Actor runs or using the Apify scheduling feature.

### Local development

#### Prerequisites

- Python 3.12+
- [Apify CLI](https://docs.apify.com/cli)

#### Setup

```bash
## Clone the repository
git clone <repo-url>
cd website-visual-monitor

## Install dependencies
pip install -r requirements.txt

## Install Playwright browser
playwright install chromium

## Run locally
apify run
```

#### Testing with custom input

```bash
## Create or edit .actor/input_schema.json with your URLs
## Then run:
apify run --purge
```

The `--purge` flag clears previous storage (datasets, key-value stores) for a clean run.

### Deployment

#### Deploy to Apify platform

```bash
## Login to Apify (one-time)
apify login

## Push and build the Actor
apify push
```

Your Actor will be available at `https://console.apify.com/actors` under "My Actors".

### FAQ and support

**Is web scraping legal?** Web scraping and monitoring are legal when you comply with the target website's Terms of Service and `robots.txt`. This Actor is a tool — you are responsible for using it responsibly.

**Known limitations:**

- Very large pages (10000+ px tall) may produce large images that take longer to compare
- Pages requiring login or cookies need session handling (not included in this version)
- The diff algorithm is pixel-based and does not detect semantic changes (e.g., text content that shifts position)

**Need help?** reach out via the Apify platform.

**Custom solutions?** Contact us for tailored monitoring solutions, multi-step flows, or integration with your CI/CD pipeline.

# Actor input Schema

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

List of website URLs to capture screenshots of and monitor for visual changes.

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

Capture the entire scrollable page instead of just the visible viewport.

## `mobile` (type: `boolean`):

Emulate a mobile device viewport (390x844) instead of desktop (1366x768).

## `delay` (type: `integer`):

Milliseconds to wait after page load before taking the screenshot. Useful for waiting for animations or lazy-loaded content.

## `threshold` (type: `number`):

Minimum percentage of pixel difference required to consider a page as changed. Set to 0 to flag any change.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "fullPage": true,
  "mobile": false,
  "delay": 0,
  "threshold": 1
}
```

# Actor output Schema

## `results` (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("void_error/website-visual-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("void_error/website-visual-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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 void_error/website-visual-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=void_error/website-visual-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Website Visual Monitor",
        "description": "takes screenshots and compare them visually to baselines, and generating diff images with change statistics.",
        "version": "0.0",
        "x-build-id": "bwZzWkhS4LRcmA6nz"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/void_error~website-visual-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-void_error-website-visual-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/void_error~website-visual-monitor/runs": {
            "post": {
                "operationId": "runs-sync-void_error-website-visual-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/void_error~website-visual-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-void_error-website-visual-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "urls"
                ],
                "properties": {
                    "urls": {
                        "title": "URLs to Monitor",
                        "type": "array",
                        "description": "List of website URLs to capture screenshots of and monitor for visual changes.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "fullPage": {
                        "title": "Full Page Screenshot",
                        "type": "boolean",
                        "description": "Capture the entire scrollable page instead of just the visible viewport.",
                        "default": true
                    },
                    "mobile": {
                        "title": "Mobile Viewport",
                        "type": "boolean",
                        "description": "Emulate a mobile device viewport (390x844) instead of desktop (1366x768).",
                        "default": false
                    },
                    "delay": {
                        "title": "Delay (ms)",
                        "minimum": 0,
                        "maximum": 30000,
                        "type": "integer",
                        "description": "Milliseconds to wait after page load before taking the screenshot. Useful for waiting for animations or lazy-loaded content.",
                        "default": 0
                    },
                    "threshold": {
                        "title": "Change Threshold (%)",
                        "minimum": 0,
                        "maximum": 100,
                        "type": "number",
                        "description": "Minimum percentage of pixel difference required to consider a page as changed. Set to 0 to flag any change.",
                        "default": 1
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
