Development

How to Test a WordPress Plugin Against Trunk Before a Major Release

Testing a WordPress plugin against trunk gives maintainers early warning before a major WordPress release. It does not make the development build suitable for production, and a green result today is not a permanent compatibility guarantee because trunk keeps changing.

Use an isolated environment, known fixtures, and the same test commands for stable and trunk. The useful signal is the difference between two comparable lanes.

Understand what you are testing

WordPress trunk is active development toward a future release. It may contain incomplete features, temporary breakage, changing APIs, and database schema work.

The release process normally provides alpha, beta, and release-candidate milestones. A trunk failure is an early signal to investigate. A failure on a late release candidate is more urgent because the change window is smaller.

Follow the WordPress beta-testing guidance and the release team's current test announcements. Confirm whether a failure is a plugin incompatibility or an upstream regression before changing production code.

Never update production to trunk

Use a disposable local, CI, or staging site that cannot send production email, charge payment methods, call customer webhooks, or overwrite shared storage.

Before a staging update, capture the database, required uploads, active extensions, exact WordPress and PHP versions, plugin commit, and environment configuration. Test restoration before relying on it. A backup that has never been imported is an assumption.

Build stable and trunk lanes with wp-env

@wordpress/env is the maintained WordPress development-environment tool used here. It resolves the selected core source, supplies the matching WordPress PHPUnit test files, exposes their path through WP_TESTS_DIR, and provides Composer, PHPUnit, and WP-CLI inside the CLI container.

Install and lock it in the plugin repository:

npm install --save-dev @wordpress/env

Create .wp-env.stable.json:

{
  "$schema": "https://schemas.wp.org/trunk/wp-env.json",
  "core": null,
  "phpVersion": "8.3",
  "plugins": ["."]
}

Create .wp-env.trunk.json with the single intentional difference:

{
  "$schema": "https://schemas.wp.org/trunk/wp-env.json",
  "core": "WordPress/WordPress#trunk",
  "phpVersion": "8.3",
  "plugins": ["."]
}

core: null resolves the latest production release. The trunk configuration checks out the current development branch. wp-env includes the core test library that corresponds to the installed WordPress source, which prevents a trunk site from accidentally running a stable test library.

Point the plugin's PHPUnit bootstrap at that supplied library:

<?php

declare(strict_types=1);

$testsDirectory = getenv('WP_TESTS_DIR');

if (!is_string($testsDirectory) || $testsDirectory === '') {
    throw new RuntimeException('WP_TESTS_DIR is not available.');
}

require_once $testsDirectory . '/includes/functions.php';

tests_add_filter(
    'muplugins_loaded',
    static function (): void {
        require dirname(__DIR__) . '/sample-plugin.php';
    }
);

require $testsDirectory . '/includes/bootstrap.php';

Replace the plugin entry file with the real one. This bootstrap loads the same plugin code in both lanes while core and its test library move together.

Make the CI matrix executable

A matrix label is useful only when setup resolves it. These GitHub Actions steps turn stable and trunk into concrete wp-env configurations:

strategy:
  fail-fast: false
  matrix:
    wordpress: [stable, trunk]

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: '22'
      cache: npm
  - run: npm ci
  - run: cp ".wp-env.${{ matrix.wordpress }}.json" .wp-env.json
  - run: npx wp-env start --update
  - run: >-
      npx wp-env run cli
      --env-cwd=wp-content/plugins/sample-plugin
      composer install --no-interaction
  - run: npx wp-env run cli wp core version --extra
  - run: >-
      npx wp-env run cli
      --env-cwd=wp-content/plugins/sample-plugin
      phpunit -- -c phpunit.xml.dist
  - if: always()
    run: npx wp-env stop

Use the plugin's real mapped directory. Record php -v, wp core version --extra, the plugin commit, Composer lockfile identity, and PHPUnit result as job artifacts. Add static analysis and coding standards separately; they do not replace a WordPress integration test.

Keep WP-CLI nightly for an interactive site

For an explicitly disposable staging site, WP-CLI can still update core to a nightly build:

wp core update --version=nightly --force
wp core version --extra
wp core update-db

Use the current wp core update documentation for accepted version values and flags. Record the exact build, confirm the SSH target before updating, and never reuse the command against production.

The WordPress Beta Tester plugin is another useful interactive route. Neither interactive option replaces the synchronized PHPUnit lane above.

Use one compatibility evidence matrix

Define the fixtures, critical surface, and expected evidence together. This prevents three overlapping checklists from drifting:

ScenarioFixture and actionExpected evidence
Fresh installEmpty WordPress database, install and activate candidate pluginRequired tables, options, roles, rewrites, and scheduled events exist once; no warnings
Existing-site upgradeSanitized fixture from previous plugin release, then candidate upgrade twiceSchema and data remain valid; permissions and migrations are idempotent
Admin and settingsRepresentative roles submit valid and invalid settingsCapabilities and nonces are enforced; stored values and errors match the contract
Front endPublished fixtures for blocks, shortcodes, widgets, and templatesStable output, escaping, localization, and time-zone behavior on stable and trunk
REST and AJAXAuthorized, unauthorized, malformed, and boundary requestsStatus codes, schemas, nonce or authentication behavior, and bounded work match
Cron and queuesDue, duplicate, and failed jobs with outbound services fakedEach job is idempotent; retry state and logs are correct; no live side effect occurs
Data and platform APIsRealistic bounded data using database, cache, media, filesystem, and HTTP fakesQueries, upgrades, cache invalidation, and failure handling remain equivalent
Public extension hooksTest consumers attached at documented prioritiesNames, timing, arguments, return values, and call counts remain compatible
Deactivation and uninstallSeparate disposable copy with scheduled work and retained dataDeactivation cleanup runs; uninstall follows the documented retention policy

Run every applicable row on stable first and then trunk with the same PHP version, database version, plugin artifact, theme, fixtures, and commands. Disable production credentials, personal data, mail delivery, payments, and customer webhooks in both lanes.

Turn on WordPress debugging and collect deprecations:

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
define('SCRIPT_DEBUG', true);

Treat new notices as findings even when the request still succeeds. A deprecated call is exactly the early warning this lane should expose.

Decide whether trunk blocks merges

Stable supported versions should remain required. Run trunk on each pull request or nightly as an early-warning lane, assign a maintainer to triage new failures, and make confirmed plugin incompatibilities blocking before the relevant release candidate.

Confirmed upstream failures should link to a minimal reproduction and remain visible without weakening stable gates. A permanently required trunk job can stop development during a transient core regression, while a permanently ignored optional job becomes noise. Document the owner and escalation point.

Minimize a failure before fixing it

When stable passes and trunk fails:

  1. Record the exact WordPress build and plugin commit.
  2. Re-run from a clean fixture.
  3. Reduce the failing test or request.
  4. Disable unrelated plugins and use a default theme where appropriate.
  5. Inspect the core change that introduced the behavior.
  6. Confirm whether the plugin used a public API correctly.
  7. Test the smallest compatible correction on both lanes.

Prefer public APIs and feature detection to version-string branches. If core appears broken, report a minimal reproduction that does not depend on proprietary plugin code.

Rehearse rollback for shared staging

Announce the test window, record the stable version, export the database, retain a matching stable installation method, and identify who can approve restoration. Rollback means restoring code and data to a verified matching state. Downgrading files while retaining a database changed by trunk may not recreate the original environment.

As release candidates arrive, rerun the same evidence matrix against each candidate and the final build. Update “Tested up to” only after testing the exact final release claimed.

Trunk testing is radar, not a certificate. A maintained environment tool, synchronized core tests, comparable stable and trunk lanes, explicit evidence, prompt triage, and a rehearsed restore path turn that radar into a useful release practice.