npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hmp-global/ddev-visdiff

v0.1.7

Published

DDEV-focused visual regression checks for comparing a local site against production.

Readme

ddev-visdiff

A lightweight visual regression CLI that compares a local DDEV site against a live site.

It is designed for DDEV-based local development:

  • live screenshots come from liveBaseUrl
  • local screenshots come from the DDEV network by default
  • each site owns its own important page list
  • optional preparation commands can refresh the local database from production first
  • reports are written as static HTML under .visdiff/reports/
  • Chromium runs inside a dedicated DDEV service based on the Playwright image
  • an optional pre-push hook runs ddev visdiff before code is pushed

Setup

Install the add-on from the DDEV project root:

ddev add-on get HMP-Global/ddev-visdiff
ddev restart

The add-on creates:

  • .ddev/docker-compose.visdiff.yaml
  • .ddev/visdiff/
  • .ddev/visdiff/Dockerfile
  • .ddev/commands/host/visdiff

The visdiff service builds from mcr.microsoft.com/playwright:v1.60.0-noble, so Chromium is part of DDEV instead of the host machine. The image also installs the Platform.sh CLI and MySQL/PostgreSQL clients for preparation commands. The CLI runtime is vendored under .ddev/visdiff/, isolated from the application dependencies.

From the site repository root, create a config:

ddev visdiff init --live https://example.com

Run manually:

ddev visdiff run

Run manually and refresh production content first:

ddev visdiff run --prepare

Install the Git hook from the same site repository:

ddev visdiff install-hook --ddev

Verify the tool from this package checkout:

npm run check
npm run smoke

npm Fallback

The package is also published to npm for direct CLI development and manual installs:

npm install --save-dev @hmp-global/ddev-visdiff
npx ddev-visdiff install-ddev

The DDEV add-on install is the preferred path for project use.

Config

init creates .visdiff.json in the current repository.

{
  "liveBaseUrl": "https://example.com",
  "localBaseUrl": "auto:ddev",
  "checks": [
    { "name": "Home", "path": "/" },
    { "name": "About", "path": "/about" },
    { "name": "Contact", "path": "/contact" }
  ],
  "viewports": [
    { "name": "desktop", "width": 1440, "height": 1000 },
    { "name": "mobile", "width": 390, "height": 844 }
  ],
  "databaseDump": {
    "enabled": false,
    "runBeforeCompare": false,
    "environment": "production",
    "file": ".visdiff/latest.sql",
    "import": true,
    "importShell": "mysql --host=db --user=db --password=db db < {file}"
  },
  "stageFileProxy": {
    "enabled": false,
    "origin": "",
    "originDir": "",
    "drush": "./vendor/bin/drush",
    "cacheRebuild": true,
    "install": true,
    "docroot": "web",
    "localModulePath": ".visdiff/modules/stage_file_proxy",
    "repository": "https://git.drupalcode.org/project/stage_file_proxy.git",
    "ref": "3.1.6",
    "guardGit": true
  },
  "prepareCommands": [],
  "threshold": 0.01,
  "waitUntil": "networkidle",
  "fullPage": true,
  "waitForMedia": true,
  "mediaTimeoutMs": 10000,
  "freezeMedia": true,
  "failOnMediaError": true,
  "hideSelectors": [],
  "ignoreSelectors": [],
  "headers": {},
  "liveHeaders": {},
  "localHeaders": {}
}

checks can use different live and local paths when a local route differs:

{
  "checks": [
    {
      "name": "Landing page",
      "path": "/",
      "livePath": "/campaign",
      "localPath": "/campaign?cache-bust=1"
    }
  ]
}

DDEV sets auto:ddev to the project's primary HTTPS URL, such as https://example.ddev.site, inside the visdiff service. If DDEV does not expose a primary URL, visdiff falls back to http://web. Use localBaseUrl when you want to override that:

{
  "localBaseUrl": "https://example.ddev.site"
}

Use localHeaders for ordinary request headers. Chromium does not allow overriding the Host header.

{
  "localBaseUrl": "https://example.ddev.site",
  "localHeaders": {
    "X-Visdiff": "local"
  }
}

Browser Rendering

The DDEV add-on uses Playwright Firefox by default because the bundled Chromium browser does not decode some MP4/H.264 video backgrounds on Linux/arm64. You can override the browser per project:

{
  "browser": "firefox"
}

Supported values are chromium, firefox, and webkit. By default, visible video elements must load a frame before screenshots are captured. This prevents a page with a blank hero video from passing only because both live and local failed to render the same media.

Full-page screenshots scroll through the page before capture so lazy-loaded, animated, and parallax sections can initialize. The renderer also stabilizes common scroll animation markup, such as AOS, so below-the-fold sections are visible in the final screenshot. If a project needs true viewport-by-viewport stitching, enable it explicitly:

{
  "stitchFullPage": true,
  "scrollDelayMs": 250,
  "hideRepeatedFixedElements": true
}

Production Content Refresh

Preparation commands run inside the DDEV visdiff service.

For most Platform.sh Drupal sites, set the environment name per site:

{
  "databaseDump": {
    "enabled": true,
    "runBeforeCompare": true,
    "environment": "production",
    "file": ".visdiff/latest.sql",
    "import": true
  }
}

That runs platform db:dump -e production -y --file .visdiff/latest.sql, then imports that dump into DDEV's default database with:

mysql --host=db --user=db --password=db db < .visdiff/latest.sql

For sites where the main production environment has another name:

{
  "databaseDump": {
    "enabled": true,
    "runBeforeCompare": true,
    "environment": "main"
  }
}

If you only want to create the dump file and import it manually, disable import:

{
  "databaseDump": {
    "enabled": true,
    "environment": "production",
    "import": false
  }
}

For Drupal file assets, use the Stage File Proxy module instead of downloading all production files. Configure stageFileProxy so prepare installs a local-only module copy, enables it in the local database after importing, and points it at production:

{
  "stageFileProxy": {
    "enabled": true,
    "origin": "https://example.com"
  }
}

When stageFileProxy.enabled is true, prepare clones stage_file_proxy release 3.1.6 into .visdiff/modules/stage_file_proxy, symlinks it into web/modules/contrib/stage_file_proxy, and adds both paths to .git/info/exclude. It then runs ./vendor/bin/drush pm:enable stage_file_proxy -y, configures stage_file_proxy.settings origin, and rebuilds Drupal caches. If your Drush command or docroot lives somewhere else, set stageFileProxy.drush or stageFileProxy.docroot.

The module is intentionally local-only. With stageFileProxy.guardGit enabled, visdiff refuses to run if staged or unpushed Git changes contain Stage File Proxy code, composer.json/composer.lock additions, or exported Drupal config such as stage_file_proxy.settings.yml or core.extension.yml entries.

The Platform CLI needs non-interactive auth inside the container, usually through PLATFORMSH_CLI_TOKEN.

Use ddev visdiff prepare when you want to refresh content without taking screenshots.

Add generated artifacts to the site repository's .gitignore:

.visdiff/reports/
.visdiff/latest.sql

Hook behavior

The DDEV pre-push hook runs ddev visdiff run --config .visdiff.json.

When databaseDump.runBeforeCompare is true, the hook refreshes content before screenshots. When visual differences exceed the threshold, the command exits with status 1, prints the report path, and blocks the push.

Ignoring Unstable Areas

Use hideSelectors to hide volatile elements before capture:

{
  "hideSelectors": [".cookie-banner", "[data-testid='timestamp']"]
}

Use ignoreSelectors to mask areas in both screenshots before comparison:

{
  "ignoreSelectors": [".ad-slot", ".map"]
}