Repository navigation
Selenium Tests #860
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: Selenium Tests | |
| on: | |
| schedule: | |
| - cron: '0 10 * * *' | |
| # develop is the head of the deploy chain: this suite runs first, then the | |
| # Terraform workflow (workflow_run on success), then the test-site deploy. | |
| push: | |
| branches: [ master, develop ] | |
| # Run on PRs targeting master or develop (branches = the PR's base branch). | |
| pull_request: | |
| branches: [ master, develop ] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # Pulling the private image mirror and pushing what the run built, both on | |
| # ghcr.io. See docker-compose-run-tests.yml's header. | |
| packages: write | |
| jobs: | |
| # THE IMAGE MIRROR, BEFORE EITHER STACK IS BUILT. Everything the two jobs | |
| # below pull or build FROM comes from private copies on ghcr.io, so that a | |
| # reset connection to Docker Hub (the 2026-09-27 scheduled run died three | |
| # seconds in on one) cannot take the suite down. `missing` copies only an | |
| # image the mirror does not have yet — added to .github/image-mirror.txt in | |
| # this commit, say — so a complete mirror costs one manifest request per | |
| # image and no request to an upstream registry at all. | |
| # | |
| # A job here rather than a call to mirror-images.yml, deliberately: a called | |
| # workflow that declares packages: write fails OUTRIGHT when the caller's | |
| # token is read-only, which it is on a Dependabot or a fork pull request. | |
| # Inline, the token is simply downgraded, and with a complete mirror this | |
| # job only reads. | |
| mirror: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v7 | |
| - name: Log in to GitHub Container Registry | |
| run: echo "${{ secrets.GITHUB_TOKEN }}" | | |
| docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| - name: Copy any image the mirror does not have yet | |
| run: .github/scripts/mirror-images.sh missing | |
| # Plain suite: full battery in containers, no coverage. Wraps | |
| # ./docker-run-tests.sh, which builds the stack (docker-compose-run-tests.yml), | |
| # runs the Selenium suite, and always tears the stack down. | |
| test: | |
| needs: mirror | |
| runs-on: ubuntu-latest | |
| env: | |
| # The name every image this job builds is tagged with, and pushed under | |
| # (the api, the client, the mock STS, the tests and the WS-Federation | |
| # side-car; common.sh derives STS_IMAGE from it). | |
| IMAGE_TAG: ${{ github.sha }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v7 | |
| with: | |
| # sts/ is a submodule (https://github.com/rcbj/iya-sts.git). The STS | |
| # image is built from it and tests/Dockerfile copies a module out of it, | |
| # so without it both builds fail — saying "failed to read dockerfile" | |
| # and "COPY sts/common/vendored/bbs2023.js: not found", neither of which names a | |
| # submodule. The launcher initialises it too (requireMockStsCheckout in | |
| # common/common.sh); asking for it here keeps a fetch failure on the | |
| # checkout step, where it reads as one. | |
| # `recursive`, not `true`, and the difference is load-bearing: there | |
| # are now submodules INSIDE submodules. api/node-ldapjs is the LDAP | |
| # library the api's client uses, and sts/node-ldapjs is the same | |
| # library inside the mock STS, which its embedded directory is built | |
| # on. `true` stops one level short of the second, and an uninitialised | |
| # submodule is an EMPTY DIRECTORY — so the sts image builds, npm | |
| # installs a package with no main, and the container dies at startup | |
| # with `Cannot find module 'ldapjs'`, naming a package rather than a | |
| # submodule. | |
| submodules: recursive | |
| - name: Install docker-compose | |
| uses: KengoTODA/actions-setup-docker-compose@v1 | |
| with: | |
| version: '5.2.0' | |
| # THE IMAGE MIRROR AND THE BUILT IMAGES ARE PRIVATE PACKAGES on ghcr.io. | |
| # GITHUB_TOKEN carries packages: write (above), and a package this | |
| # repository's workflows created grants it access. Compose runs WITHOUT | |
| # sudo on a runner, so this login is the one it uses. | |
| - name: Log in to GitHub Container Registry | |
| run: echo "${{ secrets.GITHUB_TOKEN }}" | | |
| docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| # The script's docker_compose() (common/common.sh) invokes compose with | |
| # sudo itself, so it runs unprivileged here. | |
| - name: Run Selenium test suite | |
| # See the coverage job below for why this is not the mock's default | |
| # of `debug`. | |
| env: | |
| STS_LOG_LEVEL: info | |
| run: ./docker-run-tests.sh | |
| # What this run built, to ghcr.io: under the commit and under the | |
| # branch name. Not from a pull request, whose GITHUB_TOKEN may not write | |
| # packages and whose images are nobody's to keep. | |
| - name: Push built images | |
| if: (success() || failure()) && github.event_name != 'pull_request' | |
| run: .github/scripts/push-stack-images.sh "${{ github.ref_name }}" | |
| # The tests container writes tests/report as root via a bind mount; hand it | |
| # back to the runner user before the upload step reads it. | |
| - name: Normalize report permissions | |
| if: always() | |
| run: sudo chown -R "$(id -u):$(id -g)" tests/report || true | |
| - name: Upload Test Report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: test-report | |
| path: tests/report/latest | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| # Coverage suite: same battery WITH frontend (Istanbul) + API (c8) coverage. | |
| # Wraps ./run-coverage.sh, which layers docker-compose-coverage.yml on top, | |
| # runs the suite, renders the coverage reports, and tears the stack down. | |
| # TEST_CONCURRENCY IS DELIBERATELY NOT SET HERE. It defaults to 3, and 4 | |
| # and 6 were both tried on 2026-08-31 and both made the run RED. | |
| # | |
| # 3 1,396s wall 281 passed, 0 failed | |
| # 4 1,306s wall 278 passed, 3 FAILED | |
| # 6 1,266s wall 277 passed, 4 FAILED | |
| # | |
| # The binding constraint is ONE JOB, not the pool. [109] Digital | |
| # Signature takes 594s of a 900s TEST_JOB_TIMEOUT_MS watchdog at 3 -- | |
| # already two thirds of its budget -- and adding a single slot pushes it | |
| # PAST 900s, where it is killed outright. These runners have four cores | |
| # and that job is CPU-bound crypto in a browser; the rest of the failures | |
| # are the same contention seen from the other side, browser jobs whose | |
| # waitTime is 2 seconds reporting a race as an assertion about the page. | |
| # | |
| # THAT LAST SENTENCE IS ALSO WHAT WENT WRONG AT 3 ON 2026-09-01, so read it | |
| # before raising this again: 288 of 289 jobs passed and [08] OAuth2 | |
| # Authorization Code (public, PKCE=false) failed on a two-second wait for | |
| # `#token_client_id` after the Keycloak redirect — an instrumented page's | |
| # load time, reported as an assertion about the page. run-coverage.sh now | |
| # exports TEST_WAIT_TIME_MS=10000 for this run alone, and the client's log | |
| # level went to `info` with it. Neither touches the pool: the binding | |
| # constraint above is still one job, and this number is still 3. | |
| # | |
| # So raising this number buys ~7% of wall clock and costs the run. The | |
| # levers that would actually work are SPLITTING job 109, or a runner with | |
| # more cores. Fix one of those before touching this again. | |
| # THE COVERAGE JOB IS SWITCHED OFF (2026-10-02). On run 36984561174 the | |
| # hosted runner lost contact with GitHub about a third of the way through | |
| # the instrumented suite and was failed an hour later, with no step after | |
| # the suite run. The plain test job on the same commit passed. A | |
| # standard ubuntu-latest runner cannot be given more memory or cores, so | |
| # until this job has a larger runner, run ./run-coverage.sh locally when | |
| # a coverage report is wanted. To restore it, uncomment the block below. | |
| # coverage: | |
| # needs: mirror | |
| # runs-on: ubuntu-latest | |
| # env: | |
| # # Its own tag: these images are built with COVERAGE=true, so they are | |
| # # not the same images as the test job's under the same names. | |
| # IMAGE_TAG: ${{ github.sha }}-coverage | |
| # steps: | |
| # - name: Checkout Repository | |
| # uses: actions/checkout@v7 | |
| # with: | |
| # # The mock STS submodule — see the test job above. This run uses | |
| # # --abort-on-container-exit, so a service that cannot be built takes the | |
| # # whole coverage run down with it. | |
| # # `recursive`, not `true`, and the difference is load-bearing: there | |
| # # are now submodules INSIDE submodules. api/node-ldapjs is the LDAP | |
| # # library the api's client uses, and sts/node-ldapjs is the same | |
| # # library inside the mock STS, which its embedded directory is built | |
| # # on. `true` stops one level short of the second, and an uninitialised | |
| # # submodule is an EMPTY DIRECTORY — so the sts image builds, npm | |
| # # installs a package with no main, and the container dies at startup | |
| # # with `Cannot find module 'ldapjs'`, naming a package rather than a | |
| # # submodule. | |
| # submodules: recursive | |
| # - name: Install docker-compose | |
| # uses: KengoTODA/actions-setup-docker-compose@v1 | |
| # with: | |
| # version: '5.2.0' | |
| # # THE IMAGE MIRROR AND THE BUILT IMAGES ARE PRIVATE PACKAGES on ghcr.io. | |
| # # GITHUB_TOKEN carries packages: write (above), and a package this | |
| # # repository's workflows created grants it access. Compose runs WITHOUT | |
| # # sudo on a runner, so this login is the one it uses. | |
| # - name: Log in to GitHub Container Registry | |
| # run: echo "${{ secrets.GITHUB_TOKEN }}" | | |
| # docker login ghcr.io -u "${{ github.actor }}" --password-stdin | |
| # - name: Run test suite with coverage | |
| # # `info`, not the mock's own default of `debug`. That log is the record | |
| # # of everything the mock issued and is why debug is the default for a | |
| # # run somebody is watching — but nobody reads a passing CI run's log, | |
| # # and run-coverage.sh's own header measures what it costs: about half | |
| # # of that service's CPU, 156MB written in sixteen seconds, and `info` | |
| # # roughly doubles what one instance can answer with several jobs | |
| # # driving it. The variable outranks whatever env/docker-tests.js says, | |
| # # so nothing on this machine or anybody else's changes with it. | |
| # # | |
| # # THE api AND THE CLIENT ARE AT `info` TOO NOW, and not through a | |
| # # variable — issue #269's second half was a decision rather than a | |
| # # knob. Their level comes from the config file alone and the client's | |
| # # is compiled into the browser bundle by browserify, so no runtime | |
| # # variable can reach either; `api/env/docker-tests.js` changed on | |
| # # 2026-08-31 and `client/src/env/docker-tests.js` on 2026-09-01, each | |
| # # saying so beside the key. `env/local.js` stays at `debug` on both, | |
| # # which is the stack somebody watches. The client's mattered most: | |
| # # that one is what every PAGE logs at, in Chrome, while this job is | |
| # # serving Istanbul-instrumented bundles to a pool of jobs whose every | |
| # # browser wait is two seconds. | |
| # env: | |
| # STS_LOG_LEVEL: info | |
| # run: ./run-coverage.sh | |
| # - name: Push built images | |
| # if: (success() || failure()) && github.event_name != 'pull_request' | |
| # run: .github/scripts/push-stack-images.sh | |
| # "${{ github.ref_name }}-coverage" | |
| # # coverage/ and tests/report are written as root inside the containers. | |
| # - name: Normalize coverage permissions | |
| # if: always() | |
| # run: sudo chown -R "$(id -u):$(id -g)" coverage tests/report || true | |
| # - name: Upload coverage report | |
| # if: always() | |
| # uses: actions/upload-artifact@v7 | |
| # with: | |
| # name: coverage-report | |
| # path: coverage/ | |
| # if-no-files-found: warn | |
| # retention-days: 30 | |
| # # Distinct artifact name from the test job (artifact names must be unique | |
| # # across a workflow run). | |
| # - name: Upload Test Report | |
| # if: always() | |
| # uses: actions/upload-artifact@v7 | |
| # with: | |
| # name: coverage-test-report | |
| # path: tests/report/latest | |
| # if-no-files-found: warn | |
| # retention-days: 30 | |