Skip to content

Build/Test Tools: Measure wp-admin in the browser and check performance budgets - #15957

Open
rene-hermenau wants to merge 4 commits into
WordPress:trunkfrom
rene-hermenau:perf-ci-admin-budgets
Open

rene-hermenau wants to merge 4 commits into
WordPress:trunkfrom
rene-hermenau:perf-ci-admin-budgets

Conversation

@rene-hermenau

@rene-hermenau rene-hermenau commented Nov 12, 2026 •

Copy link
Copy Markdown

This PR changes only the performance tests and their workflow. No file under src/ is touched. It lets the tests see what a change costs in the browser on wp-admin, and adds a budget check to the Compare job that fails a pull request when it makes wp-admin measurably slower.

Performance Budget Check failing for the Command Palette change with 8 errors for slower screens and 16 warnings for added scripts and stylesheets

What the new check reports for the Command Palette change from [61022]. Rendered locally from the output of check-regressions.js.

Commits Files Lines
Browser metrics on admin screens tests/performance/specs/admin.test.js, tests/performance/utils.js, tests/performance/log-results.js +116 / −57
Budget check (3 commits) tests/performance/check-regressions.js (new), .github/workflows/performance.yml, .github/workflows/reusable-performance-report-v2.yml +348

The metrics and the budget check can land separately if the budget check needs more discussion.

Why

Today the admin performance spec records only Server-Timing and time to first byte on the Dashboard, and the Compare job never fails. That is how [61022] (#63845) added about 40 scripts and 5.4 MiB of JavaScript to every classic admin screen without a visible change in the results. The fix for that regression is in #66642; this PR is about catching the next one.

Video: what that regression feels like on Settings › General (background only, not part of this PR)

Trunk on the left, the same build without the eager palette enqueue on the right. Cold cache, Chromium with 4× CPU throttling, 20 Mbit/s and 40 ms latency, real time. From 450 ms on, the Help button is clicked every 150 ms (red circle) until the Help panel opens: after about 1,740 ms and 6 clicks on trunk, after about 880 ms and 2 clicks without the palette.

4-settings-general-side-by-side.mp4

1. Browser metrics on admin screens

  • admin.test.js measures General Settings (options-general.php) next to the Dashboard, as the new suites admin-general-settings and admin-general-settings-l10n. The Dashboard keeps its suite name, so its history on codevitals.run continues.
  • New metrics: domContentLoaded from metrics.getLoadingDurations(), and scriptCount, scriptBytes, stylesheetCount, stylesheetBytes for the scripts and stylesheets in the page markup. Sizes use Resource Timing's encodedBodySize; transferSize would count the toolbar assets as 0 bytes, because the /?clear_cache request before each measurement has already cached them.
  • formatValue() shows counts as plain numbers and sizes in KiB.
  • Each suite starts from an empty results object, so the de_DE suites no longer include the en_US Server-Timing values.

2. Budget check

The goal is that wp-admin does not get slower. Code grows with every feature, so growth on its own does not block a change, but reviewers should see it.

  • New tests/performance/check-regressions.js, run in the Compare job, compares the medians of this change with the target commit and with the last release.
  • A metric regresses when it grows by more than an absolute and a relative budget. Timings (DOMContentLoaded, LCP, TTFB, server time) must also grow by more than three times the combined robust spread (MAD) of both samples.
  • Fails: a screen gets slower and the same screen loads more scripts or stylesheets, runs more database queries or uses more memory. The growth shows that the slowdown comes from the change, not from runner noise.
  • Reported for review, does not fail: growth without a slowdown, for example 100 KiB more JavaScript that does not make the screen slower. It shows up in the summary and as a warning annotation.
  • Warns: a slowdown without any growth, since timings on shared runners vary.
  • The comparison with the last release is reported but never fails.
  • The Accepted Performance Regression label marks a regression as reviewed, so the job reports it without failing.
  • Results go to the top of the job summary and to workflow annotations.

Results

Local run on trunk, with and without remove_action( 'admin_enqueue_scripts', 'wp_enqueue_command_palette_assets' ). CI-like environment (build, debug constants off, cron and external HTTP off), TEST_RUNS=10, 2 repetitions, medians.

General Settings, en_US without palette trunk
scriptCount 38 80
scriptBytes 716.79 KiB 6250.49 KiB
stylesheetCount 26 29
stylesheetBytes 504.01 KiB 630.49 KiB
domContentLoaded 47.50 ms 226.15 ms
timeToFirstByte 34.90 ms 39.65 ms
Budget check Errors Warnings Exit
Palette added 8 16 1
Palette removed 0 0 0
Same build twice, with and without palette 0 0 0
Same build, 100 KiB more JavaScript, not slower (synthetic) 0 4 0
Same build, 50 % slower DOMContentLoaded, nothing grew (synthetic) 0 4 0
Palette added, with Accepted Performance Regression 0 24 0

On the palette change, the 8 errors are domContentLoaded on all four suites, plus timeToFirstByte and wpTotal on both de_DE suites (TTFB +16 % on the Dashboard, +23 % on General Settings). The 16 warnings are the added scripts and stylesheets. The two synthetic cases take the same-build data and add 100 KiB to scriptBytes or 50 % to domContentLoaded.

The other cases

A change that adds 100 KiB of JavaScript but is not slower passes, and the growth is listed for the reviewer (synthetic case):

Performance Budget Check passing a change that adds 100 KiB of JavaScript without a slowdown, with the growth listed as warnings

The same build measured twice passes, so runner noise does not trip it:

Performance Budget Check passing when the same build is measured twice

With the Accepted Performance Regression label, the palette change is reported but does not fail:

Performance Budget Check reporting the regression without failing because of the Accepted Performance Regression label

Notes for reviewers

  • The new suites are sent to codevitals. If metrics have to be registered there first, that needs to happen before this is committed.
  • Pull requests whose target build artifact has expired (14 days) are not compared, as today.
  • The Accepted Performance Regression label has to be created in the repository. It only takes effect when it is set before the Compare job starts.
  • A slowdown that comes with no growth at all (for example a slower loop in existing code) only warns. Blocking on timings alone would need more runs per measurement to keep false failures rare.
  • A pull request comment with the results would need a workflow_run workflow; left for a follow-up.

Trac ticket: https://core.trac.wordpress.org/ticket/66685

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Implementation, tests, measurements and the ticket text, under my direction; I reviewed the change and take responsibility for it.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

… performance tests.

The admin performance spec only recorded Server-Timing and time to first byte for the Dashboard, so changes that add scripts or styles to every admin screen were not visible in the performance results. Enqueueing the Command Palette on all admin screens in [61022] added about 40 scripts and 5 MB of JavaScript to every classic admin screen without any change in the reported numbers.

The spec now also records `domContentLoaded` (from `getLoadingDurations()`) and the number and size of the scripts and stylesheets in the page markup, as `scriptCount`, `scriptBytes`, `stylesheetCount` and `stylesheetBytes`, and it measures the General Settings screen (`options-general.php`) next to the Dashboard. The new suites are added to the results sent to codevitals, and `formatValue()` shows counts as plain numbers and byte sizes in KiB.

The sizes use the Resource Timing `encodedBodySize`, not `transferSize`. The `/?clear_cache` request before each measurement loads the front end as a logged-in user, so the toolbar assets are already in the HTTP cache when the admin screen loads, and their `transferSize` is 0, or only the header size when they are revalidated. Which of the two happens depends on the files' Last-Modified date, so the numbers could change between builds without any code change. `encodedBodySize` is the same for cached and fetched responses.

Each suite now starts from an empty results object, so the de_DE suites no longer include the Server-Timing values collected for en_US.

See #66642, #63845.
The performance workflow measures pull requests and their target commit, but it only shows the numbers in a collapsed job summary table. Nothing points out a regression and nothing fails, so a change that doubles the load time of every admin screen passes all checks.

`check-regressions.js` compares the medians of this change with the target commit on pull requests, and with the last release on trunk pushes, metric by metric. A metric regresses when it grows by more than an absolute and a relative budget; timing metrics must also grow by more than three times the combined robust spread (MAD) of both samples, so CI noise does not trip the check. A metric whose target value is 0 can regress as well.

Asset counts and sizes (`scriptBytes`, `stylesheetBytes`), database queries and memory usage are deterministic for a build. Exceeding their budget fails the Compare job on pull requests. Timings only warn. The comparison with the last release is reported but never fails, which gives trunk commits a release-to-release check as well. A crash of the check itself does not fail trunk.

The `Accepted Performance Regression` label marks a regression as reviewed, so the job reports it without failing. The label is read from the pull request event, so it takes effect with the next push; re-running a job keeps the labels of the original event.

The results are written at the top of the job summary and as workflow annotations.

See #66642, #63845.
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props renehermi.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub or WordPress.org user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

… report.

With the `Accepted Performance Regression` label, the job summary still showed the red cross and `error` in the severity column. Only one sentence below the table told it apart from a failing check, so reviewers could not see at a glance that the regression had been accepted.

An accepted report now uses the warning sign, says in the same line that the label accepts the regression, and lists error-severity metrics as `error (accepted)`. The separate sentence below the table is gone. Reports without the label and the exit codes are unchanged.

See #66685.
The check failed a pull request as soon as a screen loaded more scripts or bytes than a small budget allowed. Code grows with every feature, so growth on its own is no reason to block a change. What should not happen is a screen getting slower.

A timing regression (DOMContentLoaded, LCP, TTFB, server time) now fails the check when the same screen also loads more scripts or stylesheets, runs more database queries or uses more memory. The growth confirms that the slowdown comes from the change and not from runner noise.

Growth without a slowdown is still listed in the summary and as a warning annotation, so reviewers see right away when a change adds, for example, 100 KiB of JavaScript and can judge it. A slowdown without any growth is reported as a warning as well, since timings on shared CI runners vary.

On the Command Palette change, the check reports 8 errors for the slower screens and 16 warnings for the added scripts and stylesheets.

See #66685.
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.

1 participant