Commit 0e5a736c000 for woocommerce
commit 0e5a736c0003954031c3d9530c4efbf4ce473f3b
Author: Jamel Noel Reid <MrJnrman@users.noreply.github.com>
Date: Tue Nov 3 22:12:47 2026 -0500
Add test:js:ci to reproduce a CI JavaScript unit job locally (#69263)
[TESTOPS-296] Add test:js:ci to reproduce a CI JavaScript unit job locally
Runs the same install, dependency build and test commands the setup-woocommerce-monorepo action and ci.yml run for one project. Also corrects the wp-env port note in the local-env skill: the PHP test env is 8186, the e2e env is 8086.
diff --git a/.ai/skills/woocommerce-dev-cycle/running-tests.md b/.ai/skills/woocommerce-dev-cycle/running-tests.md
index eb64180ecc3..52803471cf4 100644
--- a/.ai/skills/woocommerce-dev-cycle/running-tests.md
+++ b/.ai/skills/woocommerce-dev-cycle/running-tests.md
@@ -225,6 +225,17 @@ pnpm test:js -- status-badge.test.tsx
pnpm test:js -- --coverage
```
+### Reproducing a CI failure
+
+CI installs the project's dependencies and builds the workspace packages it depends on before running the tests; a local `pnpm test:js` with stale or missing dependency builds can fail where CI passes, or pass where CI fails. Run the job the way CI runs it, from the repository root, after `nvm use` so Node matches `.nvmrc`:
+
+```bash
+pnpm test:js:ci @woocommerce/components
+pnpm test:js:ci @woocommerce/components -- --maxWorkers=4 # extra arguments go to the test runner
+```
+
+This runs `pnpm install --filter='<project>...' --frozen-lockfile`, the `build:project:*` scripts of the project's dependencies, then `CI=true GITHUB_ACTIONS=true pnpm --filter=<project> <command>`, echoing each command. The command is the project's unit job command from `config.ci.tests` in its `package.json` (`test:js` for most projects), so it reproduces any project's CI unit job, not only jest ones.
+
### Test File Locations
- JavaScript/Jest tests: `client/admin/client/**/*.test.tsx` or `*.test.ts`
diff --git a/.ai/skills/woocommerce-local-env/SKILL.md b/.ai/skills/woocommerce-local-env/SKILL.md
index 15413c616ba..956590a9c21 100644
--- a/.ai/skills/woocommerce-local-env/SKILL.md
+++ b/.ai/skills/woocommerce-local-env/SKILL.md
@@ -104,7 +104,8 @@ Recommend targeted watchers for focused work to reduce noise and startup time:
## Environment Details
- Development URL: `http://localhost:8888/`
-- Test environment port: `8086`
+- PHP test environment (`.wp-env.test.json`, used by `test:php:env*`): `http://localhost:8186/`
+- E2E environment (`.wp-env.e2e.json`, used by `wp-env:e2e` and the Playwright `BASE_URL` default): `http://localhost:8086/`
- wp-env config: `plugins/woocommerce/.wp-env.json`
- wp-env PHP version: `8.1`
- WooCommerce package: `@woocommerce/plugin-woocommerce`
diff --git a/bin/test-js-ci.sh b/bin/test-js-ci.sh
new file mode 100755
index 00000000000..f92650b6e29
--- /dev/null
+++ b/bin/test-js-ci.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+
+# Runs a project's CI unit test job the way CI runs it, so a CI failure can be
+# reproduced without any other tool:
+#
+# pnpm test:js:ci <project> [test runner args...]
+# pnpm test:js:ci @woocommerce/components -- --maxWorkers=4
+#
+# The three steps mirror .github/actions/setup-woocommerce-monorepo/action.yml
+# (install, then build-type "dependencies") and the "Run tests" step of
+# .github/workflows/ci.yml. The dependency build is the step people skip
+# locally, and the reason some packages only fail on a laptop. The test
+# command is the project's own unit job command from `config.ci.tests` in its
+# package.json (`test:js` for most projects), the same one CI runs.
+#
+# Run `nvm use` first: CI uses the Node version in .nvmrc.
+
+set -euo pipefail
+
+usage() {
+ echo "usage: pnpm test:js:ci <project> [test runner args...]" >&2
+ echo " e.g. pnpm test:js:ci @woocommerce/number" >&2
+ exit 2
+}
+
+if [[ "${1:-}" == "--" ]]; then
+ shift
+fi
+project="${1:-}"
+if [[ -z "$project" || "$project" == -* ]]; then
+ usage
+fi
+shift
+if [[ "${1:-}" == "--" ]]; then
+ shift
+fi
+
+cd "$(dirname "$0")/.."
+
+project_dir="$(pnpm --filter="${project}" list --depth -1 --parseable)"
+if [[ -z "$project_dir" ]]; then
+ echo "error: no workspace project named '${project}'" >&2
+ usage
+fi
+
+command="$(node -e '
+ const tests = ( require( process.argv[ 1 ] ).config?.ci?.tests ) || [];
+ const unit = tests.find( ( t ) => ! t.testType || t.testType === "unit" );
+ process.stdout.write( ( unit && unit.command ) || "test:js" );
+' "${project_dir}/package.json")"
+
+run() {
+ printf '\n$' >&2
+ printf ' %q' "$@" >&2
+ printf '\n' >&2
+ "$@"
+}
+
+run pnpm install --filter="${project}..." --frozen-lockfile
+BROWSERSLIST_IGNORE_OLD_DATA=true run pnpm --if-present --workspace-concurrency=Infinity --stream --filter="${project}^..." '/^build:project:.*$/'
+CI=true GITHUB_ACTIONS=true FORCE_COLOR=1 run pnpm --filter="${project}" "${command}" "$@"
diff --git a/package.json b/package.json
index f24fceb9128..a0249afe60f 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"scripts": {
"build": "pnpm -r --workspace-concurrency=Infinity --stream '/^build:project:.*$/'",
"test": "pnpm -r test",
+ "test:js:ci": "bash bin/test-js-ci.sh",
"lint": "pnpm -r lint",
"cherry-pick": "node ./tools/cherry-pick/bin/run",
"clean": "rimraf -g '**/node_modules' 'packages/*/*/vendor' 'plugins/*/vendor' && pnpm store prune",