Skip to content

Expand Playwright E2E, migrate to wp-env, PHP 8.2 minimum, and modernize internals (XWPENG-44–47) - #1977

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

Expand Playwright E2E, migrate to wp-env, PHP 8.2 minimum, and modernize internals (XWPENG-44–47)#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

Fixes XWPENG-44, XWPENG-45, XWPENG-46, XWPENG-47.

Base branch: ticket/XWPENG-43-phpunit-11 (#1974). This PR stacks on the PHPUnit 11 migration and delivers local-dev migration, expanded E2E coverage, the PHP 8.2 minimum / 5.0.0 release alignment, Settings/DB hardening, and a Rector-driven modernization pass.

Summary

XWPENG-45 — wp-env local dev & CI

  • Replaces Docker Compose with @wordpress/env (.wp-env.json): multisite, Email Logger mu-plugin, lifecycleScripts.afterStart network activation, and env.tests.mappings for PHPUnit helper plugins.
  • Removes legacy Docker assets (docker-compose*.yml, local/docker/, local/public/ WordPress bootstrap) and the docker-images.yml workflow.
  • Updates lint-and-test.yml, deploy workflows, contributing.md, tests/bootstrap.php, and tests/wp-tests-config-wp-env.php for wp-env.
  • Adds switch-to:php8.2|8.3|8.4 npm scripts and documents MCP / Application Passwords on localhost.

XWPENG-44 — Playwright E2E

  • Adds five specs: alert-create, export-download, network-activated-records, records-filter, settings-save.
  • Adds shared helper tests/e2e/helpers/stream-plugin.js; updates existing specs and playwright.config.js for wp-env URLs and setup.

XWPENG-46 — PHP 8.2 minimum & 5.0.0 release

  • Raises minimum PHP from 7.2 to 8.2 with an admin notice (no fatal) for unsupported runtimes; bumps plugin version to 5.0.0 in stream.php, class-plugin.php, and readme.txt.
  • Aligns Composer require.php, PHPCS testVersion, CI matrix (8.2 / 8.3 / 8.4), and changelog.md.
  • Adds Fail_Php_Version_Unit_Test and Connectors_Unit_Test coverage for the version gate.

XWPENG-47 — Settings filter boundaries & DB driver contract

  • Guards wp_stream_settings_option_key, wp_stream_settings_options, and wp_stream_settings_option_fields so non-string / non-array filter returns fall back safely before typed-property assignment.
  • Declares DB_Driver::setup_storage(): Install on the interface and WPDB implementation (breaking for third-party drivers omitting the return type).

Codebase modernization (Rector 2.6)

  • Adds rector.php and composer lint-rector / composer rector scripts.
  • Applies typed properties across classes/, connectors, alerts, and exporters; constructor property promotion; two switchmatch conversions.
  • Extracts Plugin::boot() from the constructor.
  • Splits Admin into focused collaborators (Admin_Menu, Admin_Assets, Admin_Screen_Records, Admin_Screen_Settings, Admin_Ajax, Admin_Purge) with colocated hook registration and dedicated PHPUnit coverage.

Commits on this PR (ticket/XWPENG-43-phpunit-11..HEAD)

Commit Description
ae576e71 XWPENG-44 — Expand Playwright E2E coverage for Stream admin flows.
1a4675dc XWPENG-45 — Migrate local dev and test tooling to wp-env.
0c21fd72 XWPENG-46 — Raise minimum PHP to 8.2; matrix CI for supported runtimes.
0956d649 XWPENG-46 — Align release to 5.0.0; PHP version gate unit tests.
2c0932e5 Add Rector 2.6 dev tooling and empty rector.php.
47ed6f88 Apply typed properties in classes/ via Rector.
ea122a51 XWPENG-47 — Harden Settings filter boundaries; tighten DB_Driver::setup_storage contract.
49cde6ba Type the Connector family (22 children).
3a0f708c Type Alert_Type, Alert_Trigger, and Exporter families.
77ce1e09 Apply constructor property promotion via Rector.
3ad23fc1 Convert two switch statements to match expressions.
13f24b07 Extract Plugin::boot() from the constructor.
251a8f98 Split Admin god-class into focused collaborator classes.
ef7ac652 Fix PHPCS formatting in Admin collaborator tests.

Test plan

Checklist

  • Project documentation has been updated to reflect the changes in this pull request, if applicable.
  • I have tested the changes in the local development environment (see contributing.md).
  • I have added phpunit tests.

Release Changelog

  • Fix: Guard Settings filter callbacks so non-array / non-string returns cannot cause TypeErrors when typed properties are assigned (XWPENG-47).
  • New: N/A (see breaking change below).
  • Breaking: Minimum PHP raised to 8.2; plugin version 5.0.0 (XWPENG-46). Third-party DB_Driver implementations must declare setup_storage(): Install (XWPENG-47).

Release Checklist

  • This pull request is to the master branch.
  • Release version follows semantic versioning. Does it include breaking changes? Yes — 5.0.0, PHP 8.2 minimum.
  • Update changelog in readme.txt.
  • Bump version in stream.php.
  • Bump Stable tag in readme.txt.
  • Bump version in classes/class-plugin.php.
  • Draft a release on GitHub.

@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.

@shadyvb shadyvb changed the title Expand Playwright E2E and migrate local dev to wp-env (XWPENG-44, XWPENG-45) Expand Playwright E2E, migrate to wp-env, PHP 8.2 minimum, and modernize internals (XWPENG-44–47) Sep 7, 2026
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