From 318088c2cd22e13597e3296dd7aece4fd95dd3ad Mon Sep 17 00:00:00 2001 From: Alex Lavrov <36633600+alexslavr@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:25:12 +0400 Subject: [PATCH 1/3] Wrap visual-tests-demos workflow to rerun jobs failed by spot termination while in merge queue --- .../visual-tests-demos-merge-queue.yml | 147 ++++++++++++++++++ .github/workflows/visual-tests-demos.yml | 2 +- 2 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/visual-tests-demos-merge-queue.yml diff --git a/.github/workflows/visual-tests-demos-merge-queue.yml b/.github/workflows/visual-tests-demos-merge-queue.yml new file mode 100644 index 000000000000..6ff1fcdac457 --- /dev/null +++ b/.github/workflows/visual-tests-demos-merge-queue.yml @@ -0,0 +1,147 @@ +name: Demos Visual Tests (merge queue) + +on: + merge_group: + +concurrency: + group: wf-${{ github.event.merge_group.head_sha || github.sha }}-${{ github.workflow }} + cancel-in-progress: false + +jobs: + attempt1: + name: Attempt 1 + uses: ./.github/workflows/visual-tests-demos.yml + secrets: inherit + + triage: + name: Triage attempt 1 + runs-on: ubuntu-latest + needs: [attempt1] + if: always() + timeout-minutes: 10 + permissions: + actions: read + outputs: + needs_retry: ${{ steps.decide.outputs.needs_retry }} + real_failure: ${{ steps.decide.outputs.real_failure }} + steps: + - id: decide + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + ATTEMPT1_RESULT: ${{ needs.attempt1.result }} + run: | + set -euo pipefail + + emit() { + echo "needs_retry=$1" >> "$GITHUB_OUTPUT" + echo "real_failure=$2" >> "$GITHUB_OUTPUT" + } + + if [ "$ATTEMPT1_RESULT" = "success" ]; then + echo "Attempt 1 passed." + emit false false + exit 0 + fi + + if [ "$ATTEMPT1_RESULT" = "cancelled" ]; then + echo "Attempt 1 was cancelled; not retrying." + emit false true + exit 0 + fi + + # Pull every job of this run and pick the ones under attempt1 that failed. + jobs=$(gh api --paginate "/repos/$REPO/actions/runs/$RUN_ID/jobs?per_page=100") + failed_ids=$(echo "$jobs" | jq -r ' + .jobs[] + | select(.conclusion=="failure") + | select(.name | startswith("Attempt 1 /")) + | .id') + + if [ -z "$failed_ids" ]; then + echo "Attempt 1 failed but no failed inner job identified; not retrying." + emit false true + exit 0 + fi + + all_spot=1 + while IFS= read -r jid; do + [ -n "$jid" ] || continue + jname=$(echo "$jobs" | jq -r --argjson id "$jid" \ + '.jobs[] | select(.id==$id) | .name') + + spot=0 + + # Signature 1: GitHub appends this banner when the runner disconnects. + if gh api -H "Accept: application/vnd.github.raw" \ + "/repos/$REPO/actions/jobs/$jid/logs" 2>/dev/null \ + | tail -400 | grep -q "lost communication with the server"; then + spot=1 + fi + + # Signature 2: last step never completed (runner died silently, job + # timed out with an in-progress step). + if [ $spot -eq 0 ]; then + last_status=$(echo "$jobs" | jq -r --argjson id "$jid" \ + '.jobs[] | select(.id==$id) | .steps[-1].status // ""') + last_conclusion=$(echo "$jobs" | jq -r --argjson id "$jid" \ + '.jobs[] | select(.id==$id) | .steps[-1].conclusion // ""') + if [ "$last_status" = "in_progress" ] || [ -z "$last_conclusion" ]; then + spot=1 + fi + fi + + if [ $spot -eq 1 ]; then + echo "$jname → spot-like failure (retriable)" + else + echo "$jname → real failure" + all_spot=0 + fi + done <<< "$failed_ids" + + if [ $all_spot -eq 1 ]; then + echo "All attempt-1 failures look like spot terminations; retrying." + emit true false + else + echo "At least one real failure in attempt 1; not retrying." + emit false true + fi + + attempt2: + name: Attempt 2 + needs: [attempt1, triage] + if: needs.triage.outputs.needs_retry == 'true' + uses: ./.github/workflows/visual-tests-demos.yml + secrets: inherit + + gate: + name: Merge queue gate + runs-on: ubuntu-latest + needs: [attempt1, triage, attempt2] + if: always() + timeout-minutes: 5 + steps: + - name: Verdict + env: + ATTEMPT1: ${{ needs.attempt1.result }} + ATTEMPT2: ${{ needs.attempt2.result }} + REAL_FAILURE: ${{ needs.triage.outputs.real_failure }} + run: | + set -e + echo "attempt1=$ATTEMPT1 attempt2=$ATTEMPT2 real_failure=$REAL_FAILURE" + + if [ "$REAL_FAILURE" = "true" ]; then + echo "Real failure detected in attempt 1." + exit 1 + fi + if [ "$ATTEMPT1" = "success" ]; then + echo "Attempt 1 passed." + exit 0 + fi + if [ "$ATTEMPT2" = "success" ]; then + echo "Attempt 1 flaked on spot; attempt 2 recovered." + exit 0 + fi + echo "Retry did not recover." + exit 1 diff --git a/.github/workflows/visual-tests-demos.yml b/.github/workflows/visual-tests-demos.yml index 54c485c1817d..7e17bd05b833 100644 --- a/.github/workflows/visual-tests-demos.yml +++ b/.github/workflows/visual-tests-demos.yml @@ -8,7 +8,7 @@ on: pull_request: paths-ignore: - 'apps/**/*.md' - merge_group: + workflow_call: workflow_dispatch: env: From 14566b150d386e243843ea20d6b9a88296a8d8c8 Mon Sep 17 00:00:00 2001 From: Alex Lavrov <36633600+alexslavr@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:48:23 +0400 Subject: [PATCH 2/3] Retry specified jobs, not entire workflow --- .../visual-tests-demos-merge-queue.yml | 148 ++++++++++----- .github/workflows/visual-tests-demos.yml | 169 +++++++++++++----- 2 files changed, 226 insertions(+), 91 deletions(-) diff --git a/.github/workflows/visual-tests-demos-merge-queue.yml b/.github/workflows/visual-tests-demos-merge-queue.yml index 6ff1fcdac457..eef1e8f90f9d 100644 --- a/.github/workflows/visual-tests-demos-merge-queue.yml +++ b/.github/workflows/visual-tests-demos-merge-queue.yml @@ -22,8 +22,9 @@ jobs: permissions: actions: read outputs: - needs_retry: ${{ steps.decide.outputs.needs_retry }} + is_retry: ${{ steps.decide.outputs.is_retry }} real_failure: ${{ steps.decide.outputs.real_failure }} + retry_targets: ${{ steps.decide.outputs.retry_targets }} steps: - id: decide env: @@ -35,84 +36,139 @@ jobs: set -euo pipefail emit() { - echo "needs_retry=$1" >> "$GITHUB_OUTPUT" - echo "real_failure=$2" >> "$GITHUB_OUTPUT" + echo "is_retry=$1" >> "$GITHUB_OUTPUT" + echo "real_failure=$2" >> "$GITHUB_OUTPUT" + echo "retry_targets=$3" >> "$GITHUB_OUTPUT" } if [ "$ATTEMPT1_RESULT" = "success" ]; then echo "Attempt 1 passed." - emit false false + emit false false '{}' exit 0 fi - if [ "$ATTEMPT1_RESULT" = "cancelled" ]; then - echo "Attempt 1 was cancelled; not retrying." - emit false true + echo "Attempt 1 cancelled; not retrying." + emit false true '{}' exit 0 fi - # Pull every job of this run and pick the ones under attempt1 that failed. jobs=$(gh api --paginate "/repos/$REPO/actions/runs/$RUN_ID/jobs?per_page=100") - failed_ids=$(echo "$jobs" | jq -r ' - .jobs[] - | select(.conclusion=="failure") - | select(.name | startswith("Attempt 1 /")) - | .id') - - if [ -z "$failed_ids" ]; then - echo "Attempt 1 failed but no failed inner job identified; not retrying." - emit false true + failed=$(echo "$jobs" | jq -c ' + [ .jobs[] + | select(.conclusion=="failure") + | select(.name | startswith("Attempt 1 /")) + | { id, name, steps } ]') + + count=$(echo "$failed" | jq 'length') + if [ "$count" -eq 0 ]; then + echo "Attempt 1 failed but no per-job failure identified; not retrying." + emit false true '{}' exit 0 fi - all_spot=1 - while IFS= read -r jid; do - [ -n "$jid" ] || continue - jname=$(echo "$jobs" | jq -r --argjson id "$jid" \ - '.jobs[] | select(.id==$id) | .name') - + retry_json='{}' + all_retriable=1 + + for i in $(seq 0 $((count - 1))); do + entry=$(echo "$failed" | jq -c ".[$i]") + jid=$(echo "$entry" | jq -r '.id') + jname=$(echo "$entry" | jq -r '.name') + child=${jname#"Attempt 1 / "} + + category="" + case "$child" in + "CSP check (jQuery)") + category="csp-check-jquery" + ;; + "CSP check ("*")") + category="csp-check-frameworks" + ;; + angular\(*\)-screenshots-*) + category="testcafe-frameworks-all" + ;; + *) + echo "$child → real failure (not whitelisted for retry)" + all_retriable=0 + continue + ;; + esac + + # Spot signature: disconnect banner in the log tail, OR last step never + # completed (runner died silently, job hit timeout with an in-progress step). spot=0 - - # Signature 1: GitHub appends this banner when the runner disconnects. if gh api -H "Accept: application/vnd.github.raw" \ "/repos/$REPO/actions/jobs/$jid/logs" 2>/dev/null \ | tail -400 | grep -q "lost communication with the server"; then spot=1 fi - - # Signature 2: last step never completed (runner died silently, job - # timed out with an in-progress step). if [ $spot -eq 0 ]; then - last_status=$(echo "$jobs" | jq -r --argjson id "$jid" \ - '.jobs[] | select(.id==$id) | .steps[-1].status // ""') - last_conclusion=$(echo "$jobs" | jq -r --argjson id "$jid" \ - '.jobs[] | select(.id==$id) | .steps[-1].conclusion // ""') + last_status=$(echo "$entry" | jq -r '.steps[-1].status // ""') + last_conclusion=$(echo "$entry" | jq -r '.steps[-1].conclusion // ""') if [ "$last_status" = "in_progress" ] || [ -z "$last_conclusion" ]; then spot=1 fi fi - if [ $spot -eq 1 ]; then - echo "$jname → spot-like failure (retriable)" - else - echo "$jname → real failure" - all_spot=0 + if [ $spot -eq 0 ]; then + echo "$child → real failure (no spot signature)" + all_retriable=0 + continue fi - done <<< "$failed_ids" - - if [ $all_spot -eq 1 ]; then - echo "All attempt-1 failures look like spot terminations; retrying." - emit true false - else - echo "At least one real failure in attempt 1; not retrying." - emit false true + + echo "$child → spot-like failure, category=$category" + case "$category" in + csp-check-jquery) + retry_json=$(echo "$retry_json" | jq -c '.["csp-check-jquery"] = true') + ;; + csp-check-frameworks) + inner=$(echo "$child" | sed -E 's/^CSP check \((.+)\)$/\1/') + fw=$(echo "$inner" | awk '{print $1}') + shard=$(echo "$inner" | awk '{print $2}') + si=${shard%/*} + st=${shard#*/} + retry_json=$(echo "$retry_json" | jq -c \ + --arg fw "$fw" --argjson si "$si" --argjson st "$st" \ + '.["csp-check-frameworks"].include = + ((.["csp-check-frameworks"].include // []) + + [{FRAMEWORK:$fw, SHARD_INDEX:$si, SHARD_TOTAL:$st}])') + ;; + testcafe-frameworks-all) + constel=$(echo "$child" | sed -E 's/^(.+)-screenshots-.+$/\1/') + theme=$(echo "$child" | sed -E 's/^.+-screenshots-(.+)$/\1/') + retry_json=$(echo "$retry_json" | jq -c \ + --arg constel "$constel" --arg theme "$theme" \ + '.["testcafe-frameworks-all"].include = + ((.["testcafe-frameworks-all"].include // []) + + [{CONSTEL:$constel, THEME:$theme}])') + ;; + esac + done + + if [ $all_retriable -eq 0 ]; then + echo "At least one non-retriable failure; not retrying." + emit false true '{}' + exit 0 fi + target_count=$(echo "$retry_json" | jq 'to_entries | length') + if [ "$target_count" -eq 0 ]; then + echo "No retriable targets identified; not retrying." + emit false true '{}' + exit 0 + fi + + echo "Attempt 2 will re-run:" + echo "$retry_json" | jq . + emit true false "$(echo "$retry_json" | jq -c .)" + attempt2: name: Attempt 2 needs: [attempt1, triage] - if: needs.triage.outputs.needs_retry == 'true' + if: needs.triage.outputs.is_retry == 'true' uses: ./.github/workflows/visual-tests-demos.yml + with: + is_retry: true + retry_targets: ${{ needs.triage.outputs.retry_targets }} secrets: inherit gate: diff --git a/.github/workflows/visual-tests-demos.yml b/.github/workflows/visual-tests-demos.yml index 7e17bd05b833..7cce89dceca6 100644 --- a/.github/workflows/visual-tests-demos.yml +++ b/.github/workflows/visual-tests-demos.yml @@ -9,6 +9,15 @@ on: paths-ignore: - 'apps/**/*.md' workflow_call: + inputs: + is_retry: + description: 'Set by the merge-queue wrapper on attempt 2.' + type: boolean + default: false + retry_targets: + description: 'JSON map of jobs / matrix entries to re-run on retry.' + type: string + default: '{}' workflow_dispatch: env: @@ -142,6 +151,84 @@ jobs: echo "matrix=$matrix" >> $GITHUB_OUTPUT + determine-frameworks-all-matrix: + runs-on: ubuntu-latest + name: Determine testcafe-frameworks-all matrix + needs: [check-should-run, determine-framework-tests-scope] + if: | + always() && + needs.check-should-run.outputs.should-run == 'true' && + needs.determine-framework-tests-scope.result == 'success' && + needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Build matrix + id: matrix + env: + IS_RETRY: ${{ inputs.is_retry }} + RETRY_TARGETS: ${{ inputs.retry_targets }} + run: | + set -euo pipefail + if [ "$IS_RETRY" = "true" ]; then + matrix=$(echo "$RETRY_TARGETS" | jq -c '.["testcafe-frameworks-all"] // {"include":[]}') + else + matrix=$(cat <<'JSON' + { + "CONSTEL": [ + "react(1/3)","react(2/3)","react(3/3)", + "vue(1/5)","vue(2/5)","vue(3/5)","vue(4/5)","vue(5/5)", + "angular(1/10)","angular(2/10)","angular(3/10)","angular(4/10)","angular(5/10)", + "angular(6/10)","angular(7/10)","angular(8/10)","angular(9/10)","angular(10/10)" + ], + "THEME": ["fluent.blue.light"] + } + JSON + ) + matrix=$(echo "$matrix" | jq -c .) + fi + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + + determine-csp-frameworks-matrix: + runs-on: ubuntu-latest + name: Determine csp-check-frameworks matrix + needs: [check-should-run, determine-framework-tests-scope] + if: | + always() && + needs.check-should-run.outputs.should-run == 'true' && + needs.determine-framework-tests-scope.result == 'success' && + needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Build matrix + id: matrix + env: + IS_RETRY: ${{ inputs.is_retry }} + RETRY_TARGETS: ${{ inputs.retry_targets }} + run: | + set -euo pipefail + if [ "$IS_RETRY" = "true" ]; then + matrix=$(echo "$RETRY_TARGETS" | jq -c '.["csp-check-frameworks"] // {"include":[]}') + else + matrix=$(cat <<'JSON' + { + "include": [ + { "FRAMEWORK": "React", "SHARD_INDEX": 1, "SHARD_TOTAL": 2 }, + { "FRAMEWORK": "React", "SHARD_INDEX": 2, "SHARD_TOTAL": 2 }, + { "FRAMEWORK": "Vue", "SHARD_INDEX": 1, "SHARD_TOTAL": 2 }, + { "FRAMEWORK": "Vue", "SHARD_INDEX": 2, "SHARD_TOTAL": 2 }, + { "FRAMEWORK": "Angular", "SHARD_INDEX": 1, "SHARD_TOTAL": 3 }, + { "FRAMEWORK": "Angular", "SHARD_INDEX": 2, "SHARD_TOTAL": 3 }, + { "FRAMEWORK": "Angular", "SHARD_INDEX": 3, "SHARD_TOTAL": 3 } + ] + } + JSON + ) + matrix=$(echo "$matrix" | jq -c .) + fi + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + build-devextreme: runs-on: devextreme-shr2 name: Build DevExtreme @@ -149,7 +236,8 @@ jobs: if: | always() && needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' + needs.determine-framework-tests-scope.result == 'success' && + inputs.is_retry != true env: NODE_OPTIONS: --max-old-space-size=8192 timeout-minutes: 30 @@ -283,7 +371,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && - needs.build-devextreme.result == 'success' + needs.build-devextreme.result == 'success' && + inputs.is_retry != true env: NODE_OPTIONS: --max-old-space-size=8192 @@ -355,7 +444,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && - needs.build-devextreme.result == 'success' + needs.build-devextreme.result == 'success' && + inputs.is_retry != true runs-on: ubuntu-latest timeout-minutes: 60 @@ -498,7 +588,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-devextreme.result == 'success' + needs.build-devextreme.result == 'success' && + inputs.is_retry != true steps: - name: Get sources @@ -611,7 +702,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && - needs.build-devextreme.result == 'success' + needs.build-devextreme.result == 'success' && + inputs.is_retry != true steps: - name: Get sources @@ -678,7 +770,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-jquery-test-matrix.result == 'success' && - needs.build-devextreme.result == 'success' + needs.build-devextreme.result == 'success' && + inputs.is_retry != true strategy: fail-fast: false matrix: ${{ fromJson(needs.determine-jquery-test-matrix.outputs.matrix) }} @@ -830,37 +923,20 @@ jobs: if-no-files-found: ignore testcafe-frameworks-all: - needs: [check-should-run, determine-framework-tests-scope, build-demos] + needs: [check-should-run, determine-framework-tests-scope, build-demos, determine-frameworks-all-matrix] if: | always() && needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'all' && - needs.build-demos.result == 'success' + needs.determine-frameworks-all-matrix.result == 'success' && + ( + (inputs.is_retry != true && needs.build-demos.result == 'success') || + (inputs.is_retry == true && fromJson(inputs.retry_targets)['testcafe-frameworks-all'] != null) + ) strategy: fail-fast: false - matrix: - CONSTEL: [ - react(1/3), - react(2/3), - react(3/3), - vue(1/5), - vue(2/5), - vue(3/5), - vue(4/5), - vue(5/5), - angular(1/10), - angular(2/10), - angular(3/10), - angular(4/10), - angular(5/10), - angular(6/10), - angular(7/10), - angular(8/10), - angular(9/10), - angular(10/10), - ] - THEME: ['fluent.blue.light'] + matrix: ${{ fromJson(needs.determine-frameworks-all-matrix.outputs.matrix) }} runs-on: devextreme-shr2 name: ${{ matrix.CONSTEL }}-screenshots-${{ matrix.THEME }} @@ -976,6 +1052,7 @@ jobs: name: screenshots-${{ env.JOB_NAME }} path: ${{ github.workspace }}/apps/demos/testing/artifacts/compared-screenshots/**/* if-no-files-found: ignore + overwrite: true testcafe-frameworks-changed: needs: [check-should-run, determine-framework-tests-scope, build-demos] @@ -984,7 +1061,8 @@ jobs: needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope == 'changed' && - needs.build-demos.result == 'success' + needs.build-demos.result == 'success' && + inputs.is_retry != true strategy: fail-fast: false matrix: @@ -1144,6 +1222,7 @@ jobs: name: screenshots-angular pattern: screenshots-angular* delete-merged: true + overwrite: true - name: Merge jQuery accessibility reports if: needs.testcafe-jquery.result == 'failure' @@ -1160,7 +1239,10 @@ jobs: if: | always() && needs.check-should-run.outputs.should-run == 'true' && - needs.build-devextreme.result == 'success' + ( + (inputs.is_retry != true && needs.build-devextreme.result == 'success') || + (inputs.is_retry == true && fromJson(inputs.retry_targets)['csp-check-jquery'] == true) + ) runs-on: devextreme-shr2 timeout-minutes: 60 env: @@ -1226,29 +1308,24 @@ jobs: name: csp-violations-jquery path: apps/demos/csp-reports/ if-no-files-found: ignore + overwrite: true csp-check-frameworks: name: ${{ matrix.SHARD_TOTAL == 1 && format('CSP check ({0})', matrix.FRAMEWORK) || format('CSP check ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} - needs: [check-should-run, determine-framework-tests-scope, build-devextreme] + needs: [check-should-run, determine-framework-tests-scope, build-devextreme, determine-csp-frameworks-matrix] if: | always() && needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && - needs.build-devextreme.result == 'success' + needs.determine-csp-frameworks-matrix.result == 'success' && + ( + (inputs.is_retry != true && needs.build-devextreme.result == 'success') || + (inputs.is_retry == true && fromJson(inputs.retry_targets)['csp-check-frameworks'] != null) + ) strategy: fail-fast: false - # Angular AOT bundling is slow, so it is split into parallel shards - # (CSP_SHARD_*, round-robin in csp-bundle-angular.js). React/Vue stay light. - matrix: - include: - - { FRAMEWORK: React, SHARD_INDEX: 1, SHARD_TOTAL: 2 } - - { FRAMEWORK: React, SHARD_INDEX: 2, SHARD_TOTAL: 2 } - - { FRAMEWORK: Vue, SHARD_INDEX: 1, SHARD_TOTAL: 2 } - - { FRAMEWORK: Vue, SHARD_INDEX: 2, SHARD_TOTAL: 2 } - - { FRAMEWORK: Angular, SHARD_INDEX: 1, SHARD_TOTAL: 3 } - - { FRAMEWORK: Angular, SHARD_INDEX: 2, SHARD_TOTAL: 3 } - - { FRAMEWORK: Angular, SHARD_INDEX: 3, SHARD_TOTAL: 3 } + matrix: ${{ fromJson(needs.determine-csp-frameworks-matrix.outputs.matrix) }} runs-on: devextreme-shr2 timeout-minutes: 60 env: @@ -1325,6 +1402,7 @@ jobs: name: csp-violations-${{ matrix.FRAMEWORK }}${{ matrix.SHARD_TOTAL != 1 && format('-shard{0}', matrix.SHARD_INDEX) || '' }} path: apps/demos/csp-reports/ if-no-files-found: ignore + overwrite: true csp-report-summary: name: CSP Violations Summary @@ -1396,3 +1474,4 @@ jobs: name: csp-violations-report path: apps/demos/csp-reports/ if-no-files-found: ignore + overwrite: true From 1f69bfa4b87c549d4eef5bb65613ae67781c6e37 Mon Sep 17 00:00:00 2001 From: Alex Lavrov <36633600+alexslavr@users.noreply.github.com> Date: Thu, 20 Aug 2026 18:14:03 +0400 Subject: [PATCH 3/3] Do not retry CSP checks for now --- .../visual-tests-demos-merge-queue.yml | 21 ------ .github/workflows/visual-tests-demos.yml | 70 +++++-------------- 2 files changed, 16 insertions(+), 75 deletions(-) diff --git a/.github/workflows/visual-tests-demos-merge-queue.yml b/.github/workflows/visual-tests-demos-merge-queue.yml index eef1e8f90f9d..20f4797f47e7 100644 --- a/.github/workflows/visual-tests-demos-merge-queue.yml +++ b/.github/workflows/visual-tests-demos-merge-queue.yml @@ -77,12 +77,6 @@ jobs: category="" case "$child" in - "CSP check (jQuery)") - category="csp-check-jquery" - ;; - "CSP check ("*")") - category="csp-check-frameworks" - ;; angular\(*\)-screenshots-*) category="testcafe-frameworks-all" ;; @@ -117,21 +111,6 @@ jobs: echo "$child → spot-like failure, category=$category" case "$category" in - csp-check-jquery) - retry_json=$(echo "$retry_json" | jq -c '.["csp-check-jquery"] = true') - ;; - csp-check-frameworks) - inner=$(echo "$child" | sed -E 's/^CSP check \((.+)\)$/\1/') - fw=$(echo "$inner" | awk '{print $1}') - shard=$(echo "$inner" | awk '{print $2}') - si=${shard%/*} - st=${shard#*/} - retry_json=$(echo "$retry_json" | jq -c \ - --arg fw "$fw" --argjson si "$si" --argjson st "$st" \ - '.["csp-check-frameworks"].include = - ((.["csp-check-frameworks"].include // []) - + [{FRAMEWORK:$fw, SHARD_INDEX:$si, SHARD_TOTAL:$st}])') - ;; testcafe-frameworks-all) constel=$(echo "$child" | sed -E 's/^(.+)-screenshots-.+$/\1/') theme=$(echo "$child" | sed -E 's/^.+-screenshots-(.+)$/\1/') diff --git a/.github/workflows/visual-tests-demos.yml b/.github/workflows/visual-tests-demos.yml index 7cce89dceca6..753c6a6e5771 100644 --- a/.github/workflows/visual-tests-demos.yml +++ b/.github/workflows/visual-tests-demos.yml @@ -189,46 +189,6 @@ jobs: fi echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - determine-csp-frameworks-matrix: - runs-on: ubuntu-latest - name: Determine csp-check-frameworks matrix - needs: [check-should-run, determine-framework-tests-scope] - if: | - always() && - needs.check-should-run.outputs.should-run == 'true' && - needs.determine-framework-tests-scope.result == 'success' && - needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' - outputs: - matrix: ${{ steps.matrix.outputs.matrix }} - steps: - - name: Build matrix - id: matrix - env: - IS_RETRY: ${{ inputs.is_retry }} - RETRY_TARGETS: ${{ inputs.retry_targets }} - run: | - set -euo pipefail - if [ "$IS_RETRY" = "true" ]; then - matrix=$(echo "$RETRY_TARGETS" | jq -c '.["csp-check-frameworks"] // {"include":[]}') - else - matrix=$(cat <<'JSON' - { - "include": [ - { "FRAMEWORK": "React", "SHARD_INDEX": 1, "SHARD_TOTAL": 2 }, - { "FRAMEWORK": "React", "SHARD_INDEX": 2, "SHARD_TOTAL": 2 }, - { "FRAMEWORK": "Vue", "SHARD_INDEX": 1, "SHARD_TOTAL": 2 }, - { "FRAMEWORK": "Vue", "SHARD_INDEX": 2, "SHARD_TOTAL": 2 }, - { "FRAMEWORK": "Angular", "SHARD_INDEX": 1, "SHARD_TOTAL": 3 }, - { "FRAMEWORK": "Angular", "SHARD_INDEX": 2, "SHARD_TOTAL": 3 }, - { "FRAMEWORK": "Angular", "SHARD_INDEX": 3, "SHARD_TOTAL": 3 } - ] - } - JSON - ) - matrix=$(echo "$matrix" | jq -c .) - fi - echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - build-devextreme: runs-on: devextreme-shr2 name: Build DevExtreme @@ -1239,10 +1199,8 @@ jobs: if: | always() && needs.check-should-run.outputs.should-run == 'true' && - ( - (inputs.is_retry != true && needs.build-devextreme.result == 'success') || - (inputs.is_retry == true && fromJson(inputs.retry_targets)['csp-check-jquery'] == true) - ) + needs.build-devextreme.result == 'success' && + inputs.is_retry != true runs-on: devextreme-shr2 timeout-minutes: 60 env: @@ -1308,24 +1266,30 @@ jobs: name: csp-violations-jquery path: apps/demos/csp-reports/ if-no-files-found: ignore - overwrite: true csp-check-frameworks: name: ${{ matrix.SHARD_TOTAL == 1 && format('CSP check ({0})', matrix.FRAMEWORK) || format('CSP check ({0} {1}/{2})', matrix.FRAMEWORK, matrix.SHARD_INDEX, matrix.SHARD_TOTAL) }} - needs: [check-should-run, determine-framework-tests-scope, build-devextreme, determine-csp-frameworks-matrix] + needs: [check-should-run, determine-framework-tests-scope, build-devextreme] if: | always() && needs.check-should-run.outputs.should-run == 'true' && needs.determine-framework-tests-scope.result == 'success' && needs.determine-framework-tests-scope.outputs.framework-tests-scope != 'none' && - needs.determine-csp-frameworks-matrix.result == 'success' && - ( - (inputs.is_retry != true && needs.build-devextreme.result == 'success') || - (inputs.is_retry == true && fromJson(inputs.retry_targets)['csp-check-frameworks'] != null) - ) + needs.build-devextreme.result == 'success' && + inputs.is_retry != true strategy: fail-fast: false - matrix: ${{ fromJson(needs.determine-csp-frameworks-matrix.outputs.matrix) }} + # Angular AOT bundling is slow, so it is split into parallel shards + # (CSP_SHARD_*, round-robin in csp-bundle-angular.js). React/Vue stay light. + matrix: + include: + - { FRAMEWORK: React, SHARD_INDEX: 1, SHARD_TOTAL: 2 } + - { FRAMEWORK: React, SHARD_INDEX: 2, SHARD_TOTAL: 2 } + - { FRAMEWORK: Vue, SHARD_INDEX: 1, SHARD_TOTAL: 2 } + - { FRAMEWORK: Vue, SHARD_INDEX: 2, SHARD_TOTAL: 2 } + - { FRAMEWORK: Angular, SHARD_INDEX: 1, SHARD_TOTAL: 3 } + - { FRAMEWORK: Angular, SHARD_INDEX: 2, SHARD_TOTAL: 3 } + - { FRAMEWORK: Angular, SHARD_INDEX: 3, SHARD_TOTAL: 3 } runs-on: devextreme-shr2 timeout-minutes: 60 env: @@ -1402,7 +1366,6 @@ jobs: name: csp-violations-${{ matrix.FRAMEWORK }}${{ matrix.SHARD_TOTAL != 1 && format('-shard{0}', matrix.SHARD_INDEX) || '' }} path: apps/demos/csp-reports/ if-no-files-found: ignore - overwrite: true csp-report-summary: name: CSP Violations Summary @@ -1474,4 +1437,3 @@ jobs: name: csp-violations-report path: apps/demos/csp-reports/ if-no-files-found: ignore - overwrite: true