Migrate dev, e2e, and PHPUnit from wp-env to wp-playground-cli#91
Merged
Migrate dev, e2e, and PHPUnit from wp-env to wp-playground-cli#91
Conversation
Drops the Docker dependency end-to-end. `npm run dev:wp` boots WordPress via @wp-playground/cli, Playwright's globalSetup spawns a Playground instance for e2e, and PHPUnit runs against Playground's SQLite DB with the WP test library cloned into `wordpress-plugin/.wp-tests-lib/` on demand. Why: the agent sandbox can't run Docker, so wp-env was unusable. Playground's PHP-WASM + SQLite path works everywhere Node does. Notes: - PHPUnit skips the test library's install.php fork (`WP_TESTS_SKIP_INSTALL=1`) — Playground provides a fresh DB per boot, and mounts declared with --mount don't propagate to child PHP workers that system() spawns anyway. - `$table_prefix` matches Playground's default (`wp_`) so is_blog_installed() passes without reinstalling. - Plugin activation happens through `playground/phpunit.blueprint.json` so WP loads it on the normal muplugins_loaded path.
✅ Coverage Report — plugin-php
✅ Coverage Report — plugin-typescript
✅ Coverage Report — typescript
|
The file mirrors wordpress-develop's wp-tests-config-sample.php — the WP-standard constants (ABSPATH, DB_NAME, etc.) and the `$table_prefix` global override are intentional. Also silence two warnings whose root cause isn't visible to the sniffer: putenv(WP_TESTS_SKIP_INSTALL=1) in tests/bootstrap.php, and $table_prefix being read from wp-tests-config.php via PHPUnit's FileLoader scope hoisting.
There was a problem hiding this comment.
Pull request overview
This PR migrates the repo’s WordPress dev environment, Playwright e2e tests, and plugin PHPUnit tests away from wp-env/Docker to @wp-playground/cli (PHP-WASM + SQLite), enabling Docker-free development and CI.
Changes:
- Add Playground blueprints + scripts to run
dev:wp, e2e global setup, and PHPUnit inside Playground. - Replace
wp-env-based e2e helpers with Playground-aware helpers (server lifecycle + REST auth via app passwords). - Remove
@wordpress/env,wp-envconfigs, andafter-start-*scripts; update docs/ignores/CI accordingly.
Reviewed changes
Copilot reviewed 32 out of 38 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| wordpress-plugin/tests/wp-tests-config.php | Adds a dedicated wp-tests config for Playground/SQLite runs. |
| wordpress-plugin/tests/bootstrap.php | Skips WP test lib install and relies on Playground blueprint activation. |
| wordpress-plugin/phpcs.xml.dist | Excludes the cloned WP test library directory from PHPCS. |
| wordpress-plugin/package.json | Delegates PHPUnit scripts to repo-root Playground scripts. |
| wordpress-plugin/bin/fetch-wp-tests-lib.sh | Adds on-demand fetch of WP PHPUnit test library into .wp-tests-lib/. |
| wordpress-plugin/README.md | Updates prerequisites/test instructions for Playground (no Docker). |
| wordpress-plugin/.wp-env.test.json | Removes plugin-level wp-env test config. |
| wordpress-plugin/.distignore | Removes wp-env ignores; excludes .wp-tests-lib from distributions. |
| tests/e2e/test.ts | Switches fixtures/utilities to use new Playground helper module. |
| tests/e2e/pre-publish-panel.spec.ts | Updates imports and removes unused editor fixture usage. |
| tests/e2e/mu-plugins/enable-app-passwords.php | Adds MU plugin to force-enable app passwords under Playground/HTTP. |
| tests/e2e/helpers/wp-env.ts | Removes wp-env-based server lifecycle + REST helpers. |
| tests/e2e/helpers/playground.ts | Adds Playground server lifecycle, auth bootstrap, REST helpers, and retries. |
| tests/e2e/helpers/mcp.ts | Switches command listing helper to Playground implementation. |
| tests/e2e/helpers/editor.ts | Simplifies editor navigation helper to no longer depend on WP editor fixture. |
| tests/e2e/global-teardown.ts | Stops Playground instead of tearing down wp-env state. |
| tests/e2e/global-setup.ts | Ensures Playground is running before Playwright auth setup. |
| tests/e2e/deleted-post.spec.ts | Switches REST helpers import to Playground implementation. |
| tests/e2e/block-sync.spec.ts | Updates openEditor usage after helper signature change. |
| tests/e2e/ai-actions-sidebar.spec.ts | Switches REST helpers import and openEditor usage. |
| playwright.config.ts | Updates default base URL to 127.0.0.1. |
| playground/phpunit.blueprint.json | Blueprint to activate the plugin for PHPUnit runs. |
| playground/e2e.blueprint.json | Blueprint for e2e: installs Gutenberg, activates plugin, sets options. |
| playground/dev.blueprint.json | Blueprint for local dev: installs/activates and sets local-only const. |
| package.json | Adds dev:wp and switches plugin PHP test scripts to Playground CLI. |
| package-lock.json | Removes @wordpress/env and adds @wp-playground/cli dependency tree. |
| eslint.config.mjs | Ignores generated coverage + .wp-tests-lib directories. |
| bin/after-start-e2e.sh | Removes wp-env lifecycle script for e2e. |
| bin/after-start-dev.sh | Removes wp-env lifecycle script for dev. |
| README.md | Documents npm run dev:wp in root README. |
| CLAUDE.md | Updates build/test docs to reflect Playground-based workflows. |
| .wp-env.test.json | Removes root wp-env test config. |
| .wp-env.json | Removes root wp-env dev config. |
| .prettierignore | Ignores plugin coverage + .wp-tests-lib. |
| .markdownlint-cli2.jsonc | Ignores plugin coverage + .wp-tests-lib. |
| .gitignore | Ignores .wp-tests-lib/ and .claude/. |
| .github/workflows/ci.yml | Updates PHPUnit job to run Playground-based coverage; tightens e2e timeout. |
Review feedback: - tests/e2e/helpers/playground.ts: close the stdio log fd in the parent after spawn so it isn't leaked for the lifetime of the test run. - tests/e2e/helpers/playground.ts: reconcile the multi-worker count comment with the actual flag (13, not 8). - tests/e2e/helpers/editor.ts: drop the stale "disable welcome guide and fullscreen mode" claim from openEditor()'s docblock. - .github/workflows/ci.yml: the e2e job's pretest:e2e runs `composer install`, so provision PHP + Composer explicitly like the plugin-php jobs do. - wordpress-plugin/tests/bootstrap.php: the blueprint-activated plugin loads on the regular plugins_loaded sequence, not muplugins_loaded — correct the comment. Coverage: - Playground's --xdebug flag writes xdebug.ini with xdebug.mode=debug,develop, which takes precedence over any php.ini append. Patch the generated xdebug.ini to xdebug.mode=coverage via a blueprint runPHP step so PHPUnit's coverage driver actually runs and clover.xml gets written. Without this, the andstor/clover2lcov-action step fails with "Source file does not exist".
Vendors tarballs of WordPress/wordpress-playground#3494 under vendor/ and references them via a direct dependency (@wp-playground/cli) and an npm override (@php-wasm/universal). Without this patch the object-pool proxy returns a worker to the free list before its streamed response finishes; concurrent e2e requests then land on a mid-response worker and WordPress returns 500. Released builds (3.1.20) still have the bug. Verified: all 19 e2e tests pass locally with 4 Playwright workers (3.1m). Revisit once the upstream PR lands — drop vendor/ and bump to the next released version.
The flag is a no-op in released @wp-playground/cli builds (the worker count is hard-coded to 6 in the bundle), so passing it was misleading. Inheriting stdio instead of redirecting to a temp log file surfaces Playground output directly in the Playwright console, which is easier to debug than tailing a file after a failure.
apiFetch: the 5xx retry existed to mask @wp-playground/cli's pool-proxy bug which is now patched in vendor/. Without that bug, 5xx responses are real failures and retrying just hides them. Reverted to a single fetch. stopPlayground: previously deleted the state file unconditionally after SIGTERM, even if the process was still alive. If termination failed, the next run saw Playground responding but had no PID to clean it up, leaving an orphan. Now waits up to 2s for the process to exit and keeps the state file around if it doesn't, so the next run retries the kill.
CI is consistently 3-4× slower than local on the browser↔MCP handshake (browser joins command room → MCP broadcasts awareness → browser emits open-post signal → MCP opens the post). 30s for waitForMCPReady and 120s per test were fine locally but 15/19 MCP-dependent tests timed out on GitHub runners. Bumped to 90s and 180s respectively.
Addresses two PR review points: - writeState now writes with mode 0o600. The state file holds the shared admin application password; restricting perms keeps it out of reach of other users on shared CI boxes / dev machines. - startPlaygroundSubprocess picks wp-playground-cli.cmd on Windows and wp-playground-cli elsewhere, and resolves via path.join(REPO_ROOT, …) rather than a hard-coded ./node_modules/.bin prefix.
- Remove --php / --wp from dev:wp, test:plugin-php[:coverage], and the e2e helper. The matching preferredVersions in each blueprint is now the sole source of truth; the flags were duplicated configuration that could drift. - Move WP_TESTS_CONFIG_FILE_PATH from a CLI --define into the phpunit blueprint's defineWpConfigConsts step. Kept as the last step so it wins the known upstream consts.json write-order race. - README: note that `npm run test:php` and `npm run test:plugin-php` both shell out to wp-playground-cli from the repo root, so contributors need to run `npm install` there first. Only @mount, @mount-before-install, @xdebug, @blueprint, @PORT, and the positional script still live on the CLI — none of those have a blueprint equivalent.
- ensurePlaygroundRunning now only reuses state.appPassword when we're reusing an existing Playground instance. A fresh Playground has a fresh SQLite DB, so any cached credential is orphaned and would 401 — the bootstrap is cheap enough to just redo. - editor.ts header: drop the Editor-fixture mention; these helpers take a Playwright Page directly now. - wp-tests-config.php header: the VFS mount layout is defined by the repo-root test:plugin-php script (the plugin-local test:php is now a delegator), so point readers there.
Splits playwright test runs into 3 deterministic shards via --shard=N/3. Wall-clock per shard drops from ~18m to ~8.5m; combined with workers: 1 in CI the deleted-post spec's two slow tests land in separate shards, so the single-longest-test bound doesn't block the others. Timeout dropped from 20m to 15m per shard.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
npm run dev:wpboots WordPress via@wp-playground/cli, Playwright'sglobalSetupspawns a Playground instance for e2e, and PHPUnit runs against Playground's SQLite DB.wordpress-plugin/.wp-tests-lib/(gitignored) bywordpress-plugin/bin/fetch-wp-tests-lib.sh.@wordpress/envdevDep,.wp-env.json/.wp-env.test.json, andbin/after-start-*.sh.Why
The agent sandbox can't run Docker, so wp-env was unusable there. Playground's PHP-WASM + SQLite path works everywhere Node does, and halves e2e wall time in CI.
Notable non-obvious bits
WP_TESTS_SKIP_INSTALL=1). Playground provides a fresh DB per boot, and mounts declared with--mountdon't propagate to child PHP workers thatsystem()spawns anyway — so the test library'ssystem(install.php)fork couldn't find its mounted tests dir.$table_prefix = 'wp_'(matching Playground's default) sois_blog_installed()passes without a reinstall. Usingwptests_triggeredwp_not_installed()→wp_redirect() + die().playground/phpunit.blueprint.json, so WP loads the plugin on the normalplugins_loadedpath.xdebug.mode=coverage, but Playground's--xdebugflag writes its ownxdebug.iniwithxdebug.mode=debug,developthat wins overphp.ini. A blueprintrunPHPstep rewrites the generatedxdebug.iniso PHPUnit producesclover.xml.vendor/holds tarballs from WordPress/wordpress-playground#3494, referenced via a direct dependency and anoverridesentry. The released@wp-playground/cli@3.1.20returns a pool worker to the free list before its streamed response finishes, so concurrent e2e requests land on a mid-response worker and WordPress returns 500. Revisit once the upstream PR lands.Test plan
npm run typechecknpm run lintnpm test— 41 files, 1211 testsnpm run test:plugin-php— 131 tests, 279 assertions (≈3.4s)npm run test:e2e— 19 tests pass locally, 3.1m with 4 Playwright workers