Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 210 additions & 0 deletions .github/workflows/reusable-wp-visual-regression.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
name: Reusable WP Visual Regression

on:
workflow_call:
inputs:
node-version:
description: 'Node.js version'
required: false
type: string
default: '22'
wp-versions:
description: 'JSON array of WP versions (e.g. "latest", "6.7")'
required: false
type: string
default: '["latest"]'
multisite:
description: 'Multisite mode: "none", "both", or "only"'
required: false
type: string
default: 'none'
mailpit:
description: 'Run Mailpit mail catcher (SMTP on :1025, UI on :8025)'
required: false
type: boolean
default: false
update-snapshots:
description: 'Regenerate baseline screenshots'
required: false
type: boolean
default: false
threshold:
description: 'Pixel diff tolerance as maxDiffPixelRatio (0-1)'
required: false
type: string
default: '0.2'
viewports:
description: 'JSON array of viewport presets (e.g. ["desktop", "mobile"])'
required: false
type: string
default: '["desktop", "mobile"]'
color-schemes:
description: 'JSON array of color scheme presets (e.g. ["light", "dark"])'
required: false
type: string
default: '["light"]'

jobs:
matrix-prep:
name: Prepare matrix
runs-on: ubuntu-latest
outputs:
wp-versions: ${{ steps.wp-versions.outputs.matrix }}
multisite: ${{ steps.multisite.outputs.matrix }}
steps:
- id: wp-versions
run: |
MATRIX=$(echo '${{ inputs.wp-versions }}' | jq -c '[.[] | if . == "latest" then {"input": "latest", "core": "null"} else {"input": ., "core": ("WordPress/WordPress#" + .)} end]')
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"

- id: multisite
run: |
case "${{ inputs.multisite }}" in
both) echo "matrix=[false, true]" >> "$GITHUB_OUTPUT" ;;
only) echo "matrix=[true]" >> "$GITHUB_OUTPUT" ;;
*) echo "matrix=[false]" >> "$GITHUB_OUTPUT" ;;
esac

visual-regression:
name: WP ${{ matrix.wp.input }}${{ matrix.multisite && ' / multisite' || '' }}
needs: matrix-prep
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
wp: ${{ fromJson(needs.matrix-prep.outputs.wp-versions) }}
multisite: ${{ fromJson(needs.matrix-prep.outputs.multisite) }}
steps:
- uses: actions/checkout@v4
with:
lfs: true

- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
coverage: none

- name: Install Composer dependencies
if: hashFiles('composer.json') != ''
run: composer install --no-interaction --prefer-dist

- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm

- name: Install Node.js dependencies
run: |
if [ -f package-lock.json ]; then
npm ci
else
npm install
fi

- name: Install wp-env
run: npm install -g @wordpress/env

- name: Validate wp-env configuration
run: |
if [ ! -f .wp-env.json ]; then
echo "::error::Missing .wp-env.json in repository root. See https://developer.wordpress.org/block-editor/reference-guides/packages/packages-env/"
exit 1
fi

- name: Configure wp-env override
if: matrix.wp.core != 'null'
run: |
echo '{ "core": "${{ matrix.wp.core }}" }' > .wp-env.override.json

- name: Start wp-env
run: npx wp-env start

- name: Convert to multisite
if: matrix.multisite
run: npx wp-env run cli wp core multisite-convert --title="Test Site"

- name: Start Mailpit
if: inputs.mailpit
run: |
docker run -d --name mailpit \
-p 1025:1025 \
-p 8025:8025 \
axllent/mailpit:latest

WP_CONTAINER=$(docker ps --format '{{.ID}}' --filter "name=wordpress" --no-trunc | head -1)
if [ -z "$WP_CONTAINER" ]; then
echo "::error::Could not find wp-env WordPress container"
exit 1
fi
WP_NETWORK=$(docker inspect -f '{{range $k, $_ := .NetworkSettings.Networks}}{{$k}} {{end}}' "$WP_CONTAINER" | awk '{print $1}')
docker network connect "$WP_NETWORK" mailpit

- name: Configure WordPress SMTP for Mailpit
if: inputs.mailpit
run: |

Check warning on line 144 in .github/workflows/reusable-wp-visual-regression.yml

View workflow job for this annotation

GitHub Actions / Lint workflows

[actionlint] reported by reviewdog 🐶 shellcheck reported issue in this script: SC2016:info:1:28: Expressions don't expand in single quotes, use double quotes for that [shellcheck] Raw Output: i:.github/workflows/reusable-wp-visual-regression.yml:144:15: shellcheck reported issue in this script: SC2016:info:1:28: Expressions don't expand in single quotes, use double quotes for that [shellcheck]
npx wp-env run cli wp eval '
$dir = ABSPATH . "wp-content/mu-plugins";
if (!is_dir($dir)) { mkdir($dir, 0755, true); }
file_put_contents($dir . "/mailpit-smtp.php",
"<?php\n"
. "add_filter(\"wp_mail_from\", function(\$email) {\n"
. " if (strpos(\$email, \"@localhost\") !== false) {\n"
. " return \"wordpress@example.tld\";\n"
. " }\n"
. " return \$email;\n"
. "});\n"
. "add_action(\"phpmailer_init\", function(\$phpmailer) {\n"
. " \$phpmailer->isSMTP();\n"
. " \$phpmailer->Host = \"mailpit\";\n"
. " \$phpmailer->Port = 1025;\n"
. " \$phpmailer->SMTPAutoTLS = false;\n"
. " \$phpmailer->SMTPAuth = false;\n"
. "});\n"
);
'

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Run visual regression tests
run: |
ARGS=""
if [ "${{ inputs.update-snapshots }}" = "true" ]; then
ARGS="--update-snapshots"
fi
npx playwright test $ARGS
env:
WP_BASE_URL: http://localhost:8888
WP_ADMIN_USER: admin
WP_ADMIN_PASSWORD: password
VRT_THRESHOLD: ${{ inputs.threshold }}
VRT_VIEWPORTS: ${{ inputs.viewports }}
VRT_COLOR_SCHEMES: ${{ inputs.color-schemes }}
MAILPIT_API_URL: ${{ inputs.mailpit && 'http://localhost:8025' || '' }}

- name: Upload visual diff artifacts
uses: actions/upload-artifact@v4
if: failure()
with:
name: visual-regression-diffs-wp${{ matrix.wp.input }}${{ matrix.multisite && '-multisite' || '' }}
path: test-results/
retention-days: 14

- name: Upload updated snapshots
uses: actions/upload-artifact@v4
if: inputs.update-snapshots
with:
name: updated-snapshots-wp${{ matrix.wp.input }}${{ matrix.multisite && '-multisite' || '' }}
path: |
**/__screenshots__/
**/screenshots/
retention-days: 7

- uses: actions/upload-artifact@v4
if: always()
with:
name: vrt-report-wp${{ matrix.wp.input }}${{ matrix.multisite && '-multisite' || '' }}
path: |
playwright-report/
test-results/
retention-days: 7
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `reusable-wp-e2e.yml` — optional `@axe-core/playwright` install via `a11y` input (#16)
- `reusable-wp-visual-regression.yml` — visual regression testing with Playwright (#18)

## [0.3.0] - 2026-04-06

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ All reusable workflows live in `.github/workflows/` with the `reusable-` prefix.
| `reusable-ci.yml` | PHP CI (test matrix, PHPStan, PHPCS) |
| `reusable-wp-integration.yml` | WP integration tests (real WP + MySQL matrix) |
| `reusable-wp-e2e.yml` | WP E2E tests (Playwright + wp-env) |
| `reusable-wp-visual-regression.yml` | WP visual regression tests (Playwright screenshots) |
| `reusable-release.yml` | CHANGELOG-driven release creation |
| `reusable-pr-validation.yml` | CHANGELOG entry validation |
| `reusable-conventional-commits.yml` | Commit message format validation |
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,39 @@ jobs:
wp-versions: '["latest", "6.4"]'
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The workflow file reusable-wp-visual-regression.yml is documented and added to the changelog, but the file itself is missing from the pull request. Please ensure the new workflow file is included in the commit.

### `reusable-wp-visual-regression.yml`

Playwright-based visual regression testing for WordPress. Captures screenshots and compares them against
committed baselines using Playwright's built-in `toHaveScreenshot()` API.

Caller repos must include a `.wp-env.json` in their root. The consuming repo's `playwright.config.ts` should
read the `VRT_THRESHOLD`, `VRT_VIEWPORTS`, and `VRT_COLOR_SCHEMES` environment variables to configure test
behavior. Baseline screenshots are stored in the repo (consider Git LFS for large sets).

| Input | Type | Default | Description |
|-------|------|---------|-------------|
| `node-version` | string | `"22"` | Node.js version |
| `wp-versions` | string (JSON) | `["latest"]` | WP versions (`"latest"`, `"6.7"`) |
| `multisite` | string | `"none"` | `"none"`, `"both"`, or `"only"` |
| `mailpit` | boolean | `false` | Run Mailpit mail catcher (SMTP `:1025`, API `:8025`) |
| `update-snapshots` | boolean | `false` | Regenerate baseline screenshots |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The default value of "0.2" for maxDiffPixelRatio is quite high for visual regression testing, as it allows up to 20% of pixels to differ before failing. For most projects, a much lower threshold (e.g., 0.01 or 0.05) is preferred to catch subtle visual regressions. If this was intended to be the color threshold (which defaults to 0.2 in Playwright), please clarify the description. Also, consider renaming the input to max-diff-pixel-ratio to match Playwright's terminology and avoid confusion with the color threshold option.

Suggested change
| `update-snapshots` | boolean | `false` | Regenerate baseline screenshots |
| `threshold` | string | `"0.05"` | Pixel diff tolerance (`maxDiffPixelRatio`) |

| `threshold` | string | `"0.2"` | Pixel diff tolerance (`maxDiffPixelRatio`) |
| `viewports` | string (JSON) | `["desktop", "mobile"]` | Viewport presets |
| `color-schemes` | string (JSON) | `["light"]` | Color scheme presets |

To accept a visual change, update the baseline by re-running the workflow with `update-snapshots: true`, then
download and commit the updated snapshots from the workflow artifacts.

```yaml
jobs:
visual-regression:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

According to the repository style guide, action versions should be pinned to the major version (e.g., @v0) rather than @main. This ensures stability for caller repositories.

Suggested change
visual-regression:
uses: apermo/reusable-workflows/.github/workflows/reusable-wp-visual-regression.yml@v0
References
  1. Action versions must be pinned to major (e.g. @v4, not @v4.1.2 or a SHA). (link)

uses: apermo/reusable-workflows/.github/workflows/reusable-wp-visual-regression.yml@main
with:
wp-versions: '["latest"]'
viewports: '["desktop", "mobile"]'
color-schemes: '["light", "dark"]'
```

### `reusable-ci.yml`

PHP CI pipeline with configurable test matrix, PHPStan, and PHPCS.
Expand Down
Loading