diff --git a/.github/workflows/js-css-lint-test.yml b/.github/workflows/js-css-lint-test.yml index ea8538edeeb..36f1113ab36 100644 --- a/.github/workflows/js-css-lint-test.yml +++ b/.github/workflows/js-css-lint-test.yml @@ -78,6 +78,8 @@ jobs: name: JS Tests runs-on: ubuntu-latest timeout-minutes: 20 + env: + JEST_RESULTS_FILE: jest-results.json steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -88,8 +90,11 @@ jobs: run: npm i -w assets -w storybook -w tests/js --include-workspace-root - name: Jest Tests id: test-js - run: npm run test:js + run: npm run test:js -- -- --json --outputFile=../../${{ env.JEST_RESULTS_FILE }} continue-on-error: true + - name: Check for catastrophic suite failures + id: check-catastrophic + run: bash tests/js/bin/check-catastrophic-jest-failures.sh "${{ env.JEST_RESULTS_FILE }}" - name: Jest Tests (Retry Failures 1) id: test-js-retry-1 run: npm run test:js -- -- --onlyFailures @@ -103,3 +108,8 @@ jobs: - name: Jest Tests (Retry Failures 3) run: npm run test:js -- -- --onlyFailures if: steps.test-js-retry-2.outcome == 'failure' + - name: Clean up Jest results file + run: | + if [ -f ${{ env.JEST_RESULTS_FILE }} ]; then + rm -f ${{ env.JEST_RESULTS_FILE }} + fi diff --git a/assets/js/modules/ads/components/notifications/AdsModuleSetupCTABanner.test.js b/assets/js/modules/ads/components/notifications/AdsModuleSetupCTABanner.test.js index a32fe20bdf5..7bd2b296b7f 100644 --- a/assets/js/modules/ads/components/notifications/AdsModuleSetupCTABanner.test.js +++ b/assets/js/modules/ads/components/notifications/AdsModuleSetupCTABanner.test.js @@ -24,7 +24,7 @@ * Internal dependencies */ const mockShowTooltip = jest.fn(); -jest.mock( '../../../../components/AdminMenuTooltip', () => ( { +jest.mock( '../../../../components/AdminScreenTooltip', () => ( { __esModule: true, default: jest.fn(), useShowTooltip: jest.fn( () => mockShowTooltip ), diff --git a/assets/js/modules/adsense/components/dashboard/AdBlockingRecoverySetupCTAWidget.test.js b/assets/js/modules/adsense/components/dashboard/AdBlockingRecoverySetupCTAWidget.test.js index 291b739a867..2d7c7b57491 100644 --- a/assets/js/modules/adsense/components/dashboard/AdBlockingRecoverySetupCTAWidget.test.js +++ b/assets/js/modules/adsense/components/dashboard/AdBlockingRecoverySetupCTAWidget.test.js @@ -67,7 +67,7 @@ const mockTrackEvent = jest.spyOn( tracking, 'trackEvent' ); mockTrackEvent.mockImplementation( () => Promise.resolve() ); const mockShowTooltip = jest.fn(); -jest.mock( '../../../../components/AdminMenuTooltip', () => ( { +jest.mock( '../../../../components/AdminScreenTooltip', () => ( { __esModule: true, default: jest.fn(), useShowTooltip: jest.fn( () => mockShowTooltip ), diff --git a/assets/js/modules/adsense/components/dashboard/AdSenseConnectCTAWidget.test.js b/assets/js/modules/adsense/components/dashboard/AdSenseConnectCTAWidget.test.js index 486b00a772d..b34e4b88602 100644 --- a/assets/js/modules/adsense/components/dashboard/AdSenseConnectCTAWidget.test.js +++ b/assets/js/modules/adsense/components/dashboard/AdSenseConnectCTAWidget.test.js @@ -20,7 +20,7 @@ * Internal dependencies */ const mockShowTooltip = jest.fn(); -jest.mock( '../../../../components/AdminMenuTooltip', () => ( { +jest.mock( '../../../../components/AdminScreenTooltip', () => ( { __esModule: true, default: jest.fn(), useShowTooltip: jest.fn( () => mockShowTooltip ), diff --git a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTABanner.test.js b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTABanner.test.js index b727683dedf..a3bdd63dc16 100644 --- a/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTABanner.test.js +++ b/assets/js/modules/analytics-4/components/audience-segmentation/dashboard/AudienceSegmentationSetupCTABanner.test.js @@ -20,7 +20,7 @@ * Internal dependencies */ const mockShowTooltip = jest.fn(); -jest.mock( '../../../../../components/AdminMenuTooltip', () => ( { +jest.mock( '../../../../../components/AdminScreenTooltip', () => ( { __esModule: true, default: jest.fn(), useShowTooltip: jest.fn( () => mockShowTooltip ), diff --git a/tests/js/bin/check-catastrophic-jest-failures.sh b/tests/js/bin/check-catastrophic-jest-failures.sh new file mode 100644 index 00000000000..b45fb606eda --- /dev/null +++ b/tests/js/bin/check-catastrophic-jest-failures.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Detect "catastrophic" Jest failures (runtime error test suites / missing results file) +# Usage: check-catastrophic-jest-failures.sh [results-json-path] +# Default results JSON filename: jest-results.json (looked for in current working directory). +# Exits non‑zero if a catastrophic condition is detected so CI can abort early. + +set -euo pipefail + +RESULTS_FILE="${1:-jest-results.json}" + +if [ ! -f "$RESULTS_FILE" ]; then + echo "❌ Catastrophic Jest failure: no $RESULTS_FILE produced (possible crash or misconfiguration)." >&2 + exit 1 +fi + +# Try jq if available for resilience against weird formatting; fallback to node require. +if command -v jq >/dev/null 2>&1; then + NUM_RUNTIME_ERRORS=$(jq -r '.numRuntimeErrorTestSuites // 0' "$RESULTS_FILE") +else + NUM_RUNTIME_ERRORS=$(node -e "const f=require('./${RESULTS_FILE}');console.log(f.numRuntimeErrorTestSuites||0);") +fi + +if [ "${NUM_RUNTIME_ERRORS}" -gt 0 ]; then + echo "❌ Catastrophic Jest failure: ${NUM_RUNTIME_ERRORS} test suite(s) had runtime errors (syntax/dependency issues). Aborting without retries." >&2 + exit 1 +fi + +echo "No catastrophic suite failures detected. Proceeding to potential retries for normal test failures." +exit 0