From 4b2fc8bdc1d0977dc5a4e5815b371ce653e1050c Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Thu, 22 Oct 2026 14:53:32 +0200 Subject: [PATCH] ci: Always report optional E2E results and update the report on re-runs Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 3 +- .github/workflows/optional-e2e.yml | 33 ++++++++++++++++ .../optional-e2e-gh-action/action.yml | 18 ++++++--- dev-packages/optional-e2e-gh-action/index.mjs | 39 ++++++++++++------- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 343eccc16d35..23377c9c06cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1153,7 +1153,7 @@ jobs: # - We skip optional tests on release branches # - The tests run in optional-e2e.yml, so their failures do not mark the PR as failed. This job only waits for - # them and reports failures in a PR comment. + # them. optional-e2e.yml reports the results in a PR comment. job_optional_e2e_tests: name: E2E Tests (optional) # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks @@ -1171,7 +1171,6 @@ jobs: permissions: contents: read actions: write - pull-requests: write steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) uses: actions/checkout@v7 diff --git a/.github/workflows/optional-e2e.yml b/.github/workflows/optional-e2e.yml index 8374ea398ec0..ffe170e70231 100644 --- a/.github/workflows/optional-e2e.yml +++ b/.github/workflows/optional-e2e.yml @@ -115,3 +115,36 @@ jobs: working-directory: ${{ runner.temp }}/test-application timeout-minutes: 10 run: ${{ matrix.assert-command || 'pnpm test:assert' }} + + # A separate job in this workflow, so that a re-run of this run (all jobs or only the failed ones) also updates the + # comment. GitHub re-runs the jobs that depend on a re-run job. + job_report: + name: Report in PR comment + needs: [job_optional_e2e_tests] + # Not for a cancelled run: a new push cancels the old run, and the new run reports. + if: ${{ !cancelled() && github.event_name == 'workflow_dispatch' && inputs.pr_number != '' }} + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + actions: read + pull-requests: write + steps: + - name: Check out commit ${{ env.HEAD_COMMIT }} + uses: actions/checkout@v7 + with: + ref: ${{ env.HEAD_COMMIT }} + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version-file: 'package.json' + - name: Install pnpm + uses: ./.github/actions/install-pnpm + - name: Install action dependencies + run: pnpm install --frozen-lockfile --filter @sentry-internal/optional-e2e-gh-action + - name: Report optional E2E results + uses: ./dev-packages/optional-e2e-gh-action + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + mode: report + pr_number: ${{ inputs.pr_number }} diff --git a/dev-packages/optional-e2e-gh-action/action.yml b/dev-packages/optional-e2e-gh-action/action.yml index 684ee62d491f..54edd85f97df 100644 --- a/dev-packages/optional-e2e-gh-action/action.yml +++ b/dev-packages/optional-e2e-gh-action/action.yml @@ -1,15 +1,23 @@ name: 'optional-e2e-gh-action' -description: 'Run the optional E2E tests in their own workflow, wait for them and report failures in a PR comment' +description: + 'Run the optional E2E tests in their own workflow and wait for them, or report their results in a PR comment' inputs: github_token: required: true description: 'a github access token' + mode: + required: false + default: 'dispatch' + description: '`dispatch` to run the tests and wait for them, `report` to report the current run in a PR comment' commit: - required: true - description: 'The commit that the tarballs were built from' + required: false + description: 'The commit that the tarballs were built from (dispatch mode)' matrix: - required: true - description: 'The optional E2E test matrix as JSON' + required: false + description: 'The optional E2E test matrix as JSON (dispatch mode)' + pr_number: + required: false + description: 'The PR to report in (report mode)' runs: using: 'node24' main: 'index.mjs' diff --git a/dev-packages/optional-e2e-gh-action/index.mjs b/dev-packages/optional-e2e-gh-action/index.mjs index 54136e26542f..59194dbc4bd2 100644 --- a/dev-packages/optional-e2e-gh-action/index.mjs +++ b/dev-packages/optional-e2e-gh-action/index.mjs @@ -12,6 +12,12 @@ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); async function run() { const octokit = getOctokit(core.getInput('github_token')); const { repo } = context; + + if (core.getInput('mode') === 'report') { + await report(octokit, repo); + return; + } + const pr = context.payload.pull_request; const correlationId = `${context.runId}-${process.env.GITHUB_RUN_ATTEMPT}`; @@ -41,14 +47,20 @@ async function run() { if (workflowRun.conclusion !== 'success') { core.warning(`Optional E2E tests ${workflowRun.conclusion}: ${workflowRun.html_url}`); } +} - if (pr) { - // A failed report must not fail this job, because that marks the PR as failed. - try { - await reportInComment(octokit, repo, pr.number, workflowRun); - } catch (error) { - core.warning(`Could not report the optional E2E results in a PR comment: ${error.message}`); - } +// Runs as the last job of the optional E2E run itself, so a re-run of that run also updates the comment. +async function report(octokit, repo) { + const prNumber = Number(core.getInput('pr_number')); + if (!prNumber) { + return; + } + + try { + const { data: workflowRun } = await octokit.rest.actions.getWorkflowRun({ ...repo, run_id: context.runId }); + await reportInComment(octokit, repo, prNumber, workflowRun); + } catch (error) { + core.warning(`Could not report the optional E2E results in a PR comment: ${error.message}`); } } @@ -80,8 +92,7 @@ async function waitForRun(octokit, repo, workflowId, correlationId) { return workflowRun; } -// Keeps one comment per PR up to date, so new pushes do not add more comments. The comment is only -// created when a check fails. +// Keeps one comment per PR up to date, so new pushes and re-runs do not add more comments. async function reportInComment(octokit, repo, prNumber, workflowRun) { const jobs = await octokit.paginate(octokit.rest.actions.listJobsForWorkflowRun, { ...repo, @@ -89,9 +100,13 @@ async function reportInComment(octokit, repo, prNumber, workflowRun) { filter: 'latest', per_page: 100, }); - const ran = jobs.filter(({ conclusion }) => conclusion !== 'skipped'); + // The report job itself is still in progress. + const ran = jobs.filter(({ status, conclusion }) => status === 'completed' && conclusion !== 'skipped'); const failing = ran.filter(({ conclusion }) => conclusion !== 'success'); const successful = ran.filter(({ conclusion }) => conclusion === 'success'); + if (!ran.length) { + return; + } const comments = await octokit.paginate(octokit.rest.issues.listComments, { ...repo, @@ -102,10 +117,6 @@ async function reportInComment(octokit, repo, prNumber, workflowRun) { ({ user, body }) => user?.login === 'github-actions[bot]' && body?.startsWith(COMMENT_MARKER), ); - if (!failing.length && !previousComment) { - return; - } - const counts = [ failing.length && `${failing.length} failing`, successful.length && `${successful.length} successful`,