Skip to content

chore: fix npmPackage update failure in CI [skip ci] #604

chore: fix npmPackage update failure in CI [skip ci]

chore: fix npmPackage update failure in CI [skip ci] #604

Workflow file for this run

name: Validation
on:
schedule:
- cron: '30 23 * * *'
pull_request:
branches: ['main', '25.*']
merge_group:
workflow_dispatch:
inputs:
components:
description: 'Space-separated component names (e.g. "grid combo-box"), empty for all'
required: false
default: ''
shards:
description: 'Number of parallel IT shards'
required: false
default: '5'
local-wc-validation:
description: 'Run validation against local web-components checkout'
required: false
default: false
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: true
env:
FORK_COUNT: 4
jobs:
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
outputs:
skip: ${{ steps.skip-ci.outputs.skip }}
components: ${{ steps.components.outputs.elements }}
build-cache-key: ${{ steps.build-key.outputs.key }}
steps:
- name: Set Maven args
run: |
args="-ntp -B"
[ "${{ runner.debug }}" != "1" ] && args="$args -q"
echo "MAVEN_ARGS=$args" >> "$GITHUB_ENV"
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
fetch-depth: ${{ github.event_name == 'merge_group' && 50 || 1 }}
- name: Check for skip-ci
id: skip-ci
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
if [ "${{ github.event_name }}" = "merge_group" ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if echo "$PR_TITLE" | grep -q '\[skip ci\]'; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Compute build cache key
id: build-key
if: steps.skip-ci.outputs.skip != 'true'
run: echo "key=${{ runner.os }}-build-${{ github.run_id }}" >> "$GITHUB_OUTPUT"
- name: Restore build cache
id: build-cache
if: steps.skip-ci.outputs.skip != 'true'
uses: actions/cache@v5
with:
path: |
~/.m2/repository/com/vaadin
vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/main/resources/META-INF/frontend/generated/
key: ${{ steps.build-key.outputs.key }}
- name: Setup JDK 21
if: steps.skip-ci.outputs.skip != 'true' && steps.build-cache.outputs.cache-hit != 'true'
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Setup Node
if: steps.skip-ci.outputs.skip != 'true' && steps.build-cache.outputs.cache-hit != 'true'
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Show environment info
if: steps.skip-ci.outputs.skip != 'true' && steps.build-cache.outputs.cache-hit != 'true'
run: |
java -version
mvn -version
node --version
npm --version
google-chrome --version
chromedriver --version
- name: Install artifacts
if: steps.skip-ci.outputs.skip != 'true' && steps.build-cache.outputs.cache-hit != 'true'
run: |
mvn install -Dmaven.test.skip=true -Drelease -T $FORK_COUNT $MAVEN_ARGS || {
echo "::warning::First attempt failed (Maven multi-thread race). Retrying..."
sleep 15
mvn install -Dmaven.test.skip=true -Drelease -T $FORK_COUNT $MAVEN_ARGS
}
- name: Detect modified components
id: components
if: steps.skip-ci.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
elements="${{ github.event.inputs.components || '' }}"
changed_files=""
if [ -n "$elements" ]; then
echo "elements=$elements" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ github.event_name }}" = "pull_request" ]; then
pr_number="${{ github.event.pull_request.number }}"
changed_files=$(gh api "repos/${{ github.repository }}/pulls/$pr_number/files" --jq '.[].filename')
elif [ "${{ github.event_name }}" = "merge_group" ]; then
changed_files=$(git diff --name-only "${{ github.event.merge_group.base_sha }}" "${{ github.event.merge_group.head_sha }}")
fi
if [ -n "$changed_files" ]; then
modified=$(echo "$changed_files" \
| grep 'vaadin.*flow-parent' \
| sed 's,^vaadin-\(.*\)-flow-parent.*,\1,' \
| sort -u)
nmods=$(echo "$modified" | wc -w | tr -d ' ')
all_files=$(echo "$changed_files" | sort -u | tr -d '[:space:]')
component_files=$(echo "$changed_files" | grep 'vaadin.*flow-parent' | sort -u | tr -d '[:space:]')
if [ "$nmods" -lt 5 ] && [ ${#all_files} -eq ${#component_files} ]; then
elements=$(echo $modified | tr '\n' ' ')
echo "Running partial build for: $elements"
fi
fi
echo "elements=$elements" >> "$GITHUB_OUTPUT"
fast-tests:
name: ${{ matrix.name }}
needs: build
if: needs.build.outputs.skip != 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
outputs:
matrix: ${{ steps.shards.outputs.matrix }}
has-tests: ${{ steps.shards.outputs.has-tests }}
war-cache-key: ${{ steps.war-key.outputs.key }}
unit-failed: ${{ steps.type-failed.outputs.unit }}
wtr-failed: ${{ steps.type-failed.outputs.wtr }}
strategy:
fail-fast: true
matrix:
include:
- name: Unit tests
type: unit
- name: WTR tests
type: wtr
- name: Package WAR
type: war
steps:
- name: Set Maven args
run: |
args="-ntp -B"
[ "${{ runner.debug }}" != "1" ] && args="$args -q"
echo "MAVEN_ARGS=$args" >> "$GITHUB_ENV"
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
fetch-depth: 1
- name: Setup Node
if: matrix.type != 'unit'
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Checkout web-components
if: matrix.type != 'unit' && github.event_name == 'schedule' || inputs.local-wc-validation
uses: actions/checkout@v6
with:
repository: vaadin/web-components
ref: main
path: web-components
- name: Install web-components
if: matrix.type != 'unit' && github.event_name == 'schedule' || inputs.local-wc-validation
run: yarn install --ignore-engines --ignore-scripts
working-directory: web-components
- name: Configure local web-components
if: matrix.type != 'unit' && github.event_name == 'schedule' || inputs.local-wc-validation
run: echo "LOCAL_WEB_COMPONENTS_PATH=web-components" > .env
- name: npm install
if: matrix.type != 'unit'
run: npm ci --ignore-scripts
- name: Detect @NpmPackage version changes
id: npm-changes
if: matrix.type != 'unit'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
changed=false
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Use the PR files API so we diff against the merge base, not against
# main's current tip — otherwise unrelated @NpmPackage commits already
# merged into main since the PR branched would trigger a false positive.
if gh api --paginate "repos/$REPO/pulls/$PR_NUMBER/files" \
--jq '.[] | select(.filename | endswith(".java")) | .patch // ""' \
| grep -qE '^\+.*@NpmPackage'; then
changed=true
fi
else
base="${{ github.base_ref || 'main' }}"
if git fetch --depth=1 origin "$base" 2>/dev/null && \
git diff FETCH_HEAD -- '*.java' | grep -q '@NpmPackage'; then
changed=true
fi
fi
if [ "$changed" = "true" ]; then
echo "MAVEN_OPTS=${MAVEN_OPTS} -Dvaadin.npm.minimumFrontendPackageAgeDays=0" >> "$GITHUB_ENV"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Request changes on PR for @NpmPackage version bump
if: >-
matrix.type == 'war'
&& steps.npm-changes.outputs.changed == 'true'
&& github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ secrets.VAADIN_REVIEW_BOT }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
MARKER="<!-- vaadin-flow-components:npm-package-bump-review -->"
existing=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \
--jq "[.[] | select(.state == \"CHANGES_REQUESTED\") | select(.body | contains(\"$MARKER\"))] | length")
if [ "$existing" -gt 0 ]; then
echo "Existing CHANGES_REQUESTED review found, skipping."
exit 0
fi
BODY=$(sed 's/^ //' <<'EOF'
<!-- vaadin-flow-components:npm-package-bump-review -->
This PR is updating the @NpmPackage version. Due to the default value of `vaadin.npm.minimumFrontendPackageAgeDays` is 1, I'm blocking this PR for now.
In order to not producing a failed snapshot for other projects, please come back one day after to unblock and merge it.
EOF
)
gh api -X POST "repos/$REPO/pulls/$PR_NUMBER/reviews" \
-f event=REQUEST_CHANGES \
-f body="$BODY"
- name: Merge ITs
if: matrix.type == 'war'
run: node scripts/mergeITs.js ${{ needs.build.outputs.components }}
- name: Compute WAR cache key
if: matrix.type == 'war'
id: war-key
env:
KEY: ${{ runner.os }}-war-${{ hashFiles('**/pom.xml', '**/src/main/java/**', '**/src/main/resources/**', 'integration-tests/src/test/java/**') }}
run: echo "key=$KEY" >> "$GITHUB_OUTPUT"
- name: Restore WAR cache
if: matrix.type == 'war'
id: war-cache
uses: actions/cache@v5
with:
path: |
integration-tests/pom.xml
integration-tests/target
key: ${{ steps.war-key.outputs.key }}
- name: Setup JDK 21
if: steps.war-cache.outputs.cache-hit != 'true'
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Install TestBench license
if: env.TB_LICENSE != '' && steps.war-cache.outputs.cache-hit != 'true'
env:
TB_LICENSE: ${{ secrets.TB_LICENSE }}
run: |
mkdir -p ~/.vaadin
user="${TB_LICENSE%%/*}"
key="${TB_LICENSE#*/}"
echo "{\"username\":\"${user}\",\"proKey\":\"${key}\"}" > ~/.vaadin/proKey
- name: Restore build cache
if: steps.war-cache.outputs.cache-hit != 'true'
id: build-cache
uses: actions/cache@v5
with:
path: |
~/.m2/repository/com/vaadin
vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/main/resources/META-INF/frontend/generated/
key: ${{ needs.build.outputs.build-cache-key }}
fail-on-cache-miss: true
- name: Run unit tests
if: matrix.type == 'unit'
run: mvn test -Drelease -T $FORK_COUNT -Dsurefire.parallel=classes -Dsurefire.threadCount=2 -DskipSvgChartsBuild $MAVEN_ARGS
- name: Upload unit test reports
if: always() && matrix.type == 'unit'
uses: actions/upload-artifact@v6
with:
name: surefire-reports
path: '**/target/surefire-reports/TEST-*.xml'
retention-days: 1
if-no-files-found: ignore
- name: Run WTR tests
if: matrix.type == 'wtr'
run: node scripts/wtr.js ${{ needs.build.outputs.components }}
- name: Upload WTR reports
if: always() && matrix.type == 'wtr'
uses: actions/upload-artifact@v6
with:
name: wtr-reports
path: '**/wtr-results.xml'
retention-days: 1
if-no-files-found: ignore
- name: Package integration-tests WAR
if: matrix.type == 'war' && steps.war-cache.outputs.cache-hit != 'true'
run: |
mvn package -pl integration-tests \
-Dvaadin.pnpm.enable -Drun-it -Drelease \
-Dvaadin.productionMode -Dvaadin.force.production.build=true \
-Dmaven.test.skip=true -DskipJetty $MAVEN_ARGS
- name: Compile IT test sources
if: matrix.type == 'war' && steps.war-cache.outputs.cache-hit != 'true'
run: mvn test-compile -pl integration-tests -Drun-it -DskipFrontend -DskipJetty -DskipUnitTests $MAVEN_ARGS
- name: Compute IT shards
if: matrix.type == 'war'
id: shards
env:
SHARDS: ${{ github.event.inputs.shards || '0' }}
TARGET_PER_SHARD: 35
MAX_SHARDS: 10
run: |
mapfile -t its < <(find integration-tests/src/test/java -name '*IT.java' -printf '%P\n' | sed -e 's|/|.|g' -e 's|\.java$||' | sort)
count=${#its[@]}
echo "Found $count IT classes"
if [ "$count" -eq 0 ]; then
echo "has-tests=false" >> "$GITHUB_OUTPUT"
echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT"
exit 0
fi
n=$SHARDS
if [ "$n" -eq 0 ]; then
n=$(( (count + TARGET_PER_SHARD - 1) / TARGET_PER_SHARD ))
[ "$n" -lt 1 ] && n=1
[ "$n" -gt "$MAX_SHARDS" ] && n=$MAX_SHARDS
fi
if [ "$n" -gt "$count" ]; then n=$count; fi
echo "$n shards for $count classes (~$(( count / n )) per shard)"
declare -a buckets
for i in $(seq 1 $n); do buckets[$i]=""; done
i=1
for t in "${its[@]}"; do
if [ -n "${buckets[$i]}" ]; then buckets[$i]+=","; fi
buckets[$i]+="$t"
i=$((i+1))
[ $i -gt $n ] && i=1
done
json='{"include":['
for k in $(seq 1 $n); do
[ $k -gt 1 ] && json+=','
json+='{"shard":"'"$k"'","tests":"'"${buckets[$k]}"'"}'
done
json+=']}'
echo "$json"
echo "matrix=$json" >> "$GITHUB_OUTPUT"
echo "has-tests=true" >> "$GITHUB_OUTPUT"
- name: Record job type on failure
id: type-failed
if: always() && failure()
run: echo "${{ matrix.type }}=true" >> "$GITHUB_OUTPUT"
its:
name: ITs (${{ matrix.shard }})
needs: [build, fast-tests]
if: needs.fast-tests.outputs.has-tests == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.fast-tests.outputs.matrix) }}
steps:
- name: Set Maven args
run: |
args="-ntp -B"
[ "${{ runner.debug }}" != "1" ] && args="$args -q"
echo "MAVEN_ARGS=$args" >> "$GITHUB_ENV"
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
fetch-depth: 1
- name: Setup JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Install TestBench license
if: env.TB_LICENSE != ''
env:
TB_LICENSE: ${{ secrets.TB_LICENSE }}
run: |
mkdir -p ~/.vaadin
user="${TB_LICENSE%%/*}"
key="${TB_LICENSE#*/}"
echo "{\"username\":\"${user}\",\"proKey\":\"${key}\"}" > ~/.vaadin/proKey
- name: Restore build cache
uses: actions/cache@v5
with:
path: |
~/.m2/repository/com/vaadin
vaadin-charts-flow-parent/vaadin-charts-flow-svg-generator/src/main/resources/META-INF/frontend/generated/
key: ${{ needs.build.outputs.build-cache-key }}
fail-on-cache-miss: true
- name: Restore WAR cache
uses: actions/cache@v5
with:
path: |
integration-tests/pom.xml
integration-tests/target
key: ${{ needs.fast-tests.outputs.war-cache-key }}
fail-on-cache-miss: true
- name: Run integration tests (shard ${{ matrix.shard }})
env:
SHARD_TESTS: ${{ matrix.tests }}
run: |
mvn -pl integration-tests -Drun-it \
jetty:start-war failsafe:integration-test jetty:stop failsafe:verify \
-Dvaadin.productionMode \
-DskipFrontend \
-Dfailsafe.forkCount=$FORK_COUNT \
-Dcom.vaadin.testbench.Parameters.testsInParallel=2 \
-Dfailsafe.rerunFailingTestsCount=2 \
-Dmaven.test.redirectTestOutputToFile=true \
-Dtest.reuseDriver=true \
-Dit.test="$SHARD_TESTS" \
-B
- name: Upload error screenshots
if: failure()
uses: actions/upload-artifact@v6
with:
name: error-screenshots-${{ matrix.shard }}
path: integration-tests/error-screenshots/
retention-days: 5
if-no-files-found: ignore
- name: Upload failsafe reports
if: always()
uses: actions/upload-artifact@v6
with:
name: failsafe-reports-${{ matrix.shard }}
path: integration-tests/target/failsafe-reports/
retention-days: 1
if-no-files-found: ignore
results:
name: Collect tests
needs: [build, fast-tests, its]
if: always() && needs.build.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
actions: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
fetch-depth: 1
- uses: actions/download-artifact@v8
continue-on-error: true
with:
name: surefire-reports
path: surefire-reports
- uses: actions/download-artifact@v8
continue-on-error: true
with:
pattern: failsafe-reports-*
merge-multiple: true
path: failsafe-reports
- uses: actions/download-artifact@v8
continue-on-error: true
with:
name: wtr-reports
path: wtr-reports
- name: Remove passing test reports
run: |
# Detect real failures via testsuite attributes (flaky retries leave
# <flakyError> tags but keep failures="0" errors="0" on the suite).
has_failures=false
for dir in surefire-reports failsafe-reports wtr-reports; do
[ -d "$dir" ] || continue
if find "$dir" -name '*.xml' | xargs grep -lE 'failures="[1-9]|errors="[1-9]' 2>/dev/null | grep -q .; then
has_failures=true
break
fi
done
echo "has_failures=$has_failures" >> $GITHUB_ENV
# Red build: keep only failing reports so dorny focuses on failures.
# Green build: keep all reports so every suite shows as passed.
if [ "$has_failures" = "true" ]; then
for dir in surefire-reports failsafe-reports wtr-reports; do
[ -d "$dir" ] || continue
find "$dir" -name '*.xml' | while read -r f; do
grep -qE 'failures="[1-9]|errors="[1-9]' "$f" || rm -f "$f"
done
done
fi
- name: Check for unit test reports
id: unit-check
run: |
count=$(find surefire-reports -name 'TEST-*.xml' 2>/dev/null | wc -l | tr -d ' ')
echo "count=$count" >> $GITHUB_OUTPUT
- name: Unit Tests header
if: steps.unit-check.outputs.count != '0' && (needs.fast-tests.outputs.unit-failed != 'true' || env.has_failures == 'true')
run: echo "## Unit Tests" >> $GITHUB_STEP_SUMMARY
- name: Publish unit test results
id: unit-dorny
if: steps.unit-check.outputs.count != '0' && (needs.fast-tests.outputs.unit-failed != 'true' || env.has_failures == 'true')
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
with:
name: Unit Tests
path: surefire-reports/**/TEST-*.xml
reporter: java-junit
list-tests: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
list-suites: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
fail-on-error: 'false'
- name: Unit test compilation error
if: needs.fast-tests.outputs.unit-failed == 'true' && env.has_failures != 'true'
run: |
echo ":x: Unit tests compilation failed. Check the **Unit tests** job for details." >> $GITHUB_STEP_SUMMARY
- name: Check for IT reports
id: it-check
run: |
count=$(find failsafe-reports -name 'TEST-*.xml' 2>/dev/null | wc -l | tr -d ' ')
echo "count=$count" >> $GITHUB_OUTPUT
- name: Integration Tests header
if: steps.it-check.outputs.count != '0' && (needs.fast-tests.result == 'success' || env.has_failures == 'true')
run: echo "## Integration Tests" >> $GITHUB_STEP_SUMMARY
- name: Publish integration test results
id: it-dorny
if: steps.it-check.outputs.count != '0' && (needs.fast-tests.result == 'success' || env.has_failures == 'true')
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
with:
name: Integration Tests
path: failsafe-reports/TEST-*.xml
reporter: java-junit
list-tests: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
list-suites: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
fail-on-error: 'false'
- name: Check for WTR reports
id: wtr-check
run: |
count=$(find wtr-reports -name 'wtr-results.xml' 2>/dev/null | wc -l | tr -d ' ')
echo "count=$count" >> $GITHUB_OUTPUT
- name: WTR Tests header
if: steps.wtr-check.outputs.count != '0' && (needs.fast-tests.result == 'success' || needs.fast-tests.outputs.wtr-failed == 'true')
run: echo "## WTR Tests" >> $GITHUB_STEP_SUMMARY
- name: Publish WTR results
id: wtr-dorny
if: steps.wtr-check.outputs.count != '0' && (needs.fast-tests.result == 'success' || needs.fast-tests.outputs.wtr-failed == 'true')
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
with:
name: WTR Tests
path: wtr-reports/**/wtr-results.xml
reporter: java-junit
list-tests: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
list-suites: ${{ env.has_failures == 'true' && 'failed' || 'all' }}
fail-on-error: 'false'
- name: WTR setup error
if: steps.wtr-check.outputs.count == '0' && needs.fast-tests.outputs.wtr-failed == 'true'
run: |
echo ":x: WTR tests did not produce results. Check the **WTR tests** job for details." >> $GITHUB_STEP_SUMMARY
- uses: actions/download-artifact@v8
if: needs.its.result != 'success'
continue-on-error: true
with:
pattern: error-screenshots-*
merge-multiple: true
path: error-screenshots
- name: Upload merged error screenshots
if: needs.its.result != 'success'
uses: actions/upload-artifact@v6
with:
name: error-screenshots
path: error-screenshots/
retention-days: 5
if-no-files-found: ignore
- name: Delete intermediate artifacts
if: always()
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
run: |
gh api --paginate "repos/$GH_REPO/actions/runs/$RUN_ID/artifacts" \
--jq '.artifacts[] | select(
(.name | startswith("error-screenshots-")) or
(.name | startswith("failsafe-reports-")) or
.name == "wtr-reports" or
.name == "surefire-reports"
) | "\(.id) \(.name)"' \
| while read -r id name; do
echo "Deleting artifact $name (id=$id)"
gh api -X DELETE "repos/$GH_REPO/actions/artifacts/$id" || true
done
- name: Fail if any test failed
if: always()
run: |
failed=false
[[ "${{ steps.unit-dorny.outputs.conclusion }}" == "failure" ]] && failed=true
[[ "${{ steps.wtr-dorny.outputs.conclusion }}" == "failure" ]] && failed=true
[[ "${{ steps.it-dorny.outputs.conclusion }}" == "failure" ]] && failed=true
[[ "$failed" == "true" ]] && exit 1 || exit 0