ci(DEVA11Y-735): PR smoke test for a11y-scan SPM plugin (end-to-end scan) #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
| # Smoke-tests the `a11y-scan` SwiftPM command plugin end-to-end on every PR: | |
| # builds the plugin and runs a real accessibility scan against the tests/spm | |
| # harness (sample SwiftUI sources with intentional a11y issues). It reuses the | |
| # repository's own gated integration test (testA11yScanPluginRuns) so the scan | |
| # invocation stays defined in exactly one place. | |
| # | |
| # The scan downloads the BrowserStack CLI and makes authenticated network calls, | |
| # so it needs BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY repo secrets. Those | |
| # secrets are never exposed to fork PRs, so that job is gated to same-repo PRs | |
| # (and manual dispatch); fork PRs skip it. The scan step is itself guarded on the | |
| # secrets being present, so if they are not configured the scan is skipped and the | |
| # job still passes on the build step alone. | |
| # | |
| # A second job (scripts-lint) syntax-checks every launcher script under scripts/. | |
| # It needs no secrets, so it runs on all PRs including forks. | |
| name: SPM plugin smoke test | |
| on: | |
| pull_request: | |
| branches: [main, master] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: spm-smoke-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| spm-smoke: | |
| name: a11y-scan end-to-end (SwiftPM) | |
| runs-on: macos-14 | |
| timeout-minutes: 25 | |
| # Secrets are unavailable to fork PRs, so the authenticated scan can only run | |
| # on same-repo PRs or a manual dispatch. Fork PRs skip this job. | |
| if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.fork == false | |
| env: | |
| BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} | |
| BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} | |
| # Un-gates tests/spm/Tests/A11yDemoLibTests/testA11yScanPluginRuns, which is | |
| # skipped unless RUN_A11Y_SCAN=1 and BrowserStack credentials are present. | |
| RUN_A11Y_SCAN: "1" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Swift toolchain | |
| run: swift --version | |
| # The repo root is a plugin-only package (no buildable target), so it is | |
| # not built directly. Building the tests/spm harness compiles both the | |
| # a11y-scan command plugin (via the path dependency) and the sample sources. | |
| - name: Build harness (compiles the a11y-scan plugin) | |
| working-directory: tests/spm | |
| run: swift build | |
| # Guarded on the secrets actually being set: GitHub exposes an unset secret | |
| # as an empty string (present, not nil), so without this guard the scan would | |
| # run with empty credentials and fail. When the secrets are absent this step | |
| # is skipped and the job stays green on the build step alone. | |
| - name: End-to-end scan smoke (tests/spm) | |
| if: env.BROWSERSTACK_USERNAME != '' && env.BROWSERSTACK_ACCESS_KEY != '' | |
| working-directory: tests/spm | |
| run: swift test | |
| scripts-lint: | |
| name: Launcher scripts (bash syntax) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| # Every script under scripts/ — the bash, zsh and fish variants alike — is | |
| # a bash script (`#!/usr/bin/env bash -il`); the variants differ only in which | |
| # login shell they source BrowserStack creds from. So all of them are | |
| # syntax-checked with `bash -n`. The scripts self-update, register git | |
| # hooks and need credentials, so they are not executed here — this is a | |
| # static syntax gate. (Checksum integrity is covered separately by | |
| # verify-selfupdate-checksums.yml.) | |
| - name: Syntax-check all launcher scripts (bash -n) | |
| run: | | |
| set -uo pipefail | |
| shopt -s globstar nullglob | |
| scripts=(scripts/**/*.sh) | |
| if [ ${#scripts[@]} -eq 0 ]; then | |
| echo "::error::No .sh scripts found under scripts/ — checkout or glob is wrong." | |
| exit 1 | |
| fi | |
| status=0 | |
| for script in "${scripts[@]}"; do | |
| # Plain log lines, not ::notice file=/::error file= workflow commands: | |
| # scripts/ filenames are attacker-controllable on fork PRs, and | |
| # interpolating them into a workflow command is an injection vector. | |
| if bash -n "$script"; then | |
| echo "OK $script" | |
| else | |
| echo "FAILED $script (bash -n syntax error above)" | |
| status=1 | |
| fi | |
| done | |
| exit "$status" |