Skip to content

Likes: add minified build for CSS #106238

Likes: add minified build for CSS

Likes: add minified build for CSS #106238

Workflow file for this run

name: Tests
on:
pull_request:
push:
branches: ['trunk', '*/branch-*']
concurrency:
# Trunk runs need to not be cancelled for concurrency, mainly for code coverage. Everything else can be.
group: tests-${{ github.event_name }}-${{ github.ref }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/trunk' && github.run_id || '' }}
cancel-in-progress: true
permissions:
# actions/checkout, actions/upload-artifact, actions/download-artifact
contents: read
# Note posting the coverage comment uses an app token, so no need for permissions
env:
COMPOSER_ROOT_VERSION: 'dev-trunk'
jobs:
create-matrix:
name: 'Determine tests matrix'
runs-on: ubuntu-latest
timeout-minutes: 2 # 2025-11-20: Takes a few seconds.
outputs:
matrix: ${{ steps.create-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v6
- id: create-matrix
run: |
MATRIX="$(.github/files/generate-ci-matrix.php)"
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
run-tests:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
needs: create-matrix
services:
database:
image: mariadb:12.0
env:
MARIADB_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=5
continue-on-error: ${{ matrix.experimental }}
timeout-minutes: ${{ matrix.timeout }}
env:
TEST_SCRIPT: ${{ matrix.script }}
WP_BRANCH: ${{ matrix.wp }}
PHP_VERSION: ${{ matrix.php }}
NODE_VERSION: ${{ matrix.node }}
MONOREPO_BASE: ${{ github.workspace }}
WITH_WOOCOMMERCE: ${{ matrix.with-woocommerce }}
WITH_WPCOMSH: ${{ matrix.with-wpcomsh }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson( needs.create-matrix.outputs.matrix ) }}
# Note matrix-job outputs are kind of weird. Last-to-run job that sets a non-empty value wins.
outputs:
did-coverage: ${{ ( steps.run-tests.conclusion != 'cancelled' && steps.process-coverage.conclusion == 'success' && steps.upload-artifacts.conclusion == 'success' ) && 'true' || '' }}
coverage-status: ${{ matrix.script == 'test-coverage' && steps.run-tests.conclusion || '' }}
steps:
- uses: actions/checkout@v6
with:
# Test coverage checks require a fetch depth > 1.
fetch-depth: 2
# For pull requests, list-changed-projects.sh needs the merge base.
# But it doesn't have to be checked out.
- name: Deepen to merge base
if: github.event_name == 'pull_request'
uses: ./.github/actions/deepen-to-merge-base
with:
checkout: false
- name: Setup tools
uses: ./.github/actions/tool-setup
with:
php: ${{ matrix.php }}
coverage: ${{ matrix.script == 'test-coverage' && 'pcov' || 'none' }}
node: ${{ matrix.node }}
- name: Monorepo install
run: |
echo "::group::Pnpm"
pnpm install
echo "::endgroup::"
- name: Detect changed projects
id: changed
run: |
CHANGED="$(EXTRA=test .github/files/list-changed-projects.sh)"
# Only test certain plugins in combination with WC
if [[ "$WITH_WOOCOMMERCE" == true ]]; then
echo "Testing with WooCommerce, filtering for projects that have WooCommerce-specific tests."
CHANGED=$( jq -c 'with_entries( select( .key == "plugins/jetpack" ) )' <<<"$CHANGED" )
fi
# Ensure certain plugins are tested if WITH_WPCOMSH and wpcomsh is changed.
if [[ "$WITH_WPCOMSH" == true ]] && jq -e '.["plugins/wpcomsh"] // false' <<<"$CHANGED" > /dev/null; then
echo "Testing with wpcomsh, making sure projects that have wpcomsh-specific tests are also tested."
CHANGED=$( jq -c '. += { "plugins/jetpack": true }' <<<"$CHANGED" )
fi
ANY_PLUGINS="$(jq --argjson changed "$CHANGED" -n '$changed | with_entries( select( .key | startswith( "plugins/" ) ) ) | any')"
echo "projects=${CHANGED}" >> "$GITHUB_OUTPUT"
echo "any-plugins=${ANY_PLUGINS}" >> "$GITHUB_OUTPUT"
- name: Select WordPress version
if: matrix.wp != 'none'
run: .github/files/select-wordpress-tag.sh
- name: Composer Install
env:
CHANGED: ${{ steps.changed.outputs.projects }}
run: |
# If we're going to be making WorDBless use WP "nightlies", remove the relevant package from Composer's cache to get the latest version.
if [[ "$WP_BRANCH" == 'trunk' && ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) ]]; then
echo "::group::Clear composer cache for roots/wordpress"
DIR=$(composer config cache-files-dir)
rm -rf "$DIR/roots/wordpress" "$DIR/roots/wordpress-no-content"
echo "::endgroup::"
fi
echo "::group::Composer"
composer install --working-dir=tools/php-test-env
if [[ ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) && ( "$WP_BRANCH" == 'trunk' || "$WP_BRANCH" == 'previous' ) ]]; then
VER=$(composer --format=json --working-dir="tools/php-test-env" show | jq -r '.installed[] | select( .name == "roots/wordpress" ) | .version')
if [[ -n "$VER" ]]; then
INSVER=$WORDPRESS_TAG
[[ "$WORDPRESS_TAG" == 'trunk' ]] && INSVER="dev-main as $VER"
echo "Supposed to run tests against WordPress $WORDPRESS_TAG, so setting roots/wordpress and roots/wordpress-no-content to \"$INSVER\""
# Composer seems to sometimes have issues with deleting the wordpress dir on its own, so do it manually first.
rm -rf "tools/php-test-env/wordpress"
composer --working-dir="tools/php-test-env" require --dev "roots/wordpress:$INSVER" "roots/wordpress-no-content:$INSVER"
fi
fi
echo "::endgroup::"
echo "Checking for non-mirrored require-dev packages, in case this is testing a release branch"
for SLUG in $( jq -r 'keys[]' <<<"$CHANGED" ); do
PKGS=()
readarray -t PKGS < <( jq -r '.extra["non-mirrored-require-dev"] // empty | .[] | . += "=@dev"' "projects/$SLUG/composer.json" )
if [[ ${#PKGS[@]} -gt 0 ]]; then
echo "::group::Adding packages for $SLUG: ${PKGS[*]}"
# Make sure monorepo repositories entry is present.
JSON=$( jq --tab '.repositories //= [] | if any( .repositories[]; .type == "path" and ( .url | startswith( "../" ) ) and .options?.monorepo? ) then . else .repositories += [ { type: "path", url: "../../packages/*", options: { monorepo: true } } ] end' "projects/$SLUG/composer.json" )
echo "$JSON" > "projects/$SLUG/composer.json"
# Use --no-install and --ignore-platform-reqs here. Code below (either in .github/files/setup-wordpress-env.sh or the "Run project tests" step) will do a `composer install` or `composer update` as necessary.
composer require --working-dir="projects/$SLUG/" --dev --no-install --ignore-platform-reqs "${PKGS[@]}"
echo "::endgroup::"
fi
done
- name: Setup WordPress environment for plugin tests
env:
API_TOKEN_GITHUB: ${{ secrets.GITHUB_TOKEN }}
CHANGED: ${{ steps.changed.outputs.projects }}
if: steps.changed.outputs.any-plugins == 'true' && matrix.wp != 'none'
run: .github/files/setup-wordpress-env.sh
- name: Run project tests
id: run-tests
env:
FORCE_PACKAGE_TESTS: ${{ matrix.force-package-tests && 'true' || 'false' }}
CHANGED: ${{ steps.changed.outputs.projects }}
run: |
EXIT=0
declare -A PIDS
PIDS=()
MAXPIDS=$( nproc )
FAILED=()
mkdir artifacts
[[ "$TEST_SCRIPT" == "test-coverage" ]] && mkdir coverage
for P in composer.json projects/*/*/composer.json; do
if [[ ${#PIDS[@]} -ge $MAXPIDS ]]; then
if ! wait -fn -p PID "${!PIDS[@]}"; then
echo "::error::Tests for ${PIDS[$PID]} failed!"
FAILED+=( "${PIDS[$PID]}" )
EXIT=1
fi
echo "Finished ${PIDS[$PID]}"
unset PIDS[$PID]
fi
if [[ "$P" == "composer.json" ]]; then
DIR="."
SLUG="monorepo"
else
DIR="${P%/composer.json}"
SLUG="${DIR#projects/}"
fi
if [[ "${SLUG%%/*}" != "plugins" && "$WP_BRANCH" != 'latest' && "$WP_BRANCH" != 'none' && "$FORCE_PACKAGE_TESTS" != "true" ]]; then
echo "Skipping $SLUG, only plugins run for WP_BRANCH = $WP_BRANCH"
continue
fi
if ! jq --argjson changed "$CHANGED" --arg p "$SLUG" -ne '$changed[$p] // false' > /dev/null; then
echo "Skipping $SLUG, no changes in it or its dependencies"
elif ! jq --arg script "$TEST_SCRIPT" -e '.scripts[$script] // false' "$P" > /dev/null; then
echo "Skipping $SLUG, no test script is defined in composer.json"
elif php -r 'exit( preg_match( "/^>=\\s*(\\d+\\.\\d+)$/", $argv[1], $m ) && version_compare( PHP_VERSION, $m[1], "<" ) ? 0 : 1 );' "$( jq -r '.require.php // ""' "$P" )"; then
echo "Skipping $SLUG, requires PHP $( jq -r '.require.php // ""' "$P" ) but PHP version is $( php -r 'echo PHP_VERSION;' )"
else
if jq --arg script "skip-$TEST_SCRIPT" -e '.scripts[$script] // false' "$P" > /dev/null; then
{ composer --working-dir="$DIR" run "skip-$TEST_SCRIPT"; CODE=$?; } || true
if [[ $CODE -eq 3 ]]; then
echo "Skipping tests for $SLUG due to skip-$TEST_SCRIPT script"
continue
elif [[ $CODE -ne 0 ]]; then
echo "::error::Script skip-$TEST_SCRIPT failed to run $CODE!"
FAILED+=( "$SLUG" )
EXIT=1
continue
fi
fi
echo "Running tests for $SLUG"
{
# Composer install, if appropriate. Note setup-wordpress-env.sh did it already for plugins.
if [[ "${SLUG%%/*}" != "plugins" && ( "$TEST_SCRIPT" == "test-php" || "$TEST_SCRIPT" == "test-coverage" ) ]]; then
if [[ "$TEST_SCRIPT" == "test-coverage" ]] &&
! jq -e '.scripts["test-php"]' "$DIR/composer.json" &>/dev/null
then
echo "Skipping composer install, assuming test-coverage is only JS because the project has no test-php."
else
if [[ ! -f "$DIR/composer.lock" ]]; then
echo 'No composer.lock, running `composer update`'
composer --working-dir="$DIR" update
elif composer --working-dir="$DIR" check-platform-reqs --lock; then
echo 'Platform reqs pass, running `composer install`'
composer --working-dir="$DIR" install
if [[ "$TEST_SCRIPT" == "test-php" ]] && composer info --locked phpunit/phpunit &>/dev/null; then
echo 'Updating PHPUnit in case a newer version than locked is usable'
composer --working-dir="$DIR" update -W phpunit/phpunit
fi
else
echo 'Platform reqs failed, running `composer update`'
composer --working-dir="$DIR" update
fi
fi
fi
if [[ "${SLUG%%/*}" == "plugins" ]]; then
export WP_TESTS_CONFIG_FILE_PATH="$WORDPRESS_DEVELOP_DIR/wp-tests-config.${SLUG##*/}.php"
fi
mkdir -p "artifacts/$SLUG"
export ARTIFACTS_DIR="$GITHUB_WORKSPACE/artifacts/$SLUG"
if [[ "$TEST_SCRIPT" == "test-coverage" ]]; then
mkdir -p "coverage/$SLUG"
export COVERAGE_DIR="$GITHUB_WORKSPACE/coverage/$SLUG"
fi
FAIL=false
if ! composer run --timeout=0 --working-dir="$DIR" "$TEST_SCRIPT"; then
FAIL=true
fi
# Actions seems to slow down if there are a lot of files, so clean up Composer stuff after each test.
# We don't do it for JS stuff, as that might break things with how JS does package deps.
rm -rf "$DIR/vendor" "$DIR/jetpack_vendor" "$DIR/wordpress"
if $FAIL; then
echo "Tests for $SLUG failed!"
exit 1
fi
} 2> >( sed -u 's!^!['"$SLUG"'] !' >&2 ) > >( sed -u 's!^!['"$SLUG"'] !' ) &
PIDS[$!]=$SLUG
fi
done
while [[ ${#PIDS[@]} -gt 0 ]]; do
if ! wait -fn -p PID "${!PIDS[@]}"; then
echo "::error::Tests for ${PIDS[$PID]} failed!"
FAILED+=( "${PIDS[$PID]}" )
EXIT=1
fi
echo "Finished ${PIDS[$PID]}"
unset PIDS[$PID]
done
if [[ ${#FAILED[@]} -gt 0 ]]; then
echo ''
echo 'The following tests failed:'
printf " - %s\n" "${FAILED[@]}"
fi
exit $EXIT
- name: Process coverage results
id: process-coverage
env:
CHANGED: ${{ steps.changed.outputs.projects }}
if: matrix.script == 'test-coverage' && always()
run: .github/files/coverage-munger/process-coverage.sh
- name: Check for artifacts
id: check-artifacts
# Default for `if` is `success()`, we want this to run always.
if: always()
run: |
[[ -d artifacts ]] && find artifacts -type d -empty -delete
if [[ -d artifacts ]]; then
echo "any=true" >> "$GITHUB_OUTPUT"
else
echo "any=false" >> "$GITHUB_OUTPUT"
fi
- name: Upload artifacts
id: upload-artifacts
if: always() && steps.check-artifacts.outputs.any == 'true'
uses: actions/upload-artifact@v6
with:
name: ${{ matrix.artifact }}
path: artifacts
include-hidden-files: true
retention-days: 7
publish-coverage-data:
name: Publish coverage data
runs-on: ubuntu-latest
timeout-minutes: 10 # 2025-11-20 Takes about 2 minutes.
needs: run-tests
if: always() && needs.run-tests.outputs.did-coverage == 'true' && github.repository == 'Automattic/jetpack' && ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name || github.event_name == 'push' && github.ref == 'refs/heads/trunk' )
steps:
- uses: actions/checkout@v6
- name: Get token
id: get_token
if: github.event_name == 'pull_request'
uses: ./.github/actions/gh-app-token
with:
app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }}
private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }}
- name: Setup tools
uses: ./.github/actions/tool-setup
- name: Download coverage artifact
uses: actions/download-artifact@v7
with:
name: 'Code coverage'
path: coverage
- name: Upload coverage results
env:
PR_ID: ${{ github.event_name != 'pull_request' && 'trunk' || github.event.pull_request.number }}
SECRET: ${{ secrets.CODECOV_SECRET }}
STATUS: ${{ needs.run-tests.outputs.coverage-status }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
POST_MESSAGE_TOKEN: ${{ steps.get_token.outputs.token }}
run: .github/files/coverage-munger/upload-coverage.sh
storybook-test:
name: Storybook tests
runs-on: ubuntu-latest
timeout-minutes: 20 # 2025-11-20: Takes about 4 minutes
steps:
- uses: actions/checkout@v6
# For pull requests, list-changed-projects.sh needs the merge base.
# But it doesn't have to be checked out.
- name: Deepen to merge base
if: github.event_name == 'pull_request'
uses: ./.github/actions/deepen-to-merge-base
with:
checkout: false
- name: Setup tools
uses: ./.github/actions/tool-setup
- name: Monorepo install
run: |
echo "::group::Pnpm"
pnpm install
echo "::endgroup::"
- name: Detect changed projects
id: changed
run: |
CHANGED=$( .github/files/list-changed-projects.sh )
PROJECTS=$( node -e 'const r = { "js-packages/storybook": true }; for ( const p of require( "./projects/js-packages/storybook/storybook/projects.js" ).projects ) { const m = p.match( /\/projects\/([^/]+\/[^/]+)(?:$|\/)/ ); m && ( r[ m[1] ] = true ); } console.log( JSON.stringify( r ) );' )
ANY=$( jq --argjson changed "$CHANGED" --argjson projects "$PROJECTS" -n '$changed | with_entries( select( $projects[ .key ] ) ) | any' )
echo "any=${ANY}" >> "$GITHUB_OUTPUT"
- name: Build storybook
if: steps.changed.outputs.any == 'true'
run: |
pnpm jetpack build -v js-packages/storybook
- name: Install playwright
if: steps.changed.outputs.any == 'true'
run: |
cd projects/js-packages/storybook
pnpm exec playwright install --with-deps chromium
- name: Test storybook
if: steps.changed.outputs.any == 'true'
env:
# Chromium bug, see https://github.com/microsoft/playwright/issues/34046
LANG: en_US
LC_ALL: en_US
run: |
cd projects/js-packages/storybook
node bin/webserver.mjs
REFERENCE_URL=https://automattic.github.io/jetpack-storybook/ pnpm exec test-storybook -c storybook --url 'http://127.0.0.1:6006/index.html'
# Probably this should be a linting test, but we don't run linting on trunk or release branches.
plugin-deps:
name: Check plugin monorepo dep versions
runs-on: ubuntu-latest
timeout-minutes: 5 # 2025-11-20: Takes less than a minute.
steps:
- uses: actions/checkout@v6
- name: Setup tools
uses: ./.github/actions/tool-setup
with:
node: false
- name: Run check
run: |
if [[ "$GITHUB_EVENT_NAME" == 'push' ]]; then
REF="${GITHUB_REF#refs/heads/}"
elif [[ "$GITHUB_EVENT_NAME" == 'pull_request' || "$GITHUB_EVENT_NAME" == 'pull_request_target' ]]; then
REF="$GITHUB_BASE_REF"
else
echo "::error::Unsupported github event \"$GITHUB_EVENT_NAME\""
exit 1
fi
echo "Detected target ref \"$REF\""
if [[ "$REF" == trunk ]]; then
ARGS=( --dev )
elif [[ "$REF" == */branch-* ]]; then
ARGS=( --release )
TMP="$(jq -r --arg P "${REF%%/branch-*}" '.extra["release-branch-prefix"] | if type == "array" then . else [ . ] end | if index( $P ) then input_filename | match( "^projects/plugins/([^/]+)/composer.json$" ).captures[0].string else empty end' projects/plugins/*/composer.json)"
while IFS= read -r LINE; do
ARGS+=( "$LINE" )
done <<<"$TMP"
else
echo "Unsupported ref \"$REF\", ignoring"
exit 0
fi
echo "Running tools/check-plugin-monorepo-deps.sh ${ARGS[@]}"
tools/check-plugin-monorepo-deps.sh "${ARGS[@]}"