Skip to content
194 changes: 194 additions & 0 deletions .github/workflows/validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
name: Validation

on:
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: ''

permissions:
contents: read
checks: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: true

env:
FORK_COUNT: 5

jobs:
validation:
name: Build and Test
runs-on: ubuntu-latest
timeout-minutes: 120
services:
selenium:
image: selenium/standalone-chrome:latest
options: --shm-size=2g
ports:
- 4444:4444
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: Setup JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'

- name: Setup Node 20
uses: actions/setup-node@v6
with:
node-version: '20'

- 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: Show environment info
run: |
java -version
mvn -version
node --version
npm --version

- name: Compile all sources
run: mvn clean test-compile -DskipFrontend $MAVEN_ARGS

- name: Unit tests and install
run: |
mvn install -Drelease -T $FORK_COUNT $MAVEN_ARGS || {
echo "::warning::First attempt failed (Maven multi-thread race). Retrying..."
sleep 15
mvn install -Drelease -T $FORK_COUNT $MAVEN_ARGS
}

- name: Check for skip-ci
id: skip-ci
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
env:
GH_TOKEN: ${{ github.token }}
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"
exit 0
fi
pr_number="${{ github.event.pull_request.number }}"
commits=$(gh api "repos/${{ github.repository }}/pulls/$pr_number/commits" --jq '.[].commit.message')
if echo "$commits" | grep -q '\[skip ci\]'; then
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"

- 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 [ -z "$elements" ] && [ "${{ github.event_name }}" = "pull_request" ]; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this would only detect changed components when checking pull requests. It would be good to also have this for checks running from the merge queue.

Related to that, the workflow also needs the respective merge queue trigger.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

pr_number="${{ github.event.pull_request.number }}"
changed_files=$(gh api "repos/${{ github.repository }}/pulls/$pr_number/files" --jq '.[].filename')
elif [ -z "$elements" ] && [ "${{ 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"

- name: npm install
if: steps.skip-ci.outputs.skip != 'true'
run: npm install --ignore-scripts --silent --quiet --no-progress

- name: Merge ITs
if: steps.skip-ci.outputs.skip != 'true'
run: node scripts/mergeITs.js ${{ steps.components.outputs.elements }}

- name: Pre-compile merged ITs
if: steps.skip-ci.outputs.skip != 'true'
run: cd integration-tests && mvn compile test-compile $MAVEN_ARGS -DskipUnitTests

- name: WTR tests
if: steps.skip-ci.outputs.skip != 'true'
run: node scripts/wtr.js ${{ steps.components.outputs.elements }}

- name: Run integration tests
if: steps.skip-ci.outputs.skip != 'true'
env:
TBLICENSE: ${{ secrets.TB_LICENSE }}
run: |
args="$MAVEN_ARGS"
[ -n "$TBLICENSE" ] && args="$args -Dvaadin.testbench.developer.license=$TBLICENSE"
args="$args -Dtest.use.hub=true -Dcom.vaadin.testbench.Parameters.hubHostname=localhost -Dcom.vaadin.testbench.Parameters.hubPort=4444"
args="$args -Dfailsafe.rerunFailingTestsCount=2 -Dmaven.test.redirectTestOutputToFile=true"
mode="-Dfailsafe.forkCount=$FORK_COUNT -Dcom.vaadin.testbench.Parameters.testsInParallel=1"

mvn verify -Dvaadin.pnpm.enable -Drun-it -Drelease -Dvaadin.productionMode \
-Dvaadin.force.production.build=true \
$mode $args -pl integration-tests -DskipUnitTests

- name: Publish unit test results
if: always()
## Make sonar happy for external actions
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
with:
name: Unit Tests
path: '**/target/surefire-reports/TEST-*.xml'
reporter: java-junit

- name: Publish integration test results
if: always()
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2
with:
name: Integration Tests
path: 'integration-tests/target/failsafe-reports/TEST-*.xml'
reporter: java-junit

- name: Upload error screenshots
if: failure()
uses: actions/upload-artifact@v5
with:
name: error-screenshots
path: 'integration-tests/error-screenshots/'
retention-days: 5
6 changes: 5 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ cmd="node scripts/mergeITs.js "`echo $elements`
tcLog "Merge IT modules - $cmd"
$cmd || tcStatus 1 "Merging ITs failed"

## Pre-compile merged ITs to avoid incremental compilation issues
tcLog "Pre-compiling merged ITs"
(cd integration-tests && mvn compile test-compile $args -DskipUnitTests) || tcStatus 1 "Merged ITs compilation failed"

## Compute variable to run tests
[ -n "$TBLICENSE" ] && args="$args -Dvaadin.testbench.developer.license=$TBLICENSE"
[ -n "$TBHUB" ] && args="$args -Dtest.use.hub=true -Dcom.vaadin.testbench.Parameters.hubHostname=$TBHUB"
Expand Down Expand Up @@ -197,7 +201,7 @@ then
else
mode="-Dfailsafe.forkCount=$FORK_COUNT -Dcom.vaadin.testbench.Parameters.testsInParallel=$TESTS_IN_PARALLEL"
### Run IT's in merged module
cmd="mvn $verify -Drun-it -Drelease -Dvaadin.productionMode -Dfailsafe.rerunFailingTestsCount=2 $mode $args -pl integration-tests -DskipUnitTests"
cmd="mvn $verify -Drun-it -Drelease -Dvaadin.productionMode -Dvaadin.force.production.build=true -Dfailsafe.rerunFailingTestsCount=2 $mode $args -pl integration-tests -DskipUnitTests"
tcLog "Running merged ITs - mvn $verify -B -Drun-it -Drelease -pl integration-tests ..."
echo $cmd
$cmd
Expand Down
14 changes: 14 additions & 0 deletions scripts/mergeITs.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,20 @@ async function copySources() {
}
});

// clean stale Flow-generated files from previous runs
['package.json', 'package-lock.json', 'pnpm-lock.yaml', 'tsconfig.json', 'types.d.ts', 'vite.generated.ts', '.npmrc', '.pnpmfile.cjs']
.forEach(f => {
const file = `${itFolder}/${f}`;
if (fs.existsSync(file)) {
console.log(`removing ${file}`);
fs.rmSync(file);
}
});

// Create empty package.json so that build-frontend does not
// perform aggressive cleanup that deletes flow-build-info.json
fs.writeFileSync(`${itFolder}/package.json`, '{}');

modules.forEach(parent => {
const id = parent.replace('-parent', '');
console.log(`Copying ${parent}/${id}-integration-tests`);
Expand Down
4 changes: 2 additions & 2 deletions scripts/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ function generateHistoryItems() {
// === Maven Execution ===

function runMvn(module, modeKey, args, message, onOutput) {
const command = `mvn ${args.join(' ')}`;
const command = `mvn -B -ntp ${args.join(' ')}`;
args = [...args, '-Dstyle.color=always'];
console.log(`\n${message}\n`);
console.log(`\n${message}\n${command}\n`);

const mvn = spawn('mvn', args, { stdio: ['inherit', 'pipe', 'pipe'] });

Expand Down
2 changes: 1 addition & 1 deletion scripts/wtr.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function runTests() {
}

// Install the IT module dependencies
execSync(`mvn -DskipTests flow:prepare-frontend flow:build-frontend`, {
execSync(`mvn -DskipTests -Dvaadin.force.production.build=true flow:prepare-frontend flow:build-frontend`, {
cwd: itFolder,
stdio: 'inherit'
});
Expand Down