Skip to content

Build/Test Tools: Harden CI setup against flaky downloads - #15210

Draft
lucatume wants to merge 9 commits into
WordPress:trunkfrom
lucatume:fix/perf-tests-setup-hardening
Draft

lucatume wants to merge 9 commits into
WordPress:trunkfrom
lucatume:fix/perf-tests-setup-hardening

Conversation

@lucatume

Copy link
Copy Markdown

Hardens CI setup steps that fail or hang on flaky external downloads. Between 2026-10-15 and 2026-10-28, 15 of 523 trunk Performance Tests runs needed a rerun, 13 of them for setup failures. Three Playwright installs hung until the 6 hour job limit.

One commit per concern:

  • Time out Playwright browser installs (performance, E2E): cap the step at 15 minutes. Successful installs took 19s–607s; the hung ones ran for 6 hours (runs 40570600406, 40571039922, 40575912886).
  • Retry downloads with curl (--fail --retry 5 --retry-all-errors, as setup-wp-cli does): performance mock data, WordPress Importer, and Gutenberg in E2E. Plugins are downloaded on the runner and installed from the zip in the workspace, because WP-CLI tries once (runs 39060627317, 39485836492, 40571772421).
  • Retry WordPress downloads up to 3 times: the performance baseline wp core download (run 39174433151), Installation Tests (both the curl | tar release path from r65004 and the nightly wp core download), and Upgrade Tests.
  • Retry the global npm install in setup-npm up to 3 times (ECONNRESET in runs 38800425109, 39405765547).
  • Skip "Archive artifacts" when the performance tests never ran, so a setup failure doesn't add a second red step. if-no-files-found: error is kept.

Retries wait 10 and then 20 seconds, like tools/local-env/scripts/utils.js.

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

Branches that run this

Release branches call these reusable workflows at @trunk, so they pick up the changes when this is committed, with no backports. Every branch from 4.7 to 7.1 was checked: none has its own copy of a changed step, and none pins these workflows to another ref.

Workflow Branches
reusable-performance-test-v2.yml trunk, 6.8–7.1
reusable-end-to-end-tests.yml trunk, 5.8–7.1. 5.8–6.3 skip the Playwright and Gutenberg steps. 6.4–6.9 pin gutenberg-version; the versioned zip URLs return 200.
reusable-upgrade-testing.yml trunk, 6.6–7.1
install-testing.yml, setup-npm trunk only

This overlaps with #13102 (checksum step after the same install and upgrade downloads) and #13867 (adds a localized wp core download to Installation Tests without a retry). Whichever lands second needs a rebase.

Testing

  • Fork PR run: Performance Tests and E2E tests pass, with the Importer and Gutenberg installed from the zip and the final git diff --exit-code clean (performance, E2E).
  • Retry loops run locally with stubbed commands under bash -e: success on attempt 1, 2 or 3, or never → 1, 2, 3 or 3 calls, exit 0, 0, 0 or 1. The tarball path re-extracts correctly after a failed attempt.
  • actionlint passes.

Not covered by pull request CI:

  • The retry branches, which only run on real network failures.
  • The performance baseline step, which runs only on push.
  • Installation and Upgrade Tests, which their path filters skip; they need a workflow_dispatch run (a release version such as 6.0 and nightly).
  • A release branch E2E run with a pinned Gutenberg version.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Used for: analysing failed runs, writing the changes and this description. I reviewed and tested the changes.

In the performance tests workflow, `playwright install --with-deps`
hung until the 6 hour job limit in runs 40570600406, 40571039922 and
40575912886, leaving the job unfinished until GitHub cancelled it.

Across the recent trunk runs, the step took under a minute in most
jobs and at most about 10 minutes during the same slowdown. A 15 minute
timeout fails a hung install quickly without failing slow ones.
The performance tests workflow downloads the theme unit test data with
a single `curl -O`. In run 40571772421 the connection was reset at 98%
and the job failed with `curl: (56) Recv failure`.

Retry the download with the flags the Set up WP-CLI action uses, and
fail on HTTP errors instead of importing an error page.
`wp plugin install wordpress-importer` queries the WordPress.org API
and downloads the plugin once. In performance test runs 39060627317
and 39485836492 the request failed and the job stopped with "The
'wordpress-importer' plugin could not be found."

Download the plugin zip on the runner with the same retrying curl
command as the mock data, install it from the mounted workspace, and
remove it, as the mock data step does with its file.
`wp core download` fetches the baseline release from WordPress.org
once. In performance test run 39174433151 it failed with "cURL error
28: Resolving timed out".

Try it up to three times, waiting 10 and then 20 seconds as the local
environment's `docker compose` retries do, and warning on each failed
attempt. Replacing it with a retrying curl was considered, but
`--force` over an existing install also verifies the md5 hash and
removes files the release no longer ships, which a plain extract would
not reproduce.
The Set up npm action installs the npm version required by
`devEngines` with a single `npm install --global`. In performance test
runs 38800425109 and 39405765547 the registry reset the connection
(`ECONNRESET`, "Invalid response body"), which npm does not retry,
and the job failed.

Try the install up to three times, waiting 10 and then 20 seconds and
warning on each failed attempt, then fail the step. Every trunk
workflow that installs npm dependencies uses this action.
The Archive artifacts step runs on `always()` and errors when the
`artifacts` directory is missing. When a setup step fails, the tests
never run, so every such job also reported "No files were found with
the provided path: artifacts", a second error that hides the real one
in the job summary.

Run the step only when the performance tests step did not skip. A test
run that fails or is cancelled still uploads its results, and a run
that produced no results still errors.
The Install Playwright browsers step in the reusable E2E workflow has
no timeout, the same as the performance workflow had when three of its
installs hung until the 6 hour job limit on 2026-10-27 (runs
40570600406, 40571039922, 40575912886).

Cap the step at 15 minutes, matching the performance workflow, so a
hang fails fast and the job can be rerun.
`wp plugin install gutenberg` downloads the plugin from WordPress.org
once, so a network error fails the E2E job. The performance workflow
failed the same way installing the WordPress Importer (runs
39060627317, 39485836492).

Download the zip on the runner with curl retries, as the performance
workflow now does for the importer, then install it from the
workspace, which the CLI container mounts at /var/www. Release
branches from 6.4 call this workflow from trunk with a pinned
`gutenberg-version`, which maps to the versioned zip URL.
The install and upgrade testing workflows download WordPress from
WordPress.org once, with `wp core download` or, for install testing
releases, `curl | tar`. Any network error fails the job, as it did for
the performance baseline in run 39174433151.

Try the download up to three times, waiting 10 and then 20 seconds, as
the performance workflow now does for its baseline. A retry overwrites
files a failed attempt extracted, and `--force` lets the nightly
`wp core download` do the same. Both workflows run large matrices, so
even rare failures repeat across jobs.
@lucatume lucatume self-assigned this Oct 30, 2026
Comment thread .github/actions/setup-npm/action.yml Dismissed

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.

🟢 Approval recommended

No active review issues remain for the CI resilience changes.

No open issues 🎉

What changed in this PR

Hardens CI setup against flaky external downloads and hanging browser installs. Release branches using the reusable workflows at @trunk inherit these changes without backports.

Changes:

  • Adds retries for WordPress, plugin, mock-data, and global npm downloads.
  • Caps Playwright browser installation steps at 15 minutes.
  • Skips performance artifact uploads when the test step was skipped, while retaining missing-file errors when it ran.
File Description
.github/​workflows/​reusable-upgrade-testing.yml Retries WordPress core downloads up to three times.
.github/​workflows/​reusable-performance-test-v2.yml Adds download retries, a Playwright timeout, and conditional artifact uploads.
.github/​workflows/​reusable-end-to-end-tests.yml Adds a Playwright timeout and retries Gutenberg ZIP downloads.
.github/​workflows/​install-testing.yml Retries release and nightly WordPress downloads.
.github/​actions/​setup-npm/​action.yml Retries global npm installation up to three times.

Review effort: Balanced

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

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