Skip to content

Report intermittent tests #1842

Report intermittent tests

Report intermittent tests #1842

name: Report intermittent tests
on:
schedule:
- cron: "42 04 * * *"
workflow_dispatch:
jobs:
backend-build:
name: Build backend 🏗️
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-env
- name: Build backend
run: ./gradlew classes jar bootJar buildTestEmails
- name: Upload backend build result
uses: ./.github/actions/upload-backend-build
backend-test:
name: BT ‍🔎
needs: [backend-build]
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
command:
[
"server-app:runContextRecreatingTests",
"server-app:runStandardTests",
"server-app:runWebsocketTests",
"server-app:runWithoutEeTests",
"ee-test:test",
"data:test",
"security:test",
"ktlint:test",
]
steps:
- uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-env
with:
node: "false"
- name: Download backend build result
uses: ./.github/actions/download-backend-build
- name: Remove ee
if: matrix.command == 'server-app:runWithoutEeTests'
run: |
rm -rf ./ee
- name: Run backend tests
timeout-minutes: 30
run: ./gradlew ${{ matrix.command }}
env:
SKIP_SERVER_BUILD: true
SKIP_EMAIL_BUILD: true
DETECT_FLAKY_TESTS: true
- name: Get report name
id: reportName
if: always()
run: |
reportName=${{ matrix.command }}
echo "reportName=${reportName//\:/_}" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
if: always()
with:
name: backend_test_reports_${{ steps.reportName.outputs.reportName }}
path: |
./**/build/reports/**/*
- name: Test Report
uses: dorny/test-reporter@v1
if: always()
with:
name: Test report for ${{ matrix.command }}
path: "**/build/test-results/**/TEST-*.xml"
fail-on-error: false
reporter: java-junit
- name: Collect flaky and failed tests
if: always()
continue-on-error: true
run: python3 scripts/collect-flaky-tests.py "${{ steps.reportName.outputs.reportName }}"
- uses: actions/upload-artifact@v4
if: always()
with:
name: flaky_tests_${{ steps.reportName.outputs.reportName }}
path: |
flaky-tests-${{ steps.reportName.outputs.reportName }}.txt
failed-tests-${{ steps.reportName.outputs.reportName }}.txt
if-no-files-found: ignore
frontend-build:
name: Build frontend 🏗️
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-env
- name: Build webapp
run: ./gradlew buildWebapp
env:
TOLGEE_API_KEY: ${{secrets.TOLGEE_API_KEY}}
TOLGEE_API_URL: ${{secrets.TOLGEE_API_URL}}
- name: Upload built webapp
uses: actions/upload-artifact@v4
with:
name: webapp
path: webapp/dist
e2e-install-deps:
name: Install E2E dependencies ⬇️
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-env
with:
java: "false"
- name: Install dependencies
run: npm ci
working-directory: ./e2e
- name: Tar node modules
run: |
tar --zstd -cf ~/node_modules.tar.zst ./e2e/node_modules
- name: Tar Cypress cache
run: |
tar --zstd -cf ~/cypress_cache.tar.zst ./Cypress
working-directory: /home/runner/.cache/
- uses: actions/upload-artifact@v4
with:
name: e2e_deps
path: |
~/node_modules.tar.zst
~/cypress_cache.tar.zst
e2e:
needs: [frontend-build, backend-build, e2e-install-deps]
runs-on: ubuntu-24.04
name: E2E testing ‍🔎
strategy:
fail-fast: false
matrix:
total_jobs: [15]
job_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
steps:
- uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-env
- name: Download backend build result
uses: ./.github/actions/download-backend-build
- name: Download frontend build result
uses: actions/download-artifact@v4
with:
name: webapp
path: ./webapp/dist
- name: Download dependencies
uses: actions/download-artifact@v4
with:
name: e2e_deps
path: ~/
- name: Untar node modules
run: |
tar --zstd -xf ~/node_modules.tar.zst ./e2e/node_modules
- name: Create cache directory
run: |
mkdir -p ~/.cache
- name: Untar Cypress cache
run: |
tar --zstd -xf ~/cypress_cache.tar.zst
working-directory: /home/runner/.cache/
- name: Run e2e test
timeout-minutes: 30
run: ./gradlew runE2e -x bootJar -x buildWebapp
env:
TOLGEE_API_KEY: ${{secrets.TOLGEE_API_KEY}}
TOLGEE_API_URL: ${{secrets.TOLGEE_API_URL}}
SKIP_WEBAPP_BUILD: true
SKIP_SERVER_BUILD: true
SKIP_EMAIL_BUILD: true
SKIP_INSTALL_E2E_DEPS: true
E2E_TOTAL_JOBS: ${{matrix.total_jobs}}
E2E_JOB_INDEX: ${{matrix.job_index}}
DETECT_FLAKY_TESTS: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: flaky_tests_e2e_${{ matrix.job_index }}
path: |
./e2e/flaky-tests-e2e-${{ matrix.job_index }}.txt
./e2e/failed-tests-e2e-${{ matrix.job_index }}.txt
if-no-files-found: ignore
- uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e_${{ matrix.job_index }}
path: |
./e2e/cypress/videos/**/*
./e2e/cypress/screenshots/**/*
./e2e/server
- uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e_server_log_${{ matrix.job_index }}
path: |
./e2e/server.log
cleanup:
if: always()
needs: [e2e, backend-test]
name: Delete artifacts 🧹
runs-on: ubuntu-24.04
steps:
- uses: geekyeggo/delete-artifact@v5
with:
name: |
e2e_deps
webapp
backend
failOnError: false
notify:
name: Report flaky tests 🔔
needs: [backend-test, e2e]
if: always()
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
pattern: flaky_tests_*
merge-multiple: true
path: ./flaky
continue-on-error: true
# Flaky tests (failed then passed on retry) and hard failures (failed
# every retry) are collected into separate artifacts. Only flaky tests
# are reported to the board; hard failures are surfaced in Slack only.
- name: Summarize results
id: summary
run: |
flaky=""
failed=""
if compgen -G './flaky/flaky-tests-*.txt' > /dev/null; then
flaky=$(cat ./flaky/flaky-tests-*.txt | sed '/^[[:space:]]*$/d' | sort -u)
fi
if compgen -G './flaky/failed-tests-*.txt' > /dev/null; then
failed=$(cat ./flaky/failed-tests-*.txt | sed '/^[[:space:]]*$/d' | sort -u)
fi
flaky_count=0
failed_count=0
if [ -n "$flaky" ]; then
flaky_count=$(printf '%s\n' "$flaky" | wc -l | tr -d ' ')
fi
if [ -n "$failed" ]; then
failed_count=$(printf '%s\n' "$failed" | wc -l | tr -d ' ')
fi
echo "Found $flaky_count flaky, $failed_count consistently failing tests"
{
echo "flaky_count=$flaky_count"
echo "failed_count=$failed_count"
echo 'flaky_list<<EOF'
[ -n "$flaky" ] && printf '%s\n' "$flaky" | head -30 | sed 's/.*/• `&`/'
echo 'EOF'
echo 'failed_list<<EOF'
[ -n "$failed" ] && printf '%s\n' "$failed" | head -30 | sed 's/.*/• `&`/'
echo 'EOF'
} >> "$GITHUB_OUTPUT"
# Always run so the sweep pass cleans up stale Suspected items even on
# nights with no flaky detections.
- name: Report flaky tests to project board
id: project
if: always()
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.PROJECTS_TOKEN }}
PROJECT_OWNER: tolgee
PROJECT_NUMBER: "4"
TEAM_ORG: tolgee
TEAM_SLUG: coders
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: python3 scripts/report-flaky-tests.py ./flaky .
# Newly confirmed flakes and hard failures are the actionable signals;
# raw per-run flaky counts (unpromoted Suspected items) don't trigger
# Slack on their own.
- name: Send Slack notification
if: steps.project.outputs.newly_confirmed_count != '0' || steps.summary.outputs.failed_count != '0'
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_FLAKY_TESTS_WEBHOOK_URL }}
NEWLY_CONFIRMED_TESTS: ${{ steps.project.outputs.newly_confirmed_list }}
NEWLY_CONFIRMED_COUNT: ${{ steps.project.outputs.newly_confirmed_count }}
FAILED_TESTS: ${{ steps.summary.outputs.failed_list }}
FAILED_COUNT: ${{ steps.summary.outputs.failed_count }}
TOTAL_CONFIRMED: ${{ steps.project.outputs.total_confirmed_count }}
TOTAL_SUSPECTED: ${{ steps.project.outputs.total_suspected_count }}
DELETED_SUSPECT: ${{ steps.project.outputs.deleted_suspect_count }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
PROJECT_URL: ${{ steps.project.outputs.project_url }}
run: |
{
if [ "${NEWLY_CONFIRMED_COUNT:-0}" != "0" ]; then
echo "🟡 ${NEWLY_CONFIRMED_COUNT} newly confirmed flaky test(s) in *${{ github.repository }}*"
echo ""
echo "$NEWLY_CONFIRMED_TESTS"
fi
if [ "${FAILED_COUNT:-0}" != "0" ]; then
if [ "${NEWLY_CONFIRMED_COUNT:-0}" != "0" ]; then echo ""; fi
echo "🔴 ${FAILED_COUNT} test(s) failing on every retry — broken, not flaky"
echo ""
echo "$FAILED_TESTS"
fi
echo ""
echo "<${RUN_URL}|View run>"
if [ -n "$PROJECT_URL" ]; then
board="<${PROJECT_URL}|Flaky tests board> — ${TOTAL_CONFIRMED:-0} confirmed, ${TOTAL_SUSPECTED:-0} suspected"
if [ "${DELETED_SUSPECT:-0}" != "0" ]; then
board="$board (${DELETED_SUSPECT} stale suspected cleaned up)"
fi
echo "$board"
fi
} > /tmp/slack-message.txt
curl --fail --show-error --silent \
--connect-timeout 5 --max-time 10 \
--retry 3 --retry-delay 2 --retry-connrefused \
-X POST -H 'Content-Type: application/json' \
-d "$(jq -n --rawfile text /tmp/slack-message.txt '{text: $text}')" \
"$SLACK_WEBHOOK_URL"