Repository navigation
chore(deps): bump actions/github-script from 8 to 9 (#16) #45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E | |
| on: | |
| pull_request: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| inputs: | |
| profile: | |
| description: Execution profile from e2e/ci.yml. | |
| required: true | |
| default: regression | |
| type: string | |
| projects: | |
| description: Optional project override, one per line. | |
| required: false | |
| default: "" | |
| type: string | |
| labels: | |
| description: Optional tag override, one per line. | |
| required: false | |
| default: "" | |
| type: string | |
| label-match: | |
| description: Optional tag matching override. | |
| required: false | |
| default: profile | |
| type: choice | |
| options: | |
| - profile | |
| - all | |
| - any | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: e2e-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| e2e: | |
| name: E2E Gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| env: | |
| COMPOSE_PROJECT_NAME: e2e-${{ github.run_id }}-${{ github.run_attempt }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v7 | |
| - id: sut | |
| name: Start example SUT and wait for readiness | |
| continue-on-error: true | |
| shell: bash | |
| run: | | |
| mkdir -p sut-logs | |
| docker compose -f compose.e2e.yml up --detach --build --wait 2>&1 | tee sut-logs/startup.log | |
| - id: e2e | |
| name: Run E2E | |
| if: steps.sut.outcome == 'success' | |
| continue-on-error: true | |
| uses: $/ | |
| env: | |
| DEMO_RUNTIME_TOKEN: synthetic-token-not-a-secret | |
| DEMO_RUNTIME_MESSAGE: |- | |
| hello from the workflow | |
| spaces, "quotes", $dollar and = survive | |
| DEMO_UNLISTED_TOKEN: must-stay-outside-the-runner | |
| with: | |
| runtime-env: | | |
| DEMO_RUNTIME_TOKEN | |
| DEMO_RUNTIME_MESSAGE | |
| profile: >- | |
| ${{ github.event_name == 'pull_request' && 'pull-request' || | |
| github.event_name == 'push' && 'main' || inputs.profile }} | |
| projects: ${{ github.event_name == 'workflow_dispatch' && inputs.projects || '' }} | |
| labels: ${{ github.event_name == 'workflow_dispatch' && inputs.labels || '' }} | |
| label-match: >- | |
| ${{ github.event_name == 'workflow_dispatch' && inputs.label-match != 'profile' && | |
| inputs.label-match || '' }} | |
| - id: sut-logs | |
| name: Capture example SUT logs | |
| if: always() && steps.sut.outcome != 'skipped' | |
| continue-on-error: true | |
| shell: bash | |
| run: | | |
| mkdir -p sut-logs | |
| docker compose -f compose.e2e.yml logs --no-color --timestamps > sut-logs/services.log 2>&1 | |
| - id: sut-cleanup | |
| name: Stop example SUT | |
| if: always() && steps.sut.outcome != 'skipped' | |
| continue-on-error: true | |
| shell: bash | |
| run: | | |
| mkdir -p sut-logs | |
| docker compose -f compose.e2e.yml down --volumes --remove-orphans > sut-logs/teardown.log 2>&1 | |
| - id: sut-artifact | |
| name: Upload example SUT logs | |
| if: always() && steps.sut.outcome != 'skipped' | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sut-logs | |
| path: sut-logs/ | |
| if-no-files-found: error | |
| retention-days: 10 | |
| - name: Update pull request | |
| if: always() && github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| env: | |
| E2E_RESULT: ${{ steps.e2e.outputs.result }} | |
| E2E_SUT_SETUP: ${{ steps.sut.outcome }} | |
| E2E_SUT_CLEANUP: ${{ steps.sut-cleanup.outcome }} | |
| E2E_SUT_LOGS: ${{ steps.sut-logs.outcome }} | |
| E2E_SUT_ARTIFACT: ${{ steps.sut-artifact.outcome }} | |
| E2E_PRIMARY_FAILURE: ${{ steps.e2e.outputs.primary-failure }} | |
| E2E_TOTAL: ${{ steps.e2e.outputs.total }} | |
| E2E_PASSED: ${{ steps.e2e.outputs.passed }} | |
| E2E_FAILED: ${{ steps.e2e.outputs.failed }} | |
| E2E_SKIPPED: ${{ steps.e2e.outputs.skipped }} | |
| E2E_PROFILE: ${{ steps.e2e.outputs.profile }} | |
| E2E_PROJECTS: ${{ steps.e2e.outputs.projects }} | |
| E2E_LABELS: ${{ steps.e2e.outputs.labels }} | |
| E2E_LABEL_MATCH: ${{ steps.e2e.outputs.label-match }} | |
| E2E_RUNNER_CACHE_HIT: ${{ steps.e2e.outputs.runner-cache-hit }} | |
| E2E_RUNNER_IMAGE: ${{ steps.e2e.outputs.runner-image-name }} | |
| E2E_RESULTS_ARTIFACT: ${{ steps.e2e.outputs.results-artifact }} | |
| E2E_REPORT_IMAGE: ${{ steps.e2e.outputs.report-image-name }} | |
| E2E_REPORT_ARTIFACT: ${{ steps.e2e.outputs.report-image-artifact }} | |
| with: | |
| script: | | |
| const marker = '<!-- playwright-e2e-report -->'; | |
| const value = (name, fallback = 'n/a') => process.env[name] || fallback; | |
| const list = (name) => value(name).split('\n').filter(Boolean).join(', '); | |
| const actionResult = value('E2E_RESULT', 'infrastructure-error'); | |
| const lifecycleFailures = ['E2E_SUT_SETUP', 'E2E_SUT_CLEANUP', 'E2E_SUT_LOGS', 'E2E_SUT_ARTIFACT'] | |
| .filter((name) => value(name) !== 'success'); | |
| const result = actionResult === 'failed' | |
| ? 'failed' | |
| : lifecycleFailures.length ? 'infrastructure-error' : actionResult; | |
| const icon = result === 'passed' ? '✅' : result === 'failed' ? '❌' : '⚠️'; | |
| const cache = value('E2E_RUNNER_CACHE_HIT', 'false') === 'true' ? 'reused' : 'built'; | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const rows = [ | |
| ['Gate', `${icon} ${result}`], | |
| ['Caller SUT', `setup: ${value('E2E_SUT_SETUP')} · cleanup: ${value('E2E_SUT_CLEANUP')} · artifact: sut-logs`], | |
| ['Tests', `${value('E2E_PASSED', '0')} passed · ${value('E2E_FAILED', '0')} failed · ${value('E2E_SKIPPED', '0')} skipped · ${value('E2E_TOTAL', '0')} total`], | |
| ['Profile', value('E2E_PROFILE')], | |
| ['Projects', list('E2E_PROJECTS')], | |
| ['Tags', `${list('E2E_LABELS')} (${value('E2E_LABEL_MATCH')})`], | |
| ['Runner', `${value('E2E_RUNNER_IMAGE')} (${cache})`], | |
| ['Results', value('E2E_RESULTS_ARTIFACT')], | |
| ['Report image', `${value('E2E_REPORT_IMAGE')} · ${value('E2E_REPORT_ARTIFACT')}`], | |
| ['Run', `[Open workflow run](${runUrl})`], | |
| ]; | |
| const failureMessage = process.env.E2E_PRIMARY_FAILURE || | |
| (lifecycleFailures.length ? `Caller lifecycle did not succeed: ${lifecycleFailures.join(', ')}` : ''); | |
| const failure = failureMessage | |
| ? `\n> ${failureMessage}\n` | |
| : ''; | |
| const body = [ | |
| marker, | |
| '## E2E test report', | |
| '', | |
| '| | |', | |
| '|---|---|', | |
| ...rows.map(([name, detail]) => `| ${name} | ${detail} |`), | |
| failure, | |
| ].join('\n'); | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| ...context.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100, | |
| }); | |
| const previous = comments.find((comment) => | |
| comment.user?.type === 'Bot' && comment.body?.includes(marker)); | |
| if (previous) { | |
| await github.rest.issues.updateComment({ | |
| ...context.repo, | |
| comment_id: previous.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| ...context.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Preserve E2E result | |
| if: always() | |
| shell: bash | |
| env: | |
| E2E_STEP_OUTCOME: ${{ steps.e2e.outcome }} | |
| SUT_SETUP_OUTCOME: ${{ steps.sut.outcome }} | |
| SUT_LOGS_OUTCOME: ${{ steps.sut-logs.outcome }} | |
| SUT_CLEANUP_OUTCOME: ${{ steps.sut-cleanup.outcome }} | |
| SUT_ARTIFACT_OUTCOME: ${{ steps.sut-artifact.outcome }} | |
| run: | | |
| for outcome in "$E2E_STEP_OUTCOME" "$SUT_SETUP_OUTCOME" "$SUT_LOGS_OUTCOME" "$SUT_CLEANUP_OUTCOME" "$SUT_ARTIFACT_OUTCOME"; do | |
| if [[ "$outcome" != "success" ]]; then | |
| exit 1 | |
| fi | |
| done |