Skip to content

Expand Playwright E2E and migrate local dev to wp-env (XWPENG-44, XWPENG-45) - #1977

Open
shadyvb wants to merge 2 commits into
ticket/XWPENG-43-phpunit-11from
ticket/XWPENG-45-wp-env
Open

Expand Playwright E2E and migrate local dev to wp-env (XWPENG-44, XWPENG-45)#1977
shadyvb wants to merge 2 commits into
ticket/XWPENG-43-phpunit-11from
ticket/XWPENG-45-wp-env

Conversation

@shadyvb

@shadyvb shadyvb commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stacks on Upgrade PHPUnit to 11.x and reconcile with host unit-test tier (XWPENG-43) #1974 (ticket/XWPENG-43-phpunit-11).
  • XWPENG-45: Replaces Docker Compose local dev with @wordpress/env (.wp-env.json): multisite setup, Email Logger mu-plugin, lifecycle scripts, and env.tests.mappings for PHPUnit; removes legacy Docker assets and updates CI/contributing docs.
  • XWPENG-44: Adds five Playwright E2E specs (alert-create, export-download, network-activated-records, records-filter, settings-save) plus shared tests/e2e/helpers/stream-plugin.js; updates existing specs and Playwright config for wp-env.

Test plan

@shadyvb
shadyvb force-pushed the ticket/XWPENG-45-wp-env branch from ba6eb08 to 4844598 Compare September 1, 2026 10:40
@shadyvb
shadyvb force-pushed the ticket/XWPENG-45-wp-env branch from 4844598 to 0067067 Compare September 1, 2026 10:48
@shadyvb
shadyvb requested review from PatelUtkarsh and bartoszgadomski and a balanced review from Copilot September 1, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Migrates local development and testing from Docker Compose to wp-env, while expanding Playwright coverage and aligning with stacked PR #1974.

Changes:

  • Adds wp-env configuration and removes legacy Docker assets.
  • Adds shared E2E helpers and five Playwright specifications.
  • Updates PHPUnit, CI, documentation, and editor tooling.

Review assessment:

  • Critical: None.
  • High: E2E exclusion cleanup has a concurrency/data-loss risk.
  • Medium/Low: Several migrated npm scripts regress behavior; the debug documentation is ineffective.

Reviewed changes

Copilot reviewed 39 out of 41 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
.distignore Removes obsolete Compose exclusions.
.github/workflows/docker-images.yml Removes legacy image publishing.
.github/workflows/lint-and-test.yml Migrates CI to wp-env.
.gitignore Ignores wp-env overrides.
.vscode/launch.json Updates wp-env path mappings.
.wp-env.json Defines multisite wp-env environments.
contributing.md Documents wp-env workflows.
docker-compose.build.yml Removes image build configuration.
docker-compose.yml Removes Compose environment.
local/docker/mkcert/Dockerfile Removes certificate image.
local/docker/wordpress/Dockerfile Removes custom WordPress image.
local/docker/wordpress/apache-ssl.conf Removes legacy TLS configuration.
local/docker/wordpress/php.ini Removes custom PHP configuration.
local/public/.htaccess Removes legacy rewrite rules.
local/public/index.php Removes legacy front controller.
local/public/wp-cli.yml Removes Compose WP-CLI configuration.
local/public/wp-config.php Removes legacy WordPress configuration.
local/public/wp-content/sunrise.php Removes legacy Mercator bootstrap.
local/public/wp-tests-config.php Removes Compose PHPUnit configuration.
package.json Migrates development and test commands.
phpunit-multisite.xml Activates additional test plugins.
phpunit.xml Activates Email Logger in tests.
playwright.config.js Adds authenticated, ordered projects.
readme.md Corrects the CI badge.
tests/bootstrap.php Resolves wp-env PHPUnit paths.
tests/e2e/admin-orphan-cleanup.spec.js Uses shared wp-env helpers.
tests/e2e/admin-ui-smoke.spec.js Uses authenticated contexts.
tests/e2e/alert-create.spec.js Tests highlight alert creation.
tests/e2e/editor-new-post.spec.js Updates authentication and cleanup.
tests/e2e/export-download.spec.js Tests CSV and JSON exports.
tests/e2e/helpers/stream-plugin.js Adds shared Stream E2E utilities.
tests/e2e/network-activated-records.spec.js Tests multisite record visibility.
tests/e2e/network-activated.spec.js Isolates activation-state testing.
tests/e2e/records-filter.spec.js Tests record-list filters.
tests/e2e/settings-save.spec.js Tests settings persistence.
tests/e2e/setup/setup.js Ensures network activation and cleanup.
tests/phpunit/Abilities_REST_Integration_Test.php Updates unknown-ability handling.
tests/phpunit/Alerts_List_Test.php Derives the alert nonce path.
tests/phpunit/connectors/Connector_Installer_Test.php Uses the displayed theme name.
tests/wp-tests-config-wp-env.php Adds tests-cli database configuration.
Suppressed comments (1)

package.json:57

  • Composer installs this executable under vendor/bin, but npm only adds node_modules/.bin to PATH, so this command fails with php-coveralls: not found on a normal checkout. Invoke the existing Composer script, which resolves Composer binaries correctly.
    "test-report": "php-coveralls --verbose",

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

*
* @param {string} [ipAddress] IP of the row this run added (TEST-NET-3).
*/
export function clearNetworkExcludeRulesViaWpCli( ipAddress = '203.0.113.44' ) {
Comment thread package.json
Comment on lines +55 to +56
"test-xdebug": "wp-env start --xdebug && wp-env run tests-cli --env-cwd=wp-content/plugins/stream -- vendor/bin/phpunit",
"test-one": "wp-env run tests-cli --env-cwd=wp-content/plugins/stream -- vendor/bin/phpunit --",
Comment thread contributing.md
By default, tests have `WP_DEBUG` as false. To enable it, prefix the PHPUnit command with `WP_STREAM_TEST_DEBUG=yes`, for example:

```sh
WP_STREAM_TEST_DEBUG=yes npm run test:php
Add five specs plus a shared helper, keep Stream network-activated so
workers can run in parallel, and isolate settings-ui and network-activated
projects so shared settings saves and plugin toggles do not race.
Replace the custom Docker Compose stack with @wordpress/env so contributors get a standard WordPress multisite environment without maintained images or checked-in bootstrap files; align CI, PHPUnit, Playwright, and docs with the new workflow.
@shadyvb
shadyvb force-pushed the ticket/XWPENG-45-wp-env branch from 0067067 to 1a4675d Compare September 1, 2026 15:22

@bartoszgadomski bartoszgadomski left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@shadyvb Thank you for working on this PR! Please check AI-assisted inline comments below.

Comment thread playwright.config.js
* record. Concurrent saves race on the success notice, so they run in the
* serial `settings-ui` project instead of `chromium`.
*/
const settingsUiSpecs = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Race: settings-save.spec.js is not isolated.

It is not in settingsUiSpecs and it is not networkActivatedSpec, so it is not covered by the chromium testIgnore on line 68 and runs in chromium with fullyParallel: true (2 workers on CI, 4 locally) — while it mutates network-wide Stream settings (records TTL, cron tracking, exclude IP).

Concretely: network-activated-records.spec.js also runs in chromium, and its beforeEach calls clearNetworkExcludeRulesViaWpCli() with no argument. The helper's default parameter is '203.0.113.44' — the same IP settings-save saves and then asserts on after reload. A concurrent worker can delete that row between the save and the assertion.

Suggest adding '**/settings-save.spec.js' to settingsUiSpecs (it saves shared settings, which is exactly the reason that project exists), and changing the no-arg helper default to clear only empty rows so global cleanup cannot delete a rule another spec owns.

Comment thread phpunit.xml
</coverage>
<php>
<const name="WP_TEST_ACTIVATED_PLUGINS" value="advanced-custom-fields/acf.php,easy-digital-downloads/easy-digital-downloads.php,jetpack/jetpack.php,user-switching/user-switching.php,wordpress-seo/wp-seo.php,two-factor/two-factor.php"/>
<const name="WP_TEST_ACTIVATED_PLUGINS" value="advanced-custom-fields/acf.php,easy-digital-downloads/easy-digital-downloads.php,jetpack/jetpack.php,user-switching/user-switching.php,wordpress-seo/wp-seo.php,two-factor/two-factor.php,wp-email-logger/wp-email-logger.php"/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two things about force-activating wp-email-logger in the PHPUnit suites:

  1. It generates Stream records. On multisite the plugin stores its log via update_site_option( 'network_emails_log', … ). Stream's Settings connector registers update_site_option, and callback_updated_option() logs any option not in the small is_option_ignored() list (_transient_*, _site_transient_*, trailing $, and three image/size options) — network_emails_log isn't excluded. So every mail sent during the multisite suite also writes a settings/updated record whose value is the flattened email log. The suite passes today, but it is hidden coupling for any record count/ordering assertion, plus larger record meta. (Single-site is unaffected: callback_update_option() only logs for WP-CLI/customizer and only for options in $labels.)
  2. Is it needed for PHPUnit at all? alert-create.spec.js deliberately uses the Highlight alert type rather than asserting via Email Logger, so this dependency reads as E2E/manual-testing oriented. Leaving it out of WP_TEST_ACTIVATED_PLUGINS would avoid both the record noise and making the integration suite depend on the make.wordpress.org zip being reachable.

For the record, the zip layout is fine — it contains wp-email-logger/wp-email-logger.php, so the path used here is correct.

Comment thread phpunit-multisite.xml
</coverage>
<php>
<const name="WP_TEST_ACTIVATED_PLUGINS" value="advanced-custom-fields/acf.php,easy-digital-downloads/easy-digital-downloads.php,jetpack/jetpack.php,user-switching/user-switching.php,two-factor/two-factor.php"/>
<const name="WP_TEST_ACTIVATED_PLUGINS" value="advanced-custom-fields/acf.php,easy-digital-downloads/easy-digital-downloads.php,jetpack/jetpack.php,user-switching/user-switching.php,wordpress-seo/wp-seo.php,two-factor/two-factor.php,wp-email-logger/wp-email-logger.php"/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This also adds wordpress-seo/wp-seo.php, which was previously single-site only (it was in phpunit.xml but not here). tests/bootstrap.php still contains:

if ( ! is_multisite() ) {
	activate_plugin( 'wordpress-seo/wp-seo.php' );
}

Since the constant is applied through the site_option_active_sitewide_plugins filter, Yoast is now network-active for the whole multisite suite while the bootstrap still deliberately skips activating it there — the two now disagree.

Intentional? If yes, the bootstrap guard is misleading and should go (or get a comment); if it was just config alignment, this quietly changes what loads for every multisite test.

Comment thread .wp-env.json
"SCRIPT_DEBUG": true
},
"lifecycleScripts": {
"afterStart": "wp-env run cli -- wp plugin activate stream wp-email-logger --network"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wp plugin activate stream assumes the plugin is mounted at wp-content/plugins/stream, but wp-env derives that name from the checkout directory, not the plugin slug. For a local source like ".", packages/env/lib/config/parse-source-string.js does const basename = path.basename( sourcePath ).

So a clone into stream-plugin/, stream-wp/, stream-2/, etc. mounts at wp-content/plugins/<dir name> and then:

  • this afterStart script fails (stream is not installed),
  • --env-cwd=wp-content/plugins/stream in the test:* scripts fails,
  • the launch.json path mapping silently stops matching.

CI is unaffected (checkout dir = repo name), so this is a contributor sharp edge. Either resolve the directory name dynamically or document in contributing.md that the clone must be named stream.

Comment thread package.json
"test:php": "npm run cli -- composer test --working-dir=wp-content/plugins/stream-src",
"test:php-multisite": "npm run cli -- composer test-multisite --working-dir=wp-content/plugins/stream-src",
"test:php-unit": "wp-env start && wp-env run tests-cli --env-cwd=wp-content/plugins/stream -- composer test-unit",
"test:php": "wp-env start && wp-env run tests-cli --env-cwd=wp-content/plugins/stream -- vendor/bin/phpunit --no-coverage",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These now bypass the Composer scripts rather than delegating to them, which changes behavior beyond the container swap:

  • composer test is phpunit --coverage-text + make-clover-relative.php. With --no-coverage and no post-step, tests/reports/clover.xml is never written — so test-report (php-coveralls, line 57) has nothing to upload, and the coveralls badge in readme.md can't be refreshed from this path.
  • test:php-multisite (line 52) re-declares inline what composer test-multisite already owns, so the multisite suite config now lives in two places.
  • test-xdebug (line 55) and test-one (line 56) each run a single suite, while their Composer counterparts run single-site and multisite.

Delegating to composer test / test-multisite / test-xdebug / test-one inside wp-env run tests-cli keeps one source of truth — test:php-unit on line 50 already does exactly that.

Minor: every test:* script prefixes wp-env start, and CI already starts the env in its own step, so a full npm run test boots wp-env four times. Probably part of why the job timeout went 10 → 20.

* active; this call is synchronous.
*/
export function networkActivateStreamViaWpCli() {
const slugs = [ 'stream', 'stream-src' ];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

networkActivateStreamViaWpCli() has no call sites in this PR (only the definition), and the stream-src fallback is the Compose-era mount name that no longer exists under wp-env — plugins: ["."] mounts at the checkout directory name, never stream-src. So the fallback can only ever fail before rethrowing.

Either drop the function, or if it is kept for upcoming specs, derive the slug from the actual mount name rather than hardcoding two guesses.

Comment thread .vscode/launch.json
"/var/www/html/wp-content/plugins/stream": "${workspaceFolder}/build",
"/var/www/html": "${workspaceFolder}/local/public"
"/var/www/html/wp-content/plugins/stream": "${workspaceFolder}",
"/var/www/html": "${workspaceFolder}/local/public/wp"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wp-env doesn't serve core from local/public/wp — that path is the Composer (roots/wordpress) install from extra.wordpress-install-dir. The container's /var/www/html is wp-env's own WordPress under ~/.wp-env/<hash>/WordPress.

As written, Xdebug maps container core files onto a different WordPress copy, so stepping into core lands in files that only coincidentally match. The plugin mapping on line 13 is the one that matters here; I'd drop this entry (or point it at the wp-env directory).


- name: Pull custom Docker images
run: docker compose pull wordpress
- name: Start wp-env

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Leftovers now that GHCR and Compose are gone:

  • WORDPRESS_IMAGE_VERSION: php8.2 (line 9) no longer has a consumer — PHP is pinned by phpVersion in .wp-env.json, and wp-env's env override is WP_ENV_PHP_VERSION (which switch-to:php* uses).
  • packages: read (lines 19 and 73) was only needed for the ghcr.io login this PR removes.

Both are harmless, just dead config worth clearing while this file is already being touched.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants