Documentation Tests (Changed Pages) #6
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: Documentation Tests (Changed Pages) | |
| description: Run the docs example tests for the pages a deploy actually changed | |
| # An early-warning signal, not a gate. doc-tests.yml runs the whole ~1000-spec estate nightly | |
| # and is the source of truth for example health; this runs only the pages touched by the commit | |
| # that was just deployed, so a broken example surfaces minutes after merge instead of the next | |
| # morning. Because the nightly covers everything anyway, this workflow is deliberately | |
| # best-effort: if it cannot work out what changed it logs why and exits green, it keeps no state | |
| # between runs, and a missed range costs at most a day of latency. Do not harden it into | |
| # something authoritative - that is what the nightly is for. | |
| # | |
| # These tests drive the deployed staging site, which is only deployed from `latest` (see the | |
| # `Deploy to Staging` step in ci.yml), so this hangs off CI's completion the same way | |
| # post-deploy-verification.yml does and tests what is now live. | |
| # | |
| # Scope: changed doc pages only. A grid *source* change can break any page, and the specs sitting | |
| # at the root of src/content/docs (example-demos, example-source-code, ...) sweep across all of | |
| # them - none of that is covered here. | |
| # | |
| # No concurrency group: each deploy tests its own range (previous deploy...this deploy), so | |
| # cancelling an in-flight run would leave that range untested. Runs are short; let them overlap. | |
| on: | |
| workflow_run: | |
| workflows: ['CI'] | |
| types: [completed] | |
| branches: ['latest'] | |
| workflow_dispatch: | |
| inputs: | |
| base_sha: | |
| description: 'Commit to diff against (blank = the previous successful CI deploy of latest)' | |
| required: false | |
| default: '' | |
| base_url: | |
| description: 'URL to test against' | |
| required: false | |
| default: 'https://grid-staging.ag-grid.com/' | |
| notify: | |
| description: 'Notify Slack on failure' | |
| type: boolean | |
| required: false | |
| default: false | |
| env: | |
| NX_NO_CLOUD: true | |
| CI: true | |
| DEFAULT_RETENTION_DAYS: 30 | |
| AG_LIBRARY: grid | |
| # The commit whose CI run deployed the site under test. For workflow_run that is the | |
| # triggering run's head, NOT github.sha: CI does not cancel an in-flight latest run, so a | |
| # second merge can already be default-branch HEAD while the first is still deploying, and | |
| # github.sha would then test the newer commit's specs against the older commit's deploy. | |
| # Reading head_sha is safe here because the job guard admits only same-repo pushes, so it | |
| # is a commit on our own latest; the untrusted-checkout pattern OSSF/Scorecard flags is a | |
| # fork's head, which that guard excludes. A dispatch has no triggering run, so github.sha. | |
| DEPLOYED_SHA: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| permissions: | |
| contents: read | |
| # read is sufficient: the only actions-scope consumer is `gh run list` for the baseline SHA. | |
| actions: read | |
| jobs: | |
| resolve: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| name: Resolve Changed Pages | |
| # Mirrors post-deploy-verification.yml: for workflow_run, only same-repo pushes whose CI run | |
| # succeeded, which keeps this off the untrusted-checkout path OSSF/Scorecard flags. | |
| if: > | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_repository.full_name == github.repository) | |
| outputs: | |
| patterns: ${{ steps.resolve.outputs.patterns }} | |
| count: ${{ steps.resolve.outputs.count }} | |
| base_sha: ${{ steps.baseline.outputs.sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ env.DEPLOYED_SHA }} | |
| # The diff needs both ends of the range and their merge base in the checkout. 50 | |
| # covers a normal merge cadence; beyond that the resolver skips rather than guessing. | |
| fetch-depth: 50 | |
| - name: Resolve baseline commit | |
| id: baseline | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| OVERRIDE: ${{ inputs.base_sha }} | |
| run: | | |
| if [ -n "${OVERRIDE}" ]; then | |
| echo "sha=${OVERRIDE}" >> "$GITHUB_OUTPUT" | |
| echo "Using the dispatched baseline ${OVERRIDE}." | |
| exit 0 | |
| fi | |
| # CI on latest is push-triggered, so each successful CI run's headSha is exactly the | |
| # commit it deployed. The previous deploy is therefore the newest successful CI run | |
| # other than our own that is an ancestor of the deployed commit. The ancestor check | |
| # covers a newer commit whose CI finished before ours triggered this workflow. | |
| SHA="" | |
| for CANDIDATE in $(gh run list \ | |
| --workflow CI \ | |
| --branch latest \ | |
| --event push \ | |
| --status success \ | |
| --limit 10 \ | |
| --json headSha \ | |
| --jq '.[].headSha'); do | |
| if [ "${CANDIDATE}" != "${DEPLOYED_SHA}" ] && git merge-base --is-ancestor "${CANDIDATE}" "${DEPLOYED_SHA}" 2>/dev/null; then | |
| SHA="${CANDIDATE}" | |
| break | |
| fi | |
| done | |
| echo "sha=${SHA}" >> "$GITHUB_OUTPUT" | |
| echo "Baseline: ${SHA:-none (no earlier successful CI run reachable in this checkout)}" | |
| - name: Resolve changed doc pages | |
| id: resolve | |
| # Both SHAs go through the environment rather than being interpolated into the command | |
| # line: base_sha is a dispatch input, so an expression here would be a shell-injection | |
| # point. | |
| env: | |
| BASE_SHA: ${{ steps.baseline.outputs.sha }} | |
| HEAD_SHA: ${{ env.DEPLOYED_SHA }} | |
| run: node scripts/ci/resolve-changed-doc-pages.mjs "${BASE_SHA}" "${HEAD_SHA}" | |
| test: | |
| needs: resolve | |
| if: ${{ needs.resolve.outputs.count != '0' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| name: Test Changed Pages | |
| # Scoped to this job rather than the workflow: the credential is only needed by the requests | |
| # to the deployed site, and report-publishing steps have no business reading it. | |
| env: | |
| AWS_CI_BYPASS_SECRET: ${{ secrets.AWS_CI_BYPASS_SECRET }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ env.DEPLOYED_SHA }} | |
| fetch-depth: 1 | |
| # Same composite action the nightly uses, so the two suites cannot drift in how they set up | |
| # or invoke Playwright. One job covering every framework on every browser, unsharded: a | |
| # filtered run is a few hundred tests at most, and splitting it would re-pay the cache | |
| # restore, community build and browser install per split for no test-time saving. The | |
| # nightly runs only vanilla and reactFunctionalTs across all three browsers because at | |
| # full estate size the rest is not worth the hours; that trade does not apply here. | |
| - uses: ./.github/actions/test-framework-examples | |
| with: | |
| ref: ${{ env.DEPLOYED_SHA }} | |
| base_url: ${{ inputs.base_url || 'https://grid-staging.ag-grid.com/' }} | |
| framework: all | |
| browsers: all | |
| test_pattern: ${{ needs.resolve.outputs.patterns }} | |
| report: | |
| needs: [resolve, test] | |
| if: always() && needs.test.result != 'skipped' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| name: Report | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| ref: ${{ env.DEPLOYED_SHA }} | |
| fetch-depth: 1 | |
| - name: Download test reports | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: combined-reports | |
| - name: Normalise example test reports | |
| run: node documentation/ag-grid-docs/scripts/normalise-ctrf-reports.mjs combined-reports | |
| - name: Publish combined test report | |
| uses: ctrf-io/github-test-reporter@e500b992f936420eb633c91644cf10d4d71df700 # v1.1.0 | |
| with: | |
| report-path: 'combined-reports/**/*.json' | |
| summary-report: true | |
| flaky-report: true | |
| skipped-report: false | |
| group-by: filePath | |
| write-ctrf-to-file: ./ctrf-report.json | |
| # Fixed-size, unlike the reporter's folded report, whose uncapped per-failure traces can | |
| # push the step summary past GitHub's 1MB limit and get the whole summary dropped. | |
| - name: Summarise failed tests | |
| if: always() | |
| run: node documentation/ag-grid-docs/scripts/summarise-ctrf-failures.mjs ./ctrf-report.json | |
| - name: Upload combined CTRF report | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ctrf-report | |
| path: ./ctrf-report.json | |
| retention-days: ${{ env.DEFAULT_RETENTION_DAYS }} | |
| # Failures only, and only for latest. A pass needs no announcement — this runs on every | |
| # deploy, so a "back to green" message per merge would drown the channel. No JIRA ticket | |
| # either: a failure here belongs to the commit that just landed, and the nightly owns | |
| # ticketing for the estate as a whole. | |
| - name: Notify Slack | |
| if: > | |
| always() && needs.test.result == 'failure' && | |
| github.ref_name == 'latest' && | |
| (github.event_name == 'workflow_run' || inputs.notify) | |
| continue-on-error: true | |
| uses: ./external/ag-shared/github/actions/slack-integration | |
| with: | |
| AG_LIBRARY: ${{ env.AG_LIBRARY }} | |
| SLACK_BOT_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_OAUTH_TOKEN }} | |
| SLACK_CHANNEL: '#ci-grid-gate' | |
| CTRF_REPORT_FILE: ./ctrf-report.json | |
| CURRENT_COMMIT_SHA: ${{ env.DEPLOYED_SHA }} | |
| PREV_COMMIT_SHA: ${{ needs.resolve.outputs.base_sha }} | |
| IS_SUCCESS: 'false' |