Virtual pages for block/theme visual regression testing in WordPress.
- Block example:
/wp-vrt/block/core-paragraph - Block variation:
/wp-vrt/block/core-button/outline - Pattern:
/wp-vrt/pattern/{pattern-slug} - Template:
/wp-vrt/template/{template-slug} - Template part:
/wp-vrt/template-part/{part-slug} - Scenario:
/wp-vrt/scenario/{scenario-slug}
/wp-json/wp-vrt/v1/discover
Returns a manifest of blocks, patterns, templates, template parts, and scenarios with URLs.
Install dev dependencies:
composer installInstall Playwright for screenshots:
npm install -D playwrightInstall dependencies and build the admin UI bundle:
npm install
npm run buildFor development:
npm run startRun tests:
WP_VRT_BASE_URL="http://bedrock.test" composer testYou can override the Node binary:
NODE_BINARY="/usr/local/bin/node" composer testTo store screenshot PNGs in wp-content/wp-vrt-snapshots, set:
WP_VRT_SNAPSHOT_DIR="/path/to/wp-content/wp-vrt-snapshots" composer testRegister custom scenarios from your theme or another plugin.
add_filter('wp_vrt_register_scenarios', function ($scenarios) {
$scenarios['button-states'] = [
'title' => 'Button - All States',
'description' => 'Default and outline buttons',
'content' => '
<!-- wp:buttons -->
<div class="wp-block-buttons">
<!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link">Default</a></div>
<!-- /wp:button -->
<!-- wp:button {"className":"is-style-outline"} -->
<div class="wp-block-button is-style-outline"><a class="wp-block-button__link">Outline</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
',
];
return $scenarios;
});You can also use a callable for content if you need custom logic.
WP VRT does not ship any built-in scenarios. Scenarios are expected to be registered by your theme or other plugins.
| Scenario | Coverage | Notes |
|---|---|---|
| (none) | N/A | Register scenarios via wp_vrt_register_scenarios |
By default, WP VRT includes all registered blocks. You can narrow it down:
// Only include these blocks
add_filter('wp_vrt_block_allowlist', function () {
return ['core/paragraph', 'core/heading', 'acf/hero'];
});
// Or exclude specific blocks
add_filter('wp_vrt_block_denylist', function () {
return ['core/legacy-widget', 'plugin/experimental-block'];
});By default, WP VRT excludes the following blocks from discovery:
| Block | Reason |
|---|---|
core/social-link-* (39 variants) |
Consolidated into single core/social-links block with all 39 social platforms as children |
core/template-part |
Cannot be meaningfully previewed without template context |
core/pattern |
Cannot be meaningfully previewed without theme context |
core/widget-group |
Requires legacy widget context |
This results in 87 discoverable blocks instead of 129, with all blocks having meaningful sample content.
You can disable items from being exposed by default. Disabled items are hidden unless you enable the flag.
add_filter('wp_vrt_is_item_enabled', function ($enabled, $type, $id, $item) {
if ($type === 'block' && $id === 'core/paragraph') {
return false;
}
return $enabled;
}, 10, 4);
add_filter('wp_vrt_template_part_enabled', function ($enabled, $template) {
return $template->slug !== 'header';
}, 10, 2);Show disabled items:
- Admin UI: toggle on
Tools → WP VRT - Discovery:
/wp-json/wp-vrt/v1/discover?include_disabled=1
To toggle items from the UI, use the disable/enable icon on each row.
The admin UI groups blocks that are commonly used together. You can add or override families:
add_filter('wp_vrt_block_families', function ($families) {
$families[] = [
'title' => 'Accordion',
'description' => 'Accordion with item blocks.',
'blocks' => ['my/accordion', 'my/accordion-item'],
];
return $families;
});Dynamic blocks render from attributes only. Static blocks still require inner markup for rendering on the frontend, so WP VRT provides minimal HTML for core blocks. You can override per block:
add_filter('wp_vrt_block_content', function ($content, $block_name) {
if ($block_name === 'core/paragraph') {
return [[
'attrs' => ['content' => 'Custom paragraph text'],
'content' => '<p>Custom paragraph text</p>',
]];
}
return $content;
}, 10, 2);Generate PNG snapshots into wp-content/wp-vrt-snapshots:
wp vrt snapshots --types=blocks --width=1200 --height=800Options:
--types=blocks,patterns,templates,parts,scenarios--base-url=https://example.test--dir=/path/to/wp-content/wp-vrt-snapshots--width=1200--height=800--node=/path/to/node
Generate snapshots and run Pest in one command:
wp vrt test --base-url="http://bedrock.test"Dynamic blocks that require post context (like query, post content, comments, etc.) are supported by providing a post context for rendering.
To provide a specific post ID for context:
add_filter('wp_vrt_dynamic_post_id', function () {
return 123;
});If no post ID is provided, WP VRT automatically:
- Uses the first published post if one exists
- Creates a temporary draft post and deletes it after rendering if no posts exist